diff --git a/src/flamenco/features/fd_features_generated.c b/src/flamenco/features/fd_features_generated.c index b908aa98169..64f22eff02e 100644 --- a/src/flamenco/features/fd_features_generated.c +++ b/src/flamenco/features/fd_features_generated.c @@ -1691,6 +1691,12 @@ fd_feature_id_t const ids[] = { .name = "poseidon_enforce_padding", .cleaned_up = {UINT_MAX, UINT_MAX, UINT_MAX} }, + { .index = offsetof(fd_features_t, vote_state_v4)>>3, + .id = {"\xec\xfa\x3a\xf2\xab\xa3\x21\x89\x19\xf4\xc3\x4c\x05\xdf\x88\xf0\x79\x57\x48\xf1\x3e\x35\x12\x0b\x7b\x56\xaa\xae\x3f\xe9\x98\x58"}, + /* Gx4XFcrVMt4HUvPzTpTSVkdDVgcDSjKhDN1RqRS6KDuZ */ + .name = "vote_state_v4", + .cleaned_up = {UINT_MAX, UINT_MAX, UINT_MAX} }, + { .index = ULONG_MAX } }; /* TODO replace this with fd_map_perfect */ @@ -1944,6 +1950,7 @@ fd_feature_id_query( ulong prefix ) { case 0x520c5e674243fab5: return &ids[ 244 ]; case 0xf08a42c3c040e908: return &ids[ 245 ]; case 0x8c7bee4552d93e0c: return &ids[ 246 ]; + case 0x8921a3abf23afaec: return &ids[ 247 ]; default: break; } return NULL; @@ -2196,4 +2203,5 @@ FD_STATIC_ASSERT( offsetof( fd_features_t, stricter_abi_and_runtime_constraints FD_STATIC_ASSERT( offsetof( fd_features_t, account_data_direct_mapping )>>3==244UL, layout ); FD_STATIC_ASSERT( offsetof( fd_features_t, fix_alt_bn128_pairing_length_check )>>3==245UL, layout ); FD_STATIC_ASSERT( offsetof( fd_features_t, poseidon_enforce_padding )>>3==246UL, layout ); +FD_STATIC_ASSERT( offsetof( fd_features_t, vote_state_v4 )>>3==247UL, layout ); FD_STATIC_ASSERT( sizeof( fd_features_t )>>3==FD_FEATURE_ID_CNT, layout ); diff --git a/src/flamenco/features/fd_features_generated.h b/src/flamenco/features/fd_features_generated.h index 2223c5147e7..0c3b8da93ba 100644 --- a/src/flamenco/features/fd_features_generated.h +++ b/src/flamenco/features/fd_features_generated.h @@ -8,10 +8,10 @@ #endif /* FEATURE_ID_CNT is the number of features in ids */ -#define FD_FEATURE_ID_CNT (247UL) +#define FD_FEATURE_ID_CNT (248UL) /* Feature set ID calculated from all feature names */ -#define FD_FEATURE_SET_ID (2146234083U) +#define FD_FEATURE_SET_ID (1260307723U) union fd_features { ulong f[ FD_FEATURE_ID_CNT ]; @@ -263,5 +263,6 @@ union fd_features { /* 0x520c5e674243fab5 */ ulong account_data_direct_mapping; /* 0xf08a42c3c040e908 */ ulong fix_alt_bn128_pairing_length_check; /* 0x8c7bee4552d93e0c */ ulong poseidon_enforce_padding; + /* 0x8921a3abf23afaec */ ulong vote_state_v4; }; }; diff --git a/src/flamenco/features/feature_map.json b/src/flamenco/features/feature_map.json index 7f8c80e808b..096507bddcc 100644 --- a/src/flamenco/features/feature_map.json +++ b/src/flamenco/features/feature_map.json @@ -245,5 +245,6 @@ {"name":"stricter_abi_and_runtime_constraints","pubkey":"sD3uVpaavUXQRvDXrMFCQ2CqLqnbz5mK8ttWNXbtD3r","old":"CxeBn9PVeeXbmjbNwLv6U4C6svNxnC4JX6mfkvgeMocM"}, {"name":"account_data_direct_mapping","pubkey":"DFN8MyKpQqFW31qczcahgnnxcAHQc6P94wtTEX5EP1RA","old":"9s3RKimHWS44rJcJ9P1rwCmn2TvMqtZQBmz815ZUUHqJ"}, {"name":"fix_alt_bn128_pairing_length_check","pubkey":"bnYzodLwmybj7e1HAe98yZrdJTd7we69eMMLgCXqKZm"}, - {"name":"poseidon_enforce_padding","pubkey":"poUdAqRXXsNmfqAZ6UqpjbeYgwBygbfQLEvWSqVhSnb"} + {"name":"poseidon_enforce_padding","pubkey":"poUdAqRXXsNmfqAZ6UqpjbeYgwBygbfQLEvWSqVhSnb"}, + {"name":"vote_state_v4","pubkey":"Gx4XFcrVMt4HUvPzTpTSVkdDVgcDSjKhDN1RqRS6KDuZ"} ] diff --git a/src/flamenco/rewards/fd_rewards.c b/src/flamenco/rewards/fd_rewards.c index 41c993dcf95..0ddaa671972 100644 --- a/src/flamenco/rewards/fd_rewards.c +++ b/src/flamenco/rewards/fd_rewards.c @@ -116,6 +116,9 @@ get_vote_credits( uchar const * account_data, case fd_vote_state_versioned_enum_current: *credits = vsv->inner.current.epoch_credits; break; + case fd_vote_state_versioned_enum_v4: + *credits = vsv->inner.v4.epoch_credits; + break; default: __builtin_unreachable(); } diff --git a/src/flamenco/runtime/fd_runtime.h b/src/flamenco/runtime/fd_runtime.h index 0d89225d70f..a9ad38e0482 100644 --- a/src/flamenco/runtime/fd_runtime.h +++ b/src/flamenco/runtime/fd_runtime.h @@ -96,6 +96,7 @@ struct fd_runtime { uchar authorized_voters_mem [ FD_AUTHORIZED_VOTERS_FOOTPRINT ] __attribute__((aligned(FD_AUTHORIZED_VOTERS_ALIGN))); uchar vote_state_landed_votes_mem[ FD_LANDED_VOTES_FOOTPRINT ] __attribute__((aligned(FD_LANDED_VOTES_ALIGN))); uchar tower_sync_landed_votes_mem[ FD_LANDED_VOTES_FOOTPRINT ] __attribute__((aligned(FD_LANDED_VOTES_ALIGN))); + uchar vote_lockout_mem [ FD_VOTE_LOCKOUTS_FOOTPRINT ] __attribute__((aligned(FD_VOTE_LOCKOUTS_ALIGN))); } tower_sync; struct { diff --git a/src/flamenco/runtime/program/fd_vote_program.c b/src/flamenco/runtime/program/fd_vote_program.c index 2ff36f0a807..b16841de8d9 100644 --- a/src/flamenco/runtime/program/fd_vote_program.c +++ b/src/flamenco/runtime/program/fd_vote_program.c @@ -6,30 +6,29 @@ #include "../sysvar/fd_sysvar_rent.h" #include "../sysvar/fd_sysvar.h" #include "../fd_system_ids.h" +#include "vote/fd_authorized_voters.h" +#include "vote/fd_vote_common.h" +#include "vote/fd_vote_lockout.h" +#include "vote/fd_vote_state_versioned.h" +#include "vote/fd_vote_state_v3.h" +#include "vote/fd_vote_state_v4.h" #include #include #include #include -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L35 -#define MAX_LOCKOUT_HISTORY 31UL +/**** +TODO: + * Remove old unused methods + * Update old permalinks */ // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L36 #define INITIAL_LOCKOUT 2UL -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L36 -#define MAX_EPOCH_CREDITS_HISTORY 64UL - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L42 #define DEFAULT_PRIOR_VOTERS_OFFSET 114 -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L45 -#define VOTE_CREDITS_GRACE_SLOTS 2 - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L48 -#define VOTE_CREDITS_MAXIMUM_PER_SLOT 16 - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L51 #define VOTE_CREDITS_MAXIMUM_PER_SLOT_OLD 8 @@ -56,851 +55,201 @@ #define DEFAULT_COMPUTE_UNITS 2100UL /**********************************************************************/ -/* size_of */ -/**********************************************************************/ - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L82 -static inline ulong -size_of_versioned( int is_current ) { - return fd_ulong_if( is_current, FD_VOTE_STATE_V3_SZ, FD_VOTE_STATE_V2_SZ ); -} - -/**********************************************************************/ -/* impl Lockout */ -/**********************************************************************/ - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L104 -static inline ulong -lockout( fd_vote_lockout_t * self ) { - /* Confirmation count can never be greater than MAX_LOCKOUT_HISTORY, preventing overflow. - Although Agave does not consider overflow, we do for fuzzing conformance. */ - ulong confirmation_count = fd_ulong_min( self->confirmation_count, MAX_LOCKOUT_HISTORY ); - return 1UL<slot, lockout( self ) ); -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L114 -static inline ulong -is_locked_out_at_slot( fd_vote_lockout_t * self, ulong slot ) { - return last_locked_out_slot( self ) >= slot; -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L122 -static void -increase_confirmation_count( fd_vote_lockout_t * self, uint by ) { - self->confirmation_count = fd_uint_sat_add( self->confirmation_count, by ); -} - -/**********************************************************************/ -/* impl From for VoteState1_14_11 */ +/* impl VoteStateVersions */ /**********************************************************************/ -/* from_vote_state_1_14_11 converts a "current" vote state object into - the older "v1.14.11" version. This destroys the "current" object in - the process. */ - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L67 -static void -from_vote_state_1_14_11( fd_vote_state_t * vote_state, - fd_vote_state_1_14_11_t * vote_state_1_14_11, /* out */ - uchar * vote_lockout_mem ) { - vote_state_1_14_11->node_pubkey = vote_state->node_pubkey; /* copy */ - vote_state_1_14_11->authorized_withdrawer = vote_state->authorized_withdrawer; /* copy */ - vote_state_1_14_11->commission = vote_state->commission; /* copy */ - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L72 - if( vote_state->votes ) { - vote_state_1_14_11->votes = deq_fd_vote_lockout_t_join( - deq_fd_vote_lockout_t_new( vote_lockout_mem, deq_fd_landed_vote_t_cnt( vote_state->votes ) ) ); - for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( vote_state->votes ); - !deq_fd_landed_vote_t_iter_done( vote_state->votes, iter ); - iter = deq_fd_landed_vote_t_iter_next( vote_state->votes, iter ) ) { - fd_landed_vote_t const * landed_vote = deq_fd_landed_vote_t_iter_ele_const( vote_state->votes, iter ); - deq_fd_vote_lockout_t_push_tail_wrap( vote_state_1_14_11->votes, landed_vote->lockout ); +/* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L176-L187 */ +static inline int +is_uninitialized( fd_vote_state_versioned_t * self ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_v0_23_5: { + fd_pubkey_t pubkey_default = { 0 }; + return !memcmp( &self->inner.v0_23_5.authorized_voter, &pubkey_default, sizeof( fd_pubkey_t ) ); } + case fd_vote_state_versioned_enum_v1_14_11: + return fd_authorized_voters_is_empty( &self->inner.v1_14_11.authorized_voters ); + case fd_vote_state_versioned_enum_current: + return fd_authorized_voters_is_empty( &self->inner.current.authorized_voters ); + case fd_vote_state_versioned_enum_v4: + return 0; // v4 vote states are always initialized + default: + __builtin_unreachable(); } - - vote_state_1_14_11->has_root_slot = vote_state->has_root_slot; /* copy */ - vote_state_1_14_11->root_slot = vote_state->root_slot; /* copy */ - vote_state_1_14_11->authorized_voters = vote_state->authorized_voters; /* move */ - vote_state_1_14_11->prior_voters = vote_state->prior_voters; /* deep copy */ - vote_state_1_14_11->epoch_credits = vote_state->epoch_credits; /* move */ - vote_state_1_14_11->last_timestamp = vote_state->last_timestamp; /* deep copy */ - - /* Clear moved objects */ - vote_state->authorized_voters.treap = NULL; - vote_state->authorized_voters.pool = NULL; - vote_state->epoch_credits = NULL; - } -/**********************************************************************/ -/* impl VoteAccount */ -/**********************************************************************/ - -/* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1074 */ +/* This function is essentially just a call to get_state, additionally + erroring out if the account is a v_0_23_5 account. Initializes + a fd_vote_state_versioned_t struct in the vote_state_mem. + https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L195-L246 */ static int -get_state( fd_txn_account_t const * self, - uchar * res ) { - - fd_bincode_decode_ctx_t decode = { - .data = fd_txn_account_get_data( self ), - .dataend = fd_txn_account_get_data( self ) + fd_txn_account_get_data_len( self ), - }; - - ulong total_sz = 0UL; - int err = fd_vote_state_versioned_decode_footprint( &decode, &total_sz ); - if( FD_UNLIKELY( err ) ) { +vsv_deserialize( fd_borrowed_account_t const * vote_account, + uchar * vote_state_mem ) { + /* To keep error codes conformant, we need to read the discriminant + from the account data first because if the discriminant matches + 0_23_5, an uninitialized account error is thrown. + + TODO: I have submitted a patch in Solana-SDK to coalesce the error + codes to simplify this handling. Remove this once merged. */ + uchar const * data = fd_borrowed_account_get_data( vote_account ); + ulong data_len = fd_borrowed_account_get_data_len( vote_account ); + if( FD_UNLIKELY( data_len dlen ) ) - return FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/src/transaction_context.rs#L983 - fd_bincode_encode_ctx_t encode = - { .data = data, - .dataend = data + dlen }; - do { - int err = fd_vote_state_versioned_encode( state, &encode ); - if( FD_UNLIKELY( err ) ) FD_LOG_ERR(( "fd_vote_state_versioned_encode failed (%d)", err )); - } while(0); + int rc = fd_vsv_get_state( vote_account->acct, vote_state_mem ); + if( FD_UNLIKELY( rc ) ) return rc; return FD_EXECUTOR_INSTR_SUCCESS; } /**********************************************************************/ -/* impl AuthorizedVoters */ +/* VoteStateHandler */ /**********************************************************************/ -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L17 -static fd_vote_authorized_voters_t * -authorized_voters_new( ulong epoch, - fd_pubkey_t const * pubkey, - uchar * mem ) { - - FD_SCRATCH_ALLOC_INIT( l, mem ); - fd_vote_authorized_voters_t * authorized_voters = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_align(), sizeof(fd_vote_authorized_voters_t) ); - void * pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_pool_align(), fd_vote_authorized_voters_pool_footprint( FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); - void * treap_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_treap_align(), fd_vote_authorized_voters_treap_footprint( FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); - - authorized_voters->pool = fd_vote_authorized_voters_pool_join( fd_vote_authorized_voters_pool_new( pool_mem, FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); - authorized_voters->treap = fd_vote_authorized_voters_treap_join( fd_vote_authorized_voters_treap_new( treap_mem, FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); - if( 0 == fd_vote_authorized_voters_pool_free( authorized_voters->pool ) ) { - FD_LOG_ERR(( "Authorized_voter pool is empty" )); - } - fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_pool_ele_acquire( authorized_voters->pool ); - ele->epoch = epoch; - ele->pubkey = *pubkey; - ele->prio = (ulong)&ele->pubkey; - fd_vote_authorized_voters_treap_ele_insert( authorized_voters->treap, ele, authorized_voters->pool ); - return authorized_voters; -} - -// Helper to create an empty AuthorizedVoters structure (for default/uninitialized states) -static fd_vote_authorized_voters_t * -authorized_voters_new_empty( uchar * mem ) { - FD_SCRATCH_ALLOC_INIT( l, mem ); - fd_vote_authorized_voters_t * authorized_voters = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_align(), sizeof(fd_vote_authorized_voters_t) ); - void * pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_pool_align(), fd_vote_authorized_voters_pool_footprint( FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); - void * treap_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_treap_align(), fd_vote_authorized_voters_treap_footprint( FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); - - authorized_voters->pool = fd_vote_authorized_voters_pool_join( fd_vote_authorized_voters_pool_new( pool_mem, FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); - authorized_voters->treap = fd_vote_authorized_voters_treap_join( fd_vote_authorized_voters_treap_new( treap_mem, FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); - return authorized_voters; -} - -static inline int -authorized_voters_is_empty( fd_vote_authorized_voters_t * self ) { - return fd_vote_authorized_voters_treap_ele_cnt( self->treap ) == 0; -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L80 -static inline int -authorized_voters_contains( fd_vote_authorized_voters_t * self, ulong epoch ) { - return !!fd_vote_authorized_voters_treap_ele_query( self->treap, epoch, self->pool ); -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L72 -static inline fd_vote_authorized_voter_t * -authorized_voters_last( fd_vote_authorized_voters_t * self ) { - fd_vote_authorized_voters_treap_rev_iter_t iter = - fd_vote_authorized_voters_treap_rev_iter_init( self->treap, self->pool ); - return fd_vote_authorized_voters_treap_rev_iter_ele( iter, self->pool ); -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L43 -static void -authorized_voters_purge_authorized_voters( fd_vote_authorized_voters_t * self, - ulong current_epoch ) { - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L46 - ulong expired_keys[ FD_VOTE_AUTHORIZED_VOTERS_MIN ]; - ulong key_cnt = 0; - for( fd_vote_authorized_voters_treap_fwd_iter_t iter = - fd_vote_authorized_voters_treap_fwd_iter_init( self->treap, self->pool ); - !fd_vote_authorized_voters_treap_fwd_iter_done( iter ); - iter = fd_vote_authorized_voters_treap_fwd_iter_next( iter, self->pool ) ) { - fd_vote_authorized_voter_t * ele = - fd_vote_authorized_voters_treap_fwd_iter_ele( iter, self->pool ); - if( ele->epoch < current_epoch ) expired_keys[key_cnt++] = ele->epoch; - } - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L52 - for( ulong i = 0; i < key_cnt; i++ ) { - fd_vote_authorized_voter_t * ele = - fd_vote_authorized_voters_treap_ele_query( self->treap, expired_keys[i], self->pool ); - fd_vote_authorized_voters_treap_ele_remove( self->treap, ele, self->pool ); - fd_vote_authorized_voters_pool_ele_release( self->pool, ele ); - } - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L60 - FD_TEST( !authorized_voters_is_empty( self ) ); - -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L91 -static fd_vote_authorized_voter_t * -authorized_voters_get_or_calculate_authorized_voter_for_epoch( fd_vote_authorized_voters_t * self, - ulong epoch, - int * existed ) { - *existed = 0; - ulong latest_epoch = 0; - fd_vote_authorized_voter_t * res = - fd_vote_authorized_voters_treap_ele_query( self->treap, epoch, self->pool ); - // "predecessor" would be more big-O optimal here, but mirroring labs logic - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L93 - if( FD_UNLIKELY( !res ) ) { - for( fd_vote_authorized_voters_treap_fwd_iter_t iter = - fd_vote_authorized_voters_treap_fwd_iter_init( self->treap, self->pool ); - !fd_vote_authorized_voters_treap_fwd_iter_done( iter ); - iter = fd_vote_authorized_voters_treap_fwd_iter_next( iter, self->pool ) ) { - fd_vote_authorized_voter_t * ele = - fd_vote_authorized_voters_treap_fwd_iter_ele( iter, self->pool ); - if( ele->epoch < epoch && ( latest_epoch == 0 || ele->epoch > latest_epoch ) ) { - latest_epoch = ele->epoch; - res = ele; +/* This is a temporary method in Agave (until the vote state v4 feature + is cleaned up) to check the vote state and, in some cases, check + if the vote account is uninitialized or not. Initializes a v3 or v4 + vote account depending on the target version. + https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L45-L77 */ +static int +get_vote_state_handler_checked( fd_borrowed_account_t const * vote_account, + int target_version, + uchar check_initialized, + uchar * vote_state_mem, + uchar * authorized_voters_mem, + uchar * landed_votes_mem ) { + int rc; + switch( target_version ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L50-L62 */ + case VOTE_STATE_TARGET_VERSION_V3: { + rc = fd_vote_state_v3_deserialize( vote_account, vote_state_mem, authorized_voters_mem, landed_votes_mem ); + if( FD_UNLIKELY( rc ) ) return rc; + + fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)vote_state_mem; + if( FD_UNLIKELY( check_initialized && is_uninitialized( versioned ) ) ) { + return FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT; } - } - *existed = 0; - return res; - } else { - *existed = 1; - return res; - } - return res; -} -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L28 -static fd_vote_authorized_voter_t * -authorized_voters_get_and_cache_authorized_voter_for_epoch( fd_vote_authorized_voters_t * self, - ulong epoch ) { - int existed = 0; - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L29 - fd_vote_authorized_voter_t * res = - authorized_voters_get_or_calculate_authorized_voter_for_epoch( self, epoch, &existed ); - if( !res ) return NULL; - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L32 - if( !existed ) { - /* insert cannot fail because !existed */ - if( 0 == fd_vote_authorized_voters_pool_free( self->pool) ) { - FD_LOG_ERR(( "Authorized_voter pool is empty" )); + return FD_EXECUTOR_INSTR_SUCCESS; } - fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_pool_ele_acquire( self->pool ); - ele->epoch = epoch; - ele->pubkey = res->pubkey; - ele->prio = (ulong)&res->pubkey; - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L33 - fd_vote_authorized_voters_treap_ele_insert( self->treap, ele, self->pool ); - } - return res; -} - -/**********************************************************************/ -/* impl VoteStateVersions */ -/**********************************************************************/ - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L66 -static fd_landed_vote_t * -landed_votes_from_lockouts( fd_vote_lockout_t * lockouts, - uchar * mem ) { - if( !lockouts ) return NULL; - - /* Allocate MAX_LOCKOUT_HISTORY (sane case) by default. In case the - vote account is corrupt, allocate as many entries are needed. */ - - ulong cnt = deq_fd_vote_lockout_t_cnt( lockouts ); - cnt = fd_ulong_max( cnt, MAX_LOCKOUT_HISTORY ); - - fd_landed_vote_t * landed_votes = deq_fd_landed_vote_t_join( deq_fd_landed_vote_t_new( mem, cnt ) ); - if( FD_UNLIKELY( !landed_votes ) ) { - FD_LOG_CRIT(( "failed to join landed votes" )); - } - - for( deq_fd_vote_lockout_t_iter_t iter = deq_fd_vote_lockout_t_iter_init( lockouts ); - !deq_fd_vote_lockout_t_iter_done( lockouts, iter ); - iter = deq_fd_vote_lockout_t_iter_next( lockouts, iter ) ) { - fd_vote_lockout_t const * ele = deq_fd_vote_lockout_t_iter_ele_const( lockouts, iter ); - - fd_landed_vote_t * elem = deq_fd_landed_vote_t_push_tail_nocopy( landed_votes ); - fd_landed_vote_new( elem ); - - elem->latency = 0; - elem->lockout.slot = ele->slot; - elem->lockout.confirmation_count = ele->confirmation_count; - } - - return landed_votes; -} - - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L70 -static inline int -is_uninitialized( fd_vote_state_versioned_t * self ) { - switch( self->discriminant ) { - case fd_vote_state_versioned_enum_v0_23_5:; - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L73 - fd_pubkey_t pubkey_default = { 0 }; - return 0 == - memcmp( &self->inner.v0_23_5.authorized_voter, &pubkey_default, sizeof( fd_pubkey_t ) ); - case fd_vote_state_versioned_enum_v1_14_11:; - return authorized_voters_is_empty( &self->inner.v1_14_11.authorized_voters ); - case fd_vote_state_versioned_enum_current: - return authorized_voters_is_empty( &self->inner.current.authorized_voters ); - default: - FD_LOG_ERR(( "missing handler or invalid vote state version: %u", self->discriminant )); - } -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L73 -static void -convert_to_current( fd_vote_state_versioned_t * self, - uchar * authorized_voters_mem, - uchar * landed_votes_mem ) { - switch( self->discriminant ) { - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L19 - case fd_vote_state_versioned_enum_v0_23_5: { - fd_vote_state_0_23_5_t * state = &self->inner.v0_23_5; - // Check if uninitialized (authorized_voter is all zeros) - int is_uninitialized = 1; - for( ulong i = 0; i < sizeof(fd_pubkey_t); i++ ) { - if( state->authorized_voter.uc[i] != 0 ) { - is_uninitialized = 0; - break; + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L63-L75 */ + case VOTE_STATE_TARGET_VERSION_V4: { + rc = vsv_deserialize( vote_account, vote_state_mem ); + if( FD_UNLIKELY( rc ) ) return rc; + + fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)vote_state_mem; + if( FD_UNLIKELY( is_uninitialized( versioned ) ) ) { + return FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT; } - } - fd_vote_authorized_voters_t * authorized_voters; - if( is_uninitialized ) { - // Create empty AuthorizedVoters (default), initialized but with no entries - authorized_voters = authorized_voters_new_empty( authorized_voters_mem ); - } else { - authorized_voters = authorized_voters_new( - state->authorized_voter_epoch, &state->authorized_voter, authorized_voters_mem ); - } - - /* Temporary to hold current */ - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L23 - fd_vote_state_t current = { - .node_pubkey = state->node_pubkey, /* copy */ - .authorized_withdrawer = state->authorized_withdrawer, /* copy */ - .commission = state->commission, /* copy */ - .votes = landed_votes_from_lockouts( state->votes, landed_votes_mem ), - .has_root_slot = state->has_root_slot, /* copy */ - .root_slot = state->root_slot, /* copy */ - .authorized_voters = *authorized_voters, - .prior_voters = (fd_vote_prior_voters_t) { - .idx = 31UL, - .is_empty = 1, - }, - .epoch_credits = state->epoch_credits, /* move */ - .last_timestamp = state->last_timestamp, /* deep copy */ - }; - - /* Move objects */ - state->epoch_credits = NULL; - - /* Emplace new vote state into target */ - self->discriminant = fd_vote_state_versioned_enum_current; - self->inner.current = current; + rc = fd_vsv_try_convert_to_v4( versioned, vote_account->acct->pubkey, landed_votes_mem ); + if( FD_UNLIKELY( rc ) ) return rc; - break; - } - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_versions.rs#L44 - case fd_vote_state_versioned_enum_v1_14_11: { - fd_vote_state_1_14_11_t * state = &self->inner.v1_14_11; - - /* Temporary to hold current */ - fd_vote_state_t current = { - .node_pubkey = state->node_pubkey, /* copy */ - .authorized_withdrawer = state->authorized_withdrawer, /* copy */ - .commission = state->commission, /* copy */ - .votes = landed_votes_from_lockouts( state->votes, landed_votes_mem ), - .has_root_slot = state->has_root_slot, /* copy */ - .root_slot = state->root_slot, /* copy */ - .authorized_voters = state->authorized_voters, /* move */ - .prior_voters = state->prior_voters, /* deep copy */ - .epoch_credits = state->epoch_credits, /* move */ - .last_timestamp = state->last_timestamp /* deep copy */ - }; - - /* Move objects */ - state->authorized_voters.treap = NULL; - state->authorized_voters.pool = NULL; - state->epoch_credits = NULL; - - /* Emplace new vote state into target */ - self->discriminant = fd_vote_state_versioned_enum_current; - self->inner.current = current; - - break; - } - case fd_vote_state_versioned_enum_current: - break; - default: - FD_LOG_ERR(( "unsupported vote state version: %u", self->discriminant )); - } -} - -/**********************************************************************/ -/* impl VoteState */ -/**********************************************************************/ - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L428 -static void -vote_state_new( fd_vote_init_t * vote_init, - fd_sol_sysvar_clock_t const * clock, - uchar * authorized_voters_mem, - fd_vote_state_t * vote_state /* out */ ) { - vote_state->node_pubkey = vote_init->node_pubkey; - vote_state->authorized_voters = *authorized_voters_new( clock->epoch, &vote_init->authorized_voter, authorized_voters_mem ); - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L431 - vote_state->authorized_withdrawer = vote_init->authorized_withdrawer; - vote_state->commission = vote_init->commission; - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L434 - vote_state->prior_voters.idx = 31; - vote_state->prior_voters.is_empty = 1; -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L985 -static inline int -verify_authorized_signer( fd_pubkey_t const * authorized, - fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ) { - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L989 - return fd_signers_contains( signers, authorized ) ? - FD_EXECUTOR_INSTR_SUCCESS : - FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; -} - -// lambda function: https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L873 -static inline int -verify( fd_pubkey_t * epoch_authorized_voter, - int authorized_withdrawer_signer, - fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ) { - if( authorized_withdrawer_signer ) - return 0; - else - return verify_authorized_signer( epoch_authorized_voter, signers ); -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L845 -static void -pop_expired_votes( fd_vote_state_t * self, ulong next_vote_slot ) { - while( !deq_fd_landed_vote_t_empty( self->votes ) ) { - fd_landed_vote_t * vote = deq_fd_landed_vote_t_peek_tail( self->votes ); - if( !( is_locked_out_at_slot( &vote->lockout, next_vote_slot ) ) ) { - deq_fd_landed_vote_t_pop_tail( self->votes ); - } else { - break; + return FD_EXECUTOR_INSTR_SUCCESS; } + default: + __builtin_unreachable(); } } -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L855 -static void -double_lockouts( fd_vote_state_t * self ) { - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L856 - ulong stack_depth = deq_fd_landed_vote_t_cnt( self->votes ); - ulong i = 0; - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L857 - for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( self->votes ); - !deq_fd_landed_vote_t_iter_done( self->votes, iter ); - iter = deq_fd_landed_vote_t_iter_next( self->votes, iter ) ) { - fd_landed_vote_t * v = deq_fd_landed_vote_t_iter_ele( self->votes, iter ); - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L860 - if( stack_depth > - fd_ulong_checked_add_expect( - i, - (ulong)v->lockout.confirmation_count, - "`confirmation_count` and tower_size should be bounded by `MAX_LOCKOUT_HISTORY`" ) ) - { - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L864 - increase_confirmation_count( &v->lockout, 1 ); - } - i++; - } -} -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L668 -static inline uchar -compute_vote_latency( ulong voted_for_slot, ulong current_slot ) { - return (uchar)fd_ulong_min( fd_ulong_sat_sub( current_slot, voted_for_slot ), UCHAR_MAX ); -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L673 -static ulong -credits_for_vote_at_index( fd_vote_state_t * self, ulong index ) { - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L679 - fd_landed_vote_t * landed_vote = deq_fd_landed_vote_t_peek_index( self->votes, index ); - ulong latency = landed_vote == NULL ? 0 : landed_vote->latency; - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L683 - ulong max_credits = VOTE_CREDITS_MAXIMUM_PER_SLOT; - - // If latency is 0, this means that the Lockout was created and stored from a software version - // that did not store vote latencies; in this case, 1 credit is awarded - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L691 - if( FD_UNLIKELY( latency == 0 ) ) { - return 1; - } - - ulong diff = 0; - int cf = fd_ulong_checked_sub( latency, VOTE_CREDITS_GRACE_SLOTS, &diff ); - if( cf != 0 || diff == 0 ) { - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L697 - return max_credits; - } - - ulong credits = 0; - cf = fd_ulong_checked_sub( max_credits, diff, &credits ); - if( cf != 0 || credits == 0 ) { - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L705 - return 1; - } - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L707 - return credits; -} - -/* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L282-L309 */ -static void -increment_credits( fd_vote_state_t * self, ulong epoch, ulong credits ) { - /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L286-L305 */ - if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_empty( self->epoch_credits ) ) ) { - /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L286-L288 */ - deq_fd_vote_epoch_credits_t_push_tail_wrap( - self->epoch_credits, - ( fd_vote_epoch_credits_t ){ .epoch = epoch, .credits = 0, .prev_credits = 0 } ); - } else if( FD_LIKELY( epoch != - deq_fd_vote_epoch_credits_t_peek_tail( self->epoch_credits )->epoch ) ) { - /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L290 */ - fd_vote_epoch_credits_t * last = deq_fd_vote_epoch_credits_t_peek_tail( self->epoch_credits ); - - ulong credits = last->credits; - ulong prev_credits = last->prev_credits; - - /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L292-L299 */ - if( FD_LIKELY( credits!=prev_credits ) ) { - if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_cnt( self->epoch_credits )>=MAX_EPOCH_CREDITS_HISTORY ) ) { - /* Although Agave performs a `.remove(0)` AFTER the call to - `.push()`, there is an edge case where the epoch credits is - full, making the call to `_push_tail()` unsafe. Since Agave's - structures are dynamically allocated, it is safe for them to - simply call `.push()` and then popping afterwards. We have to - reverse the order of operations to maintain correct behavior - and avoid overflowing the deque. - https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L303 */ - deq_fd_vote_epoch_credits_t_pop_head( self->epoch_credits ); - } - - /* This will not fail because we already popped if we're at - capacity, since the epoch_credits deque is allocated with a - minimum capacity of MAX_EPOCH_CREDITS_HISTORY. */ - deq_fd_vote_epoch_credits_t_push_tail( - self->epoch_credits, - ( fd_vote_epoch_credits_t ){ - .epoch = epoch, .credits = credits, .prev_credits = credits } ); - } else { - /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L297-L298 */ - deq_fd_vote_epoch_credits_t_peek_tail( self->epoch_credits )->epoch = epoch; - - /* Here we can perform the same deque size check and pop if - we're beyond the maximum epoch credits len. */ - if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_cnt( self->epoch_credits )>MAX_EPOCH_CREDITS_HISTORY ) ) { - deq_fd_vote_epoch_credits_t_pop_head( self->epoch_credits ); - } - } +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L888-L902 */ +static int +check_vote_account_length( fd_borrowed_account_t const * vote_account, + int target_version ) { + ulong length = fd_borrowed_account_get_data_len( vote_account ); + ulong expected; + switch( target_version ) { + case VOTE_STATE_TARGET_VERSION_V3: + expected = FD_VOTE_STATE_V3_SZ; + break; + case VOTE_STATE_TARGET_VERSION_V4: + expected = FD_VOTE_STATE_V4_SZ; + break; + default: + __builtin_unreachable(); } - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L663 - deq_fd_vote_epoch_credits_t_peek_tail( self->epoch_credits )->credits = fd_ulong_sat_add( - deq_fd_vote_epoch_credits_t_peek_tail( self->epoch_credits )->credits, credits ); -} - -static inline ulong * -last_voted_slot( fd_vote_state_t * self ); - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L595 -static void -process_next_vote_slot( fd_vote_state_t * self, - ulong next_vote_slot, - ulong epoch, - ulong current_slot ) { - ulong * last_voted_slot_ = last_voted_slot( self ); - if( FD_UNLIKELY( last_voted_slot_ && next_vote_slot <= *last_voted_slot_ ) ) return; - - pop_expired_votes( self, next_vote_slot ); - - fd_landed_vote_t landed_vote = { .latency = compute_vote_latency( next_vote_slot, current_slot ), - ( fd_vote_lockout_t ){ .slot = next_vote_slot } }; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L623 - if( FD_UNLIKELY( deq_fd_landed_vote_t_cnt( self->votes ) == MAX_LOCKOUT_HISTORY ) ) { - ulong credits = credits_for_vote_at_index( self, 0 ); - fd_landed_vote_t landed_vote = deq_fd_landed_vote_t_pop_head( self->votes ); - self->has_root_slot = 1; - self->root_slot = landed_vote.lockout.slot; - - increment_credits( self, epoch, credits ); + if( FD_UNLIKELY( length!=expected ) ) { + return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; } - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L634 - deq_fd_landed_vote_t_push_tail_wrap( self->votes, landed_vote ); - double_lockouts( self ); -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L828 -static int -get_and_update_authorized_voter( fd_vote_state_t * self, - ulong current_epoch, - fd_pubkey_t ** pubkey /* out */ ) { - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L832 - fd_vote_authorized_voter_t * authorized_voter = - authorized_voters_get_and_cache_authorized_voter_for_epoch( &self->authorized_voters, - current_epoch ); - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L835 - if( FD_UNLIKELY( !authorized_voter ) ) return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; - *pubkey = &authorized_voter->pubkey; - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L837 - authorized_voters_purge_authorized_voters( &self->authorized_voters, current_epoch ); return FD_EXECUTOR_INSTR_SUCCESS; } -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L768 +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L855-L870 */ static int -set_new_authorized_voter( fd_vote_state_t * self, - fd_pubkey_t const * authorized_pubkey, - ulong current_epoch, - ulong target_epoch, - /* "verify" closure */ int authorized_withdrawer_signer, - /* "verify" closure */ fd_pubkey_t const * signers[static FD_TXN_SIG_MAX], - fd_exec_instr_ctx_t const * ctx ) { - int rc; - fd_pubkey_t * epoch_authorized_voter = NULL; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L778 - rc = get_and_update_authorized_voter( self, current_epoch, &epoch_authorized_voter ); - if( FD_UNLIKELY( rc ) ) return rc; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L779 - rc = verify( epoch_authorized_voter, authorized_withdrawer_signer, signers ); - if( FD_UNLIKELY( rc ) ) return rc; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L786 - if( FD_UNLIKELY( authorized_voters_contains( &self->authorized_voters, target_epoch ) ) ) { - ctx->txn_out->err.custom_err = FD_VOTE_ERR_TOO_SOON_TO_REAUTHORIZE; - return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; - } - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L791 - fd_vote_authorized_voter_t * latest_authorized = - authorized_voters_last( &self->authorized_voters ); - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L794 - if( FD_UNLIKELY( ( !latest_authorized ) ) ) return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; - ulong latest_epoch = latest_authorized->epoch; - fd_pubkey_t * latest_authorized_pubkey = &latest_authorized->pubkey; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L799 - if( 0 != memcmp( latest_authorized_pubkey, authorized_pubkey, sizeof( fd_pubkey_t ) ) ) { - fd_vote_prior_voters_t * prior_voters = &self->prior_voters; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L801 - ulong epoch_of_last_authorized_switch = 0UL; - if( (!prior_voters->is_empty) & (prior_voters->idx < 32) ) { - epoch_of_last_authorized_switch = prior_voters->buf[prior_voters->idx].epoch_end; - } - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L810 - if( target_epoch <= latest_epoch ) - return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L815 - prior_voters->idx += 1UL; - prior_voters->idx %= 32UL; - prior_voters->buf[prior_voters->idx] = - ( fd_vote_prior_voter_t ){ .pubkey = *latest_authorized_pubkey, - .epoch_start = epoch_of_last_authorized_switch, - .epoch_end = target_epoch }; - prior_voters->is_empty = 0; - } - - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L822 - if( 0 == fd_vote_authorized_voters_pool_free( self->authorized_voters.pool) ) { - FD_LOG_ERR(( "Authorized_voter pool is empty" )); - } - - fd_vote_authorized_voter_t * ele = - fd_vote_authorized_voters_pool_ele_acquire( self->authorized_voters.pool ); - ele->epoch = target_epoch; - ele->pubkey = *authorized_pubkey; - ele->prio = (ulong)&ele->pubkey; - fd_vote_authorized_voters_treap_ele_insert( - self->authorized_voters.treap, ele, self->authorized_voters.pool ); - - return 0; -} +init_vote_account_state( fd_exec_instr_ctx_t * ctx, + fd_borrowed_account_t * vote_account, + fd_vote_state_versioned_t * versioned, + int target_version, + fd_vote_init_t * vote_init, + fd_sol_sysvar_clock_t const * clock ) { + /* + * N.B. Technically we should destroy() to release memory before + * newing, otherwise the pointers are wiped and memory is leaked. + * We are probably fine for now since we are bump allocating + * everything and the enclosing frame will free everything when + * popped. + */ + /* Reset the object */ + fd_vote_state_versioned_new( versioned ); -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L869 -static int -process_timestamp( fd_vote_state_t * self, - ulong slot, - long timestamp, - fd_exec_instr_ctx_t const * ctx ) { - if( FD_UNLIKELY( - ( slot < self->last_timestamp.slot || timestamp < self->last_timestamp.timestamp ) || - ( slot == self->last_timestamp.slot && - ( slot != self->last_timestamp.slot || timestamp != self->last_timestamp.timestamp ) && - self->last_timestamp.slot != 0 ) ) ) { - ctx->txn_out->err.custom_err = FD_VOTE_ERR_TIMESTAMP_TOO_OLD; - return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; + switch( target_version ) { + case VOTE_STATE_TARGET_VERSION_V3: + fd_vote_program_v3_create_new( + vote_init, + clock, + ctx->runtime->vote_program.init_account.authorized_voters_mem, + versioned + ); + return fd_vote_state_v3_set_vote_account_state( + ctx, + vote_account, + versioned, + ctx->runtime->vote_program.init_account.vote_lockout_mem + ); + case VOTE_STATE_TARGET_VERSION_V4: + fd_vote_state_v4_create_new( + vote_account->acct->pubkey, + vote_init, + clock, + ctx->runtime->vote_program.init_account.authorized_voters_mem, + versioned + ); + return fd_vote_state_v4_set_vote_account_state( ctx, vote_account, versioned ); + default: + __builtin_unreachable(); } - self->last_timestamp.slot = slot; - self->last_timestamp.timestamp = timestamp; - - return 0; } /**********************************************************************/ /* mod vote_state */ /**********************************************************************/ -// https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L166 -__attribute__((warn_unused_result)) static int -set_vote_account_state( fd_borrowed_account_t * vote_account, - fd_vote_state_t * vote_state, - fd_exec_instr_ctx_t const * ctx /* feature_set */, - uchar * vote_lockout_mem ) { - /* This is a horrible conditional expression in Agave. - The terms were broken up into their own variables. */ - - ulong vsz = size_of_versioned( 1 ); - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L175 - fd_rent_t const rent = fd_sysvar_cache_rent_read_nofail( ctx->sysvar_cache ); - int resize_needed = fd_borrowed_account_get_data_len( vote_account ) < vsz; - int resize_rent_exempt = fd_rent_exempt_minimum_balance( &rent, vsz ) <= fd_borrowed_account_get_lamports( vote_account ); - - /* The resize operation itself is part of the horrible conditional, - but behind a short-circuit operator. */ - int resize_failed = 0; - if( resize_needed && resize_rent_exempt ) { - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L179 - resize_failed = - fd_borrowed_account_set_data_length( vote_account, vsz ) != FD_EXECUTOR_INSTR_SUCCESS; - } - - if( FD_UNLIKELY( resize_needed && ( !resize_rent_exempt || resize_failed ) ) ) { - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L184 - fd_vote_state_versioned_t v1_14_11; - fd_vote_state_versioned_new_disc( &v1_14_11, fd_vote_state_versioned_enum_v1_14_11 ); - from_vote_state_1_14_11( vote_state, &v1_14_11.inner.v1_14_11, vote_lockout_mem ); - return set_state( vote_account, &v1_14_11 ); - } - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L189 - // TODO: This is stupid... optimize this... - fd_vote_state_versioned_t new_current = { .discriminant = fd_vote_state_versioned_enum_current, - .inner = { .current = *vote_state } }; - return set_state( vote_account, &new_current ); -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L727 -static inline fd_vote_lockout_t * -last_lockout( fd_vote_state_t * self ) { - if( deq_fd_landed_vote_t_empty( self->votes ) ) return NULL; - fd_landed_vote_t * last_vote = deq_fd_landed_vote_t_peek_tail( self->votes ); - return &last_vote->lockout; -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L731 -static inline ulong * -last_voted_slot( fd_vote_state_t * self ) { - fd_vote_lockout_t * last_lockout_ = last_lockout( self ); - if( FD_UNLIKELY( !last_lockout_ ) ) return NULL; - return &last_lockout_->slot; -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L573 -static uchar -contains_slot( fd_vote_state_t * vote_state, ulong slot ) { - /* Logic is copied from slice::binary_search_by() in Rust. While not fully optimized, - it aims to achieve fuzzing conformance for both sorted and unsorted inputs. */ - ulong size = deq_fd_landed_vote_t_cnt( vote_state->votes ); - if( FD_UNLIKELY( size==0UL ) ) return 0; - - ulong base = 0UL; - while( size>1UL ) { - ulong half = size / 2UL; - ulong mid = base + half; - ulong mid_slot = deq_fd_landed_vote_t_peek_index_const( vote_state->votes, mid )->lockout.slot; - base = (slotvotes, base )->lockout.slot==slot; -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L201 +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L82-L324 */ static int -check_and_filter_proposed_vote_state( fd_vote_state_t * vote_state, +check_and_filter_proposed_vote_state( fd_exec_instr_ctx_t * ctx, + fd_vote_state_versioned_t * versioned, fd_vote_lockout_t * proposed_lockouts, uchar * proposed_has_root, ulong * proposed_root, fd_hash_t const * proposed_hash, - fd_slot_hash_t const * slot_hashes, /* deque */ - fd_exec_instr_ctx_t const * ctx ) { + fd_slot_hash_t const * slot_hashes /* deque */ ) { + fd_landed_vote_t const * votes = fd_vsv_get_votes( versioned ); + ulong const * root_slot = fd_vsv_get_root_slot( versioned ); + uchar has_root_slot = !!(root_slot!=NULL); + // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L208 if( FD_UNLIKELY( deq_fd_vote_lockout_t_empty( proposed_lockouts ) ) ) { ctx->txn_out->err.custom_err = FD_VOTE_ERR_EMPTY_SLOTS; return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; } fd_landed_vote_t const * last_vote = NULL; - if( !deq_fd_landed_vote_t_empty( vote_state->votes ) ) { + if( !deq_fd_landed_vote_t_empty( votes ) ) { // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L212 - last_vote = deq_fd_landed_vote_t_peek_tail( vote_state->votes ); + last_vote = deq_fd_landed_vote_t_peek_tail_const( votes ); } // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L218 if( FD_LIKELY( last_vote ) ) { @@ -939,22 +288,22 @@ check_and_filter_proposed_vote_state( fd_vote_state_t * vote_state, the root to the latest vote in the current vote that's less than R. */ // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L242 if( proposed_root_ < earliest_slot_hash_in_history ) { - *proposed_has_root = vote_state->has_root_slot; - *proposed_root = vote_state->root_slot; - for( deq_fd_landed_vote_t_iter_t iter = - deq_fd_landed_vote_t_iter_init_rev( vote_state->votes ); - !deq_fd_landed_vote_t_iter_done_rev( vote_state->votes, iter ); - iter = deq_fd_landed_vote_t_iter_prev( vote_state->votes, iter ) ) { + *proposed_has_root = has_root_slot; + if( has_root_slot ) { + *proposed_root = *root_slot; + } + for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init_rev( votes ); + !deq_fd_landed_vote_t_iter_done_rev( votes, iter ); + iter = deq_fd_landed_vote_t_iter_prev( votes, iter ) ) { /* Ensure we're iterating from biggest to smallest vote in the current vote state */ - fd_landed_vote_t const * vote = deq_fd_landed_vote_t_iter_ele_const( vote_state->votes, iter ); + fd_landed_vote_t const * vote = deq_fd_landed_vote_t_iter_ele_const( votes, iter ); // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L248 if( vote->lockout.slot <= proposed_root_ ) { *proposed_has_root = 1; *proposed_root = vote->lockout.slot; break; } - } } } @@ -1031,7 +380,7 @@ check_and_filter_proposed_vote_state( fd_vote_state_t * vote_state, return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; } // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L310 - if( !contains_slot( vote_state, proposed_vote_slot ) && !has_root_to_check ) { + if( !fd_vote_contains_slot( votes, proposed_vote_slot ) && !has_root_to_check ) { /* If the vote slot is both: 1) Too old 2) Doesn't already exist in vote state @@ -1151,18 +500,18 @@ check_and_filter_proposed_vote_state( fd_vote_state_t * vote_state, // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L440 static int -check_slots_are_valid( fd_vote_state_t * vote_state, - ulong const * vote_slots, - fd_hash_t const * vote_hash, - fd_slot_hash_t const * slot_hashes, /* deque */ - fd_exec_instr_ctx_t const * ctx ) { +check_slots_are_valid( fd_exec_instr_ctx_t * ctx, + fd_vote_state_versioned_t * versioned, + ulong const * vote_slots, + fd_hash_t const * vote_hash, + fd_slot_hash_t const * slot_hashes /* deque */ ) { ulong i = 0; ulong j = deq_fd_slot_hash_t_cnt( slot_hashes ); ulong vote_slots_len = deq_ulong_cnt( vote_slots ); // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L462 while( i < vote_slots_len && j > 0 ) { - ulong * last_voted_slot_ = last_voted_slot( vote_state ); + ulong const * last_voted_slot_ = fd_vsv_get_last_voted_slot( versioned ); if( FD_UNLIKELY( last_voted_slot_ && *deq_ulong_peek_index_const( vote_slots, i ) <= *last_voted_slot_ ) ) { // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L469 @@ -1208,16 +557,19 @@ check_slots_are_valid( fd_vote_state_t * vote_state, // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L565 static int -process_new_vote_state( fd_vote_state_t * vote_state, +process_new_vote_state( fd_exec_instr_ctx_t * ctx, + fd_vote_state_versioned_t * versioned, fd_landed_vote_t * new_state, int has_new_root, ulong new_root, int has_timestamp, long timestamp, ulong epoch, - ulong current_slot, - fd_exec_instr_ctx_t const * ctx /* feature_set */ ) { + ulong current_slot ) { int rc; + fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( versioned ); + ulong const * root_slot = fd_vsv_get_root_slot( versioned ); + uchar has_root_slot = !!(root_slot!=NULL); FD_TEST( !deq_fd_landed_vote_t_empty( new_state ) ); // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L575 @@ -1227,13 +579,13 @@ process_new_vote_state( fd_vote_state_t * vote_state, }; // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L579 - if( FD_UNLIKELY( has_new_root && vote_state->has_root_slot ) ) { + if( FD_UNLIKELY( has_new_root && has_root_slot ) ) { // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L581 - if( FD_UNLIKELY( new_root < vote_state->root_slot ) ) { + if( FD_UNLIKELY( new_root<*root_slot ) ) { ctx->txn_out->err.custom_err = FD_VOTE_ERR_ROOT_ROLL_BACK; return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; } - } else if( FD_UNLIKELY( !has_new_root && vote_state->has_root_slot ) ) { + } else if( FD_UNLIKELY( !has_new_root && has_root_slot ) ) { // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L586 ctx->txn_out->err.custom_err = FD_VOTE_ERR_ROOT_ROLL_BACK; return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; @@ -1269,7 +621,7 @@ process_new_vote_state( fd_vote_state_t * vote_state, ctx->txn_out->err.custom_err = FD_VOTE_ERR_CONFIRMATIONS_NOT_ORDERED; return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; } else if( FD_UNLIKELY( vote->lockout.slot > - last_locked_out_slot( &previous_vote->lockout ) ) ) { + fd_vote_lockout_last_locked_out_slot( &previous_vote->lockout ) ) ) { ctx->txn_out->err.custom_err = FD_VOTE_ERR_NEW_VOTE_STATE_LOCKOUT_MISMATCH; return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; } @@ -1291,16 +643,18 @@ process_new_vote_state( fd_vote_state_t * vote_state, ulong earned_credits = 0; if( FD_LIKELY( has_new_root ) ) { - for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( vote_state->votes ); - !deq_fd_landed_vote_t_iter_done( vote_state->votes, iter ); - iter = deq_fd_landed_vote_t_iter_next( vote_state->votes, iter ) ) { - fd_landed_vote_t * current_vote = deq_fd_landed_vote_t_iter_ele( vote_state->votes, iter ); + for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( votes ); + !deq_fd_landed_vote_t_iter_done( votes, iter ); + iter = deq_fd_landed_vote_t_iter_next( votes, iter ) ) { + fd_landed_vote_t const * current_vote = deq_fd_landed_vote_t_iter_ele_const( votes, iter ); // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L647 if( FD_UNLIKELY( current_vote->lockout.slot <= new_root ) ) { // this is safe because we're inside if has_new_root earned_credits = fd_ulong_checked_add_expect( - credits_for_vote_at_index( vote_state, - current_vote_state_index ), + fd_vote_credits_for_vote_at_index( + votes, + current_vote_state_index + ), earned_credits, "`earned_credits` does not overflow" ); current_vote_state_index = fd_ulong_checked_add_expect( @@ -1335,10 +689,10 @@ process_new_vote_state( fd_vote_state_t * vote_state, // must have been expired by later votes. Check that the lockouts match this assumption. // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L686 - while( current_vote_state_index < deq_fd_landed_vote_t_cnt( vote_state->votes ) && + while( current_vote_state_index < deq_fd_landed_vote_t_cnt( votes ) && new_vote_state_index < deq_fd_landed_vote_t_cnt( new_state ) ) { - fd_landed_vote_t * current_vote = - deq_fd_landed_vote_t_peek_index( vote_state->votes, current_vote_state_index ); + fd_landed_vote_t const * current_vote = deq_fd_landed_vote_t_peek_index_const( votes, current_vote_state_index ); + // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L690 fd_landed_vote_t * new_vote = deq_fd_landed_vote_t_peek_index( new_state, new_vote_state_index ); @@ -1374,8 +728,7 @@ process_new_vote_state( fd_vote_state_t * vote_state, } // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L712 - new_vote->latency = - deq_fd_landed_vote_t_peek_index( vote_state->votes, current_vote_state_index )->latency; + new_vote->latency = deq_fd_landed_vote_t_peek_index_const( votes, current_vote_state_index )->latency; // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L714 current_vote_state_index = @@ -1408,144 +761,165 @@ process_new_vote_state( fd_vote_state_t * vote_state, if( FD_UNLIKELY( new_vote->latency == 0 ) ) { // this is unlikely because as validators upgrade, it should converge to the new vote state // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L739 - new_vote->latency = compute_vote_latency( new_vote->lockout.slot, current_slot ); + new_vote->latency = fd_vote_compute_vote_latency( new_vote->lockout.slot, current_slot ); } } // doesn't matter what the value of slot if `is_some = 0` i.e. `Option::None` // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L744 - int both_none = !vote_state->has_root_slot && !has_new_root; - if( ( !both_none && ( vote_state->has_root_slot != has_new_root || - vote_state->root_slot != new_root ) ) ) { - increment_credits( vote_state, epoch, earned_credits ); + int both_none = !has_root_slot && !has_new_root; + if( ( !both_none && ( has_root_slot!=has_new_root || + *root_slot!=new_root ) ) ) { + fd_vsv_increment_credits( versioned, epoch, earned_credits ); } // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L750 if( FD_LIKELY( has_timestamp ) ) { /* new_state asserted nonempty at function beginning */ if( deq_fd_landed_vote_t_empty( new_state ) ) { - FD_LOG_ERR(( "solana panic" )); - // TODO: solana panics ... unclear what to return - ctx->txn_out->err.custom_err = 0; - return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; + FD_LOG_ERR(( "Landed votes is empty" )); } ulong last_slot = deq_fd_landed_vote_t_peek_tail( new_state )->lockout.slot; - rc = process_timestamp( vote_state, last_slot, timestamp, ctx ); + rc = fd_vsv_process_timestamp( ctx, versioned, last_slot, timestamp ); if( FD_UNLIKELY( rc ) ) { return rc; } - vote_state->last_timestamp.timestamp = timestamp; + fd_vsv_process_timestamp( ctx, versioned, last_slot, timestamp ); } // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L754 - vote_state->has_root_slot = (uchar)has_new_root; - vote_state->root_slot = new_root; + fd_vsv_set_root_slot( versioned, has_new_root ? &new_root : NULL ); // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L755 - deq_fd_landed_vote_t_remove_all( vote_state->votes ); + deq_fd_landed_vote_t_remove_all( votes ); for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( new_state ); !deq_fd_landed_vote_t_iter_done( new_state, iter ); iter = deq_fd_landed_vote_t_iter_next( new_state, iter ) ) { fd_landed_vote_t * landed_vote = deq_fd_landed_vote_t_iter_ele( new_state, iter ); - deq_fd_landed_vote_t_push_tail_wrap( vote_state->votes, *landed_vote ); + deq_fd_landed_vote_t_push_tail_wrap( votes, *landed_vote ); } return FD_EXECUTOR_INSTR_SUCCESS; } -// https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L849 +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L716-L759 */ static int -authorize( fd_borrowed_account_t * vote_account, +authorize( fd_exec_instr_ctx_t * ctx, + fd_borrowed_account_t * vote_account, + int target_version, fd_pubkey_t const * authorized, fd_vote_authorize_t vote_authorize, fd_pubkey_t const * signers[static FD_TXN_SIG_MAX], - fd_sol_sysvar_clock_t const * clock, - fd_exec_instr_ctx_t const * ctx /* feature_set */ ) { - int rc = 0; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L857 - - rc = get_state( vote_account->acct, ctx->runtime->vote_program.authorize.vote_state_mem ); + fd_sol_sysvar_clock_t const * clock ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L724-L727 */ + int rc = get_vote_state_handler_checked( + vote_account, + target_version, + 0, + ctx->runtime->vote_program.authorize.vote_state_mem, + ctx->runtime->vote_program.authorize.authorized_voters_mem, + ctx->runtime->vote_program.authorize.landed_votes_mem + ); if( FD_UNLIKELY( rc ) ) return rc; - fd_vote_state_versioned_t * vote_state_versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.authorize.vote_state_mem; - convert_to_current( vote_state_versioned, - ctx->runtime->vote_program.authorize.authorized_voters_mem, - ctx->runtime->vote_program.authorize.landed_votes_mem ); - fd_vote_state_t * vote_state = &vote_state_versioned->inner.current; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L861 + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L729-L756 */ + fd_vote_state_versioned_t * vote_state_versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.authorize.vote_state_mem; switch( vote_authorize.discriminant ) { - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L862 - case fd_vote_authorize_enum_voter:; + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L730-L750 */ + case fd_vote_authorize_enum_voter: { - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L863 - int authorized_withdrawer_signer = - FD_EXECUTOR_INSTR_SUCCESS == - verify_authorized_signer( &vote_state->authorized_withdrawer, signers ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L731-L732 */ + int authorized_withdrawer_signer = !fd_vote_verify_authorized_signer( + fd_vsv_get_authorized_withdrawer( vote_state_versioned ), + signers + ); - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L869-L872 - ulong target_epoch; - rc = fd_ulong_checked_add( clock->leader_schedule_epoch, 1UL, &target_epoch ); - if( FD_UNLIKELY( rc!=FD_EXECUTOR_INSTR_SUCCESS ) ) { - return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; - } - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L866 - rc = set_new_authorized_voter( vote_state, - authorized, - clock->epoch, - target_epoch, - authorized_withdrawer_signer, - signers, - ctx ); - if( FD_UNLIKELY( rc ) ) return rc; - break; + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L737-L740 */ + ulong target_epoch; + rc = fd_ulong_checked_add( clock->leader_schedule_epoch, 1UL, &target_epoch ); + if( FD_UNLIKELY( rc!=FD_EXECUTOR_INSTR_SUCCESS ) ) { + return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; + } - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L883 - case fd_vote_authorize_enum_withdrawer: - rc = verify_authorized_signer( &vote_state->authorized_withdrawer, signers ); - if( FD_UNLIKELY( rc ) ) return rc; - vote_state->authorized_withdrawer = *authorized; - break; + // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L866 + rc = fd_vsv_set_new_authorized_voter( + ctx, + vote_state_versioned, + authorized, + clock->epoch, + target_epoch, + authorized_withdrawer_signer, + signers + ); + if( FD_UNLIKELY( rc ) ) return rc; + break; + } - // failing exhaustive check is fatal - default: - __builtin_unreachable(); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L751-L756 */ + case fd_vote_authorize_enum_withdrawer: { + rc = fd_vote_verify_authorized_signer( + fd_vsv_get_authorized_withdrawer( vote_state_versioned ), + signers + ); + if( FD_UNLIKELY( rc ) ) return rc; + fd_vsv_set_authorized_withdrawer( vote_state_versioned, authorized ); + break; + } + default: + __builtin_unreachable(); } - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L890 - return set_vote_account_state( vote_account, vote_state, ctx, ctx->runtime->vote_program.authorize.vote_lockout_mem ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L758 */ + return fd_vsv_set_vote_account_state( + ctx, + vote_account, + vote_state_versioned, + ctx->runtime->vote_program.authorize.vote_lockout_mem + ); } -// https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L894 +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L761-L785 */ static int -update_validator_identity( fd_borrowed_account_t * vote_account, - fd_pubkey_t const * node_pubkey, - fd_pubkey_t const * signers[static FD_TXN_SIG_MAX], - fd_exec_instr_ctx_t const * ctx /* feature_set */ ) { - int rc = 0; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L900 - rc = get_state( vote_account->acct, ctx->runtime->vote_program.update_validator_identity.vote_state_mem ); +update_validator_identity( fd_exec_instr_ctx_t * ctx, + int target_version, + fd_borrowed_account_t * vote_account, + fd_pubkey_t const * node_pubkey, + fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L768-L771 */ + int rc = get_vote_state_handler_checked( + vote_account, + target_version, + 0, + ctx->runtime->vote_program.update_validator_identity.vote_state_mem, + ctx->runtime->vote_program.update_validator_identity.authorized_voters_mem, + ctx->runtime->vote_program.update_validator_identity.landed_votes_mem + ); if( FD_UNLIKELY( rc ) ) return rc; + fd_vote_state_versioned_t * vote_state_versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.update_validator_identity.vote_state_mem; - convert_to_current( vote_state_versioned, - ctx->runtime->vote_program.update_validator_identity.authorized_voters_mem, - ctx->runtime->vote_program.update_validator_identity.landed_votes_mem ); - fd_vote_state_t * vote_state = &vote_state_versioned->inner.current; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L905 - rc = verify_authorized_signer( &vote_state->authorized_withdrawer, signers ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L774 */ + rc = fd_vote_verify_authorized_signer( + fd_vsv_get_authorized_withdrawer( vote_state_versioned ), + signers + ); if( FD_UNLIKELY( rc ) ) return rc; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L908 - rc = verify_authorized_signer( node_pubkey, signers ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L777 */ + rc = fd_vote_verify_authorized_signer( node_pubkey, signers ); if( FD_UNLIKELY( rc ) ) return rc; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L910 - vote_state->node_pubkey = *node_pubkey; + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L779 */ + fd_vsv_set_node_pubkey( vote_state_versioned, node_pubkey ); - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L912 - return set_vote_account_state( vote_account, vote_state, ctx, ctx->runtime->vote_program.update_validator_identity.vote_lockout_mem ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L782 */ + fd_vsv_set_block_revenue_collector( vote_state_versioned, node_pubkey ); + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L784 */ + return fd_vsv_set_vote_account_state( + ctx, + vote_account, + vote_state_versioned, + ctx->runtime->vote_program.update_validator_identity.vote_lockout_mem + ); } // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L971 @@ -1561,181 +935,181 @@ is_commission_update_allowed( ulong slot, fd_epoch_schedule_t const * epoch_sche } } -// https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L916 +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L787-L818 */ static int -update_commission( fd_borrowed_account_t * vote_account, - uchar commission, - fd_pubkey_t const * signers[static FD_TXN_SIG_MAX], - fd_epoch_schedule_t const * epoch_schedule, - fd_sol_sysvar_clock_t const * clock, - fd_exec_instr_ctx_t const * ctx /* feature_set */ ) { - int rc = 0; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L925 - fd_vote_state_versioned_t * vote_state_versioned = NULL; - fd_vote_state_t * vote_state = NULL; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L927 - int enforce_commission_update_rule = 1; - rc = get_state( vote_account->acct, ctx->runtime->vote_program.update_commission.vote_state_mem ); - if( FD_LIKELY( rc==FD_EXECUTOR_INSTR_SUCCESS ) ) { - vote_state_versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.update_commission.vote_state_mem; - convert_to_current( vote_state_versioned, - ctx->runtime->vote_program.update_commission.authorized_voters_mem, - ctx->runtime->vote_program.update_commission.landed_votes_mem ); - vote_state = &vote_state_versioned->inner.current; - enforce_commission_update_rule = commission > vote_state->commission; - } - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L940 - if( FD_LIKELY( enforce_commission_update_rule ) ) { - if( FD_UNLIKELY( !is_commission_update_allowed( clock->slot, epoch_schedule ) ) ) { - ctx->txn_out->err.custom_err = FD_VOTE_ERR_COMMISSION_UPDATE_TOO_LATE; - return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; - } +update_commission( fd_exec_instr_ctx_t * ctx, + int target_version, + fd_borrowed_account_t * vote_account, + uchar commission, + fd_pubkey_t const * signers[static FD_TXN_SIG_MAX], + fd_epoch_schedule_t const * epoch_schedule, + fd_sol_sysvar_clock_t const * clock ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L796-L799 */ + int rc = 0; + int get_vsv_rc = get_vote_state_handler_checked( + vote_account, + target_version, + false, + ctx->runtime->vote_program.update_commission.vote_state_mem, + ctx->runtime->vote_program.update_commission.authorized_voters_mem, + ctx->runtime->vote_program.update_commission.landed_votes_mem + ); + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L800-L804 */ + fd_vote_state_versioned_t * vote_state_versioned = NULL; + int enforce_commission_update_rule = 1; + if( FD_LIKELY( get_vsv_rc==FD_EXECUTOR_INSTR_SUCCESS ) ) { + vote_state_versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.update_commission.vote_state_mem; + enforce_commission_update_rule = (commission>fd_vsv_get_commission( vote_state_versioned )); + } + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L806-L808 */ + if( FD_UNLIKELY( enforce_commission_update_rule && !is_commission_update_allowed( clock->slot, epoch_schedule ) ) ) { + ctx->txn_out->err.custom_err = FD_VOTE_ERR_COMMISSION_UPDATE_TOO_LATE; + return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; } - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L949 - if( !vote_state ) { - rc = get_state( vote_account->acct, ctx->runtime->vote_program.update_commission.vote_state_mem ); - if( FD_UNLIKELY( rc ) ) return rc; - vote_state_versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.update_commission.vote_state_mem; - convert_to_current( vote_state_versioned, - ctx->runtime->vote_program.update_commission.authorized_voters_mem, - ctx->runtime->vote_program.update_commission.landed_votes_mem ); - vote_state = &vote_state_versioned->inner.current; - } + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L810 */ + if( FD_UNLIKELY( get_vsv_rc ) ) return get_vsv_rc; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L957 - rc = verify_authorized_signer( &vote_state->authorized_withdrawer, signers ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L813 */ + rc = fd_vote_verify_authorized_signer( + fd_vsv_get_authorized_withdrawer( vote_state_versioned ), + signers + ); if( FD_UNLIKELY( rc ) ) return rc; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L959 - vote_state->commission = commission; + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L815 */ + fd_vsv_set_commission( vote_state_versioned, commission ); - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L961 - return set_vote_account_state( vote_account, vote_state, ctx, ctx->runtime->vote_program.update_commission.vote_lockout_mem ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L817 */ + return fd_vsv_set_vote_account_state( + ctx, + vote_account, + vote_state_versioned, + ctx->runtime->vote_program.update_commission.vote_lockout_mem + ); } -// https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L997 +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L848C8-L903 */ static int -withdraw( fd_exec_instr_ctx_t const * ctx, +withdraw( fd_exec_instr_ctx_t * ctx, fd_borrowed_account_t * vote_account, + int target_version, ulong lamports, ushort to_account_index, fd_pubkey_t const * signers[static FD_TXN_SIG_MAX], fd_rent_t const * rent_sysvar, fd_sol_sysvar_clock_t const * clock ) { - int rc = 0; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1010 - rc = get_state( vote_account->acct, ctx->runtime->vote_program.withdraw.vote_state_mem ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L860-L863 */ + int rc = get_vote_state_handler_checked( + vote_account, + target_version, + 0, + ctx->runtime->vote_program.withdraw.vote_state_mem, + ctx->runtime->vote_program.withdraw.authorized_voters_mem, + ctx->runtime->vote_program.withdraw.landed_votes_mem + ); if( FD_UNLIKELY( rc ) ) return rc; - fd_vote_state_versioned_t * vote_state_versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.withdraw.vote_state_mem; - convert_to_current( vote_state_versioned, - ctx->runtime->vote_program.withdraw.authorized_voters_mem, - ctx->runtime->vote_program.withdraw.landed_votes_mem ); - fd_vote_state_t * vote_state = &vote_state_versioned->inner.current; + fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.withdraw.vote_state_mem; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1014 - rc = verify_authorized_signer( &vote_state->authorized_withdrawer, signers ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L865 */ + rc = fd_vote_verify_authorized_signer( + fd_vsv_get_authorized_withdrawer( versioned ), + signers + ); if( FD_UNLIKELY( rc ) ) return rc; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1016 - if( FD_UNLIKELY( lamports > fd_borrowed_account_get_lamports( vote_account ) ) ) + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L867-L870 */ + ulong vote_account_lamports = fd_borrowed_account_get_lamports( vote_account ); + if( FD_UNLIKELY( lamports>vote_account_lamports ) ) { return FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS; - ulong remaining_balance = fd_borrowed_account_get_lamports( vote_account ) - lamports; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1021 - if( FD_UNLIKELY( remaining_balance == 0 ) ) { - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1014 - int reject_active_vote_account_close = 0; - - ulong last_epoch_with_credits; - if( FD_LIKELY( !deq_fd_vote_epoch_credits_t_empty( vote_state->epoch_credits ) ) ) { - last_epoch_with_credits = - deq_fd_vote_epoch_credits_t_peek_tail_const( vote_state->epoch_credits )->epoch; - ulong current_epoch = clock->epoch; - reject_active_vote_account_close = - fd_ulong_sat_sub( current_epoch, last_epoch_with_credits ) < 2; + } + ulong remaining_balance = vote_account_lamports-lamports; + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L872-L896 */ + if( FD_UNLIKELY( remaining_balance==0UL ) ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L873-L883 */ + fd_vote_epoch_credits_t const * epoch_credits = fd_vsv_get_epoch_credits( versioned ); + ulong last_epoch_with_credits; + int reject_active_vote_account_close = 0; + if( FD_LIKELY( !deq_fd_vote_epoch_credits_t_empty( epoch_credits ) ) ) { + ulong current_epoch = clock->epoch; + last_epoch_with_credits = deq_fd_vote_epoch_credits_t_peek_tail_const( epoch_credits )->epoch; + reject_active_vote_account_close = fd_ulong_sat_sub( current_epoch, last_epoch_with_credits )<2UL; } - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1034 + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L885-L890 */ if( FD_UNLIKELY( reject_active_vote_account_close ) ) { - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1036 ctx->txn_out->err.custom_err = FD_VOTE_ERR_ACTIVE_VOTE_ACCOUNT_CLOSE; return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; } else { - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1040 - fd_vote_state_versioned_t vote_state_versions; - fd_vote_state_versioned_new_disc( &vote_state_versions, - fd_vote_state_versioned_enum_current ); - vote_state_versions.inner.current.prior_voters.idx = 31; - vote_state_versions.inner.current.prior_voters.is_empty = 1; - fd_vote_state_t * default_vote_state = &vote_state_versions.inner.current; - rc = 0; - rc = set_vote_account_state( vote_account, default_vote_state, ctx, ctx->runtime->vote_program.withdraw.vote_lockout_mem ); - if( FD_UNLIKELY( rc != 0 ) ) return rc; + rc = fd_vsv_deinitialize_vote_account_state( + ctx, + vote_account, + target_version, + ctx->runtime->vote_program.withdraw.vote_lockout_mem + ); + if( FD_UNLIKELY( rc ) ) return rc; } } else { - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1043 - ulong min_rent_exempt_balance = - fd_rent_exempt_minimum_balance( rent_sysvar, fd_borrowed_account_get_data_len( vote_account ) ); - if( remaining_balance < min_rent_exempt_balance ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L892-L895 */ + ulong min_rent_exempt_balance = fd_rent_exempt_minimum_balance( rent_sysvar, fd_borrowed_account_get_data_len( vote_account ) ); + if( FD_UNLIKELY( remaining_balancehash, slot_hashes, ctx ); + rc = check_slots_are_valid( ctx, versioned, vote_slots, &vote->hash, slot_hashes ); if( FD_UNLIKELY( rc ) ) return rc; for( deq_ulong_iter_t iter = deq_ulong_iter_init( vote_slots ); !deq_ulong_iter_done( vote_slots, iter ); iter = deq_ulong_iter_next( vote_slots, iter ) ) { ulong * ele = deq_ulong_iter_ele( vote_slots, iter ); // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L772 - process_next_vote_slot( vote_state, *ele, epoch, current_slot ); + fd_vsv_process_next_vote_slot( versioned, *ele, epoch, current_slot ); } return 0; } // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L783 static int -process_vote( fd_vote_state_t * vote_state, +process_vote( fd_exec_instr_ctx_t * ctx, + fd_vote_state_versioned_t * versioned, fd_vote_t const * vote, fd_slot_hash_t const * slot_hashes, /* deque */ ulong epoch, - ulong current_slot, - fd_exec_instr_ctx_t const * ctx ) { + ulong current_slot ) { // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L792 if( FD_UNLIKELY( deq_ulong_empty( vote->slots ) ) ) { ctx->txn_out->err.custom_err = FD_VOTE_ERR_EMPTY_SLOTS; @@ -1785,163 +1159,136 @@ process_vote( fd_vote_state_t * vote_state, // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L805 return process_vote_unfiltered( - vote_state, vote_slots, vote, slot_hashes, epoch, current_slot, ctx ); + ctx, + versioned, + vote_slots, + vote, + slot_hashes, + epoch, + current_slot + ); } -// https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1060 +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L905-L926 */ static int -initialize_account( fd_borrowed_account_t * vote_account, +initialize_account( fd_exec_instr_ctx_t * ctx, + fd_borrowed_account_t * vote_account, + int target_version, fd_vote_init_t * vote_init, fd_pubkey_t const * signers[static FD_TXN_SIG_MAX], - fd_sol_sysvar_clock_t const * clock, - fd_exec_instr_ctx_t const * ctx /* feature_set */ ) { + fd_sol_sysvar_clock_t const * clock ) { int rc; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1067 - ulong data_len = fd_borrowed_account_get_data_len( vote_account ); - if( FD_UNLIKELY( data_len != size_of_versioned( 1 ) ) ) { - return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; - } + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L915 */ + rc = check_vote_account_length( vote_account, target_version ); + if( FD_UNLIKELY( rc ) ) return rc; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1074 - rc = get_state( vote_account->acct, ctx->runtime->vote_program.init_account.vote_state_mem ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L916 */ + rc = fd_vsv_get_state( vote_account->acct, ctx->runtime->vote_program.init_account.vote_state_mem ); if( FD_UNLIKELY( rc ) ) return rc; fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.init_account.vote_state_mem; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1076 + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L918-L920 */ if( FD_UNLIKELY( !is_uninitialized( versioned ) ) ) { return FD_EXECUTOR_INSTR_ERR_ACC_ALREADY_INITIALIZED; } - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1081 - rc = verify_authorized_signer( &vote_init->node_pubkey, signers ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L923 */ + rc = fd_vote_verify_authorized_signer( &vote_init->node_pubkey, signers ); if( FD_UNLIKELY( rc ) ) { return rc; } - /* - * N.B. Technically we should destroy() to release memory before - * newing, otherwise the pointers are wiped and memory is leaked. - * We are probably fine for now since we are bump allocating - * everything and the enclosing frame will free everything when - * popped. - */ - // reset the object - fd_vote_state_versioned_new( versioned ); - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1083 - vote_state_new( vote_init, - clock, - ctx->runtime->vote_program.init_account.authorized_voters_mem, - &versioned->inner.current ); - return set_vote_account_state( vote_account, &versioned->inner.current, ctx, ctx->runtime->vote_program.init_account.vote_lockout_mem ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L925 */ + return init_vote_account_state( ctx, vote_account, versioned, target_version, vote_init, clock ); } -// https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1086 +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L928-L953 */ static int -verify_and_get_vote_state( fd_borrowed_account_t * vote_account, +process_vote_with_account( fd_exec_instr_ctx_t * ctx, + fd_borrowed_account_t * vote_account, + int target_version, + fd_slot_hash_t const * slot_hashes, /* deque */ fd_sol_sysvar_clock_t const * clock, - fd_pubkey_t const * signers[FD_TXN_SIG_MAX], - fd_vote_state_t * vote_state /* out */, - uchar * vote_state_mem, - uchar * authorized_voters_mem, - uchar * landed_votes_mem ) { - int rc = 0; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1091 - rc = get_state( vote_account->acct, vote_state_mem ); + fd_vote_t * vote, + fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L936-L939 */ + int rc = get_vote_state_handler_checked( + vote_account, + target_version, + 1, + ctx->runtime->vote_program.process_vote.vote_state_mem, + ctx->runtime->vote_program.process_vote.authorized_voters_mem, + ctx->runtime->vote_program.process_vote.landed_votes_mem + ); if( FD_UNLIKELY( rc ) ) return rc; - fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)vote_state_mem; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1093 - if( FD_UNLIKELY( is_uninitialized( versioned ) ) ) - return FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1097 - convert_to_current( versioned, authorized_voters_mem, landed_votes_mem ); - *vote_state = versioned->inner.current; + fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.process_vote.vote_state_mem; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1098 + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L941 */ fd_pubkey_t * authorized_voter = NULL; - rc = get_and_update_authorized_voter( vote_state, clock->epoch, &authorized_voter ); + rc = fd_authorized_voters_get_and_update_authorized_voter( versioned, clock->epoch, &authorized_voter ); if( FD_UNLIKELY( rc ) ) return rc; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1099 - rc = verify_authorized_signer( authorized_voter, signers ); - if( FD_UNLIKELY( rc ) ) return rc; - - return FD_EXECUTOR_INSTR_SUCCESS; -} - -// https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1104 -static int -process_vote_with_account( fd_borrowed_account_t * vote_account, - fd_slot_hash_t const * slot_hashes, /* deque */ - fd_sol_sysvar_clock_t const * clock, - fd_vote_t * vote, - fd_pubkey_t const * signers[static FD_TXN_SIG_MAX], - fd_exec_instr_ctx_t const * ctx ) { - - int rc; - fd_vote_state_t vote_state; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1112 - rc = verify_and_get_vote_state( vote_account, - clock, - signers, - &vote_state, - ctx->runtime->vote_program.process_vote.vote_state_mem, - ctx->runtime->vote_program.process_vote.authorized_voters_mem, - ctx->runtime->vote_program.process_vote.landed_votes_mem ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L942 */ + rc = fd_vote_verify_authorized_signer( authorized_voter, signers ); if( FD_UNLIKELY( rc ) ) return rc; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1117 - rc = process_vote( &vote_state, vote, slot_hashes, clock->epoch, clock->slot, ctx ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L944 */ + rc = process_vote( ctx, versioned, vote, slot_hashes, clock->epoch, clock->slot ); if( FD_UNLIKELY( rc ) ) return rc; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1126 + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L945-L951 */ if( FD_LIKELY( vote->has_timestamp ) ) { - if( FD_UNLIKELY( deq_ulong_cnt( vote->slots ) == 0 ) ) { + /* Calling max() on an empty iterator returns None */ + if( FD_UNLIKELY( deq_ulong_cnt( vote->slots )==0 ) ) { ctx->txn_out->err.custom_err = FD_VOTE_ERR_EMPTY_SLOTS; return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; } - ulong max = deq_ulong_peek_head( vote->slots ) ? *deq_ulong_peek_head( vote->slots ) : 0UL; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1127 + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L948 */ + ulong max = 0UL; for( deq_ulong_iter_t iter = deq_ulong_iter_init( vote->slots ); - !deq_ulong_iter_done( vote->slots, iter ); - iter = deq_ulong_iter_next( vote->slots, iter ) ) { + !deq_ulong_iter_done( vote->slots, iter ); + iter = deq_ulong_iter_next( vote->slots, iter ) ) { ulong * ele = deq_ulong_iter_ele( vote->slots, iter ); max = fd_ulong_max( max, *ele ); } - if( FD_UNLIKELY( !max ) ) { - ctx->txn_out->err.custom_err = FD_VOTE_ERR_EMPTY_SLOTS; - return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; - } - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1131 - rc = process_timestamp( &vote_state, max, vote->timestamp, ctx ); + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L950 */ + rc = fd_vsv_process_timestamp( ctx, versioned, max, vote->timestamp ); if( FD_UNLIKELY( rc ) ) return rc; } - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1133 - return set_vote_account_state( vote_account, &vote_state, ctx, ctx->runtime->vote_program.process_vote.vote_lockout_mem ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L952 */ + return fd_vsv_set_vote_account_state( + ctx, + vote_account, + versioned, + ctx->runtime->vote_program.process_vote.vote_lockout_mem + ); } // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1156 static int -do_process_vote_state_update( fd_vote_state_t * vote_state, +do_process_vote_state_update( fd_exec_instr_ctx_t * ctx, + fd_vote_state_versioned_t * versioned, fd_slot_hash_t const * slot_hashes, /* deque */ ulong epoch, ulong slot, - fd_vote_state_update_t * vote_state_update, - fd_exec_instr_ctx_t const * ctx /* feature_set */ ) { + fd_vote_state_update_t * vote_state_update ) { int rc; // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1164 rc = check_and_filter_proposed_vote_state( - vote_state, - vote_state_update->lockouts, &vote_state_update->has_root, &vote_state_update->root, &vote_state_update->hash, - slot_hashes, ctx ); + ctx, + versioned, + vote_state_update->lockouts, + &vote_state_update->has_root, + &vote_state_update->root, + &vote_state_update->hash, + slot_hashes + ); if( FD_UNLIKELY( rc ) ) return rc; // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1177 @@ -1957,109 +1304,157 @@ do_process_vote_state_update( fd_vote_state_t * vote_state, } // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1171 - return process_new_vote_state( vote_state, - landed_votes, - vote_state_update->has_root, - vote_state_update->root, - vote_state_update->has_timestamp, - vote_state_update->timestamp, - epoch, - slot, - ctx ); + return process_new_vote_state( + ctx, + versioned, + landed_votes, + vote_state_update->has_root, + vote_state_update->root, + vote_state_update->has_timestamp, + vote_state_update->timestamp, + epoch, + slot + ); } +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L955-L979 */ static int -process_vote_state_update( fd_borrowed_account_t * vote_account, +process_vote_state_update( fd_exec_instr_ctx_t * ctx, + fd_borrowed_account_t * vote_account, + int target_version, fd_slot_hash_t const * slot_hashes, fd_sol_sysvar_clock_t const * clock, fd_vote_state_update_t * vote_state_update, - fd_pubkey_t const * signers[static FD_TXN_SIG_MAX], - fd_exec_instr_ctx_t const * ctx /* feature_set */ ) { - fd_vote_state_t vote_state; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1144 - int rc = verify_and_get_vote_state( vote_account, - clock, - signers, - &vote_state, - ctx->runtime->vote_program.process_vote.vote_state_mem, - ctx->runtime->vote_program.process_vote.authorized_voters_mem, - ctx->runtime->vote_program.process_vote.landed_votes_mem ); + fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ) { + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L963-L966 */ + int rc = get_vote_state_handler_checked( + vote_account, + target_version, + 1, + ctx->runtime->vote_program.process_vote.vote_state_mem, + ctx->runtime->vote_program.process_vote.authorized_voters_mem, + ctx->runtime->vote_program.process_vote.landed_votes_mem + ); + if( FD_UNLIKELY( rc ) ) return rc; + + fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.process_vote.vote_state_mem; + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L968 */ + fd_pubkey_t * authorized_voter = NULL; + rc = fd_authorized_voters_get_and_update_authorized_voter( versioned, clock->epoch, &authorized_voter ); if( FD_UNLIKELY( rc ) ) return rc; + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L969 */ + rc = fd_vote_verify_authorized_signer( authorized_voter, signers ); + if( FD_UNLIKELY( rc ) ) return rc; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1145 + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L971-L977 */ rc = do_process_vote_state_update( - &vote_state, slot_hashes, clock->epoch, clock->slot, vote_state_update, ctx ); + ctx, + versioned, + slot_hashes, + clock->epoch, + clock->slot, + vote_state_update + ); if( FD_UNLIKELY( rc ) ) { return rc; } - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1153 - rc = set_vote_account_state( vote_account, &vote_state, ctx, ctx->runtime->vote_program.process_vote.vote_lockout_mem ); - - return rc; + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L978 */ + return fd_vsv_set_vote_account_state( + ctx, + vote_account, + versioned, + ctx->runtime->vote_program.process_vote.vote_lockout_mem + ); } -// https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1206 +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1035-L1061 */ static int -do_process_tower_sync( fd_vote_state_t * vote_state, +do_process_tower_sync( fd_exec_instr_ctx_t * ctx, + fd_vote_state_versioned_t * versioned, fd_slot_hash_t const * slot_hashes, /* deque */ ulong epoch, ulong slot, - fd_tower_sync_t * tower_sync, - fd_exec_instr_ctx_t const * ctx /* feature_set */ ) { - - do { - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1214 - int err = check_and_filter_proposed_vote_state( - vote_state, - tower_sync->lockouts, &tower_sync->has_root, &tower_sync->root, &tower_sync->hash, - slot_hashes, ctx ); - if( FD_UNLIKELY( err ) ) return err; - } while(0); + fd_tower_sync_t * tower_sync ) { + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1042-L1048 */ + int rc = check_and_filter_proposed_vote_state( + ctx, + versioned, + tower_sync->lockouts, + &tower_sync->has_root, + &tower_sync->root, + &tower_sync->hash, + slot_hashes + ); + if( FD_UNLIKELY( rc ) ) return rc; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1221 + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1049-L1060 */ return process_new_vote_state( - vote_state, - landed_votes_from_lockouts( tower_sync->lockouts, ctx->runtime->vote_program.tower_sync.tower_sync_landed_votes_mem ), + ctx, + versioned, + fd_vote_landed_votes_from_lockouts( tower_sync->lockouts, ctx->runtime->vote_program.tower_sync.tower_sync_landed_votes_mem ), tower_sync->has_root, tower_sync->root, tower_sync->has_timestamp, tower_sync->timestamp, epoch, - slot, - ctx ); + slot + ); } -// https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1186 +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1009-L1033 */ static int -process_tower_sync( fd_borrowed_account_t * vote_account, +process_tower_sync( fd_exec_instr_ctx_t * ctx, + fd_borrowed_account_t * vote_account, + int target_version, fd_slot_hash_t const * slot_hashes, /* deque */ fd_sol_sysvar_clock_t const * clock, fd_tower_sync_t * tower_sync, - fd_pubkey_t const * signers[static FD_TXN_SIG_MAX], - fd_exec_instr_ctx_t const * ctx /* feature_set */ ) { - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1194 - fd_vote_state_t vote_state; - do { - int err = verify_and_get_vote_state( vote_account, - clock, - signers, - &vote_state, - ctx->runtime->vote_program.tower_sync.vote_state_mem, - ctx->runtime->vote_program.tower_sync.authorized_voters_mem, - ctx->runtime->vote_program.tower_sync.vote_state_landed_votes_mem ); - if( FD_UNLIKELY( err ) ) return err; - } while(0); + fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1017-L1020 */ + int rc = get_vote_state_handler_checked( + vote_account, + target_version, + 1, + ctx->runtime->vote_program.tower_sync.vote_state_mem, + ctx->runtime->vote_program.tower_sync.authorized_voters_mem, + ctx->runtime->vote_program.tower_sync.vote_state_landed_votes_mem + ); + if( FD_UNLIKELY( rc ) ) return rc; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1195 - do { - int err = do_process_tower_sync( &vote_state, slot_hashes, clock->epoch, clock->slot, tower_sync, ctx ); - if( FD_UNLIKELY( err ) ) return err; - } while(0); + fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)ctx->runtime->vote_program.tower_sync.vote_state_mem; + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1022 */ + fd_pubkey_t * authorized_voter = NULL; + rc = fd_authorized_voters_get_and_update_authorized_voter( versioned, clock->epoch, &authorized_voter ); + if( FD_UNLIKELY( rc ) ) return rc; + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1023 */ + rc = fd_vote_verify_authorized_signer( authorized_voter, signers ); + if( FD_UNLIKELY( rc ) ) return rc; + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1025-L1031 */ + rc = do_process_tower_sync( + ctx, + versioned, + slot_hashes, + clock->epoch, + clock->slot, + tower_sync + ); + if( FD_UNLIKELY( rc ) ) return rc; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1203 - return set_vote_account_state( vote_account, &vote_state, ctx, ctx->runtime->vote_program.process_vote.vote_lockout_mem ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L1032 */ + return fd_vsv_set_vote_account_state( + ctx, + vote_account, + versioned, + ctx->runtime->vote_program.tower_sync.vote_lockout_mem + ); } /**********************************************************************/ @@ -2111,21 +1506,20 @@ fd_vote_decode_compact_update( fd_compact_vote_state_update_t * compact_update, /* mod vote_processor */ /**********************************************************************/ -// https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L21 +/* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L21-L51 */ static int -process_authorize_with_seed_instruction( - /* invoke_context */ - fd_exec_instr_ctx_t const * ctx, - /* transaction_context */ - fd_borrowed_account_t * vote_account, - fd_pubkey_t const * new_authority, - fd_vote_authorize_t authorization_type, - fd_pubkey_t const * current_authority_derived_key_owner, - uchar const * current_authority_derived_key_seed, - ulong current_authority_derived_key_seed_len ) { +process_authorize_with_seed_instruction( /* invoke_context */ + fd_exec_instr_ctx_t * ctx, + int target_version, + fd_borrowed_account_t * vote_account, + fd_pubkey_t const * new_authority, + fd_vote_authorize_t authorization_type, + fd_pubkey_t const * current_authority_derived_key_owner, + uchar const * current_authority_derived_key_seed, + ulong current_authority_derived_key_seed_len ) { int rc = 0; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L31 + /* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L31 */ rc = fd_sysvar_instr_acct_check( ctx, 1, &fd_sysvar_clock_id ); if( FD_UNLIKELY( rc ) ) return rc; @@ -2136,18 +1530,15 @@ process_authorize_with_seed_instruction( fd_pubkey_t * expected_authority_keys[FD_TXN_SIG_MAX] = { 0 }; fd_pubkey_t single_signer = { 0 }; - if( ctx->instr->acct_cnt < 3 ) - return FD_EXECUTOR_INSTR_ERR_MISSING_ACC; - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L33 + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_processor.rs#L30-L42 */ if( fd_instr_acc_is_signer_idx( ctx->instr, 2, &rc ) ) { - // https://github.com/anza-xyz/agave/blob/v2.1.14/programs/vote/src/vote_processor.rs#L34 + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_processor.rs#L31 */ fd_pubkey_t const * base_pubkey = NULL; rc = fd_exec_instr_ctx_get_key_of_account_at_index( ctx, 2UL, &base_pubkey ); if( FD_UNLIKELY( rc ) ) return rc; - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L37 + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_processor.rs#L34-L40 */ expected_authority_keys[0] = &single_signer; rc = fd_pubkey_create_with_seed( ctx, base_pubkey->uc, @@ -2158,13 +1549,16 @@ process_authorize_with_seed_instruction( if( FD_UNLIKELY( rc ) ) return rc; } - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L43 - return authorize( vote_account, - new_authority, - authorization_type, - (fd_pubkey_t const **)expected_authority_keys, - clock, - ctx ); + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_processor.rs#L43-L50 */ + return authorize( + ctx, + vote_account, + target_version, + new_authority, + authorization_type, + (fd_pubkey_t const **)expected_authority_keys, + clock + ); } /**********************************************************************/ @@ -2180,33 +1574,18 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L57 FD_EXEC_CU_UPDATE( ctx, DEFAULT_COMPUTE_UNITS ); - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L64 - if( FD_UNLIKELY( ctx->instr->acct_cnt < 1 ) ) { - return FD_EXECUTOR_INSTR_ERR_MISSING_ACC; - } - + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_processor.rs#L64-L67 */ fd_guarded_borrowed_account_t me = {0}; FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, 0, &me ); - - switch( rc ) { - case FD_ACC_MGR_SUCCESS: - break; - case FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT: - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/src/transaction_context.rs#L637 - return FD_EXECUTOR_INSTR_ERR_MISSING_ACC; - default: - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/src/transaction_context.rs#L639 - return FD_EXECUTOR_INSTR_ERR_ACC_BORROW_FAILED; - } - - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L65 if( FD_UNLIKELY( 0 != memcmp( fd_borrowed_account_get_owner( &me ), fd_solana_vote_program_id.key, sizeof( fd_pubkey_t ) ) ) ) { - // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L66 return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_OWNER; } + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_processor.rs#L69-L74 */ + int target_version = FD_FEATURE_ACTIVE_BANK( ctx->bank, vote_state_v4 ) ? VOTE_STATE_TARGET_VERSION_V4 : VOTE_STATE_TARGET_VERSION_V3; + // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L69 fd_pubkey_t const * signers[FD_TXN_SIG_MAX] = { 0 }; fd_exec_instr_ctx_get_signers( ctx, signers ); @@ -2260,11 +1639,7 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { if( FD_UNLIKELY( !clock ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR; // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L78 - rc = initialize_account( &me, - &instruction->inner.initialize_account, - signers, - clock, - ctx ); + rc = initialize_account( ctx, &me, target_version, &instruction->inner.initialize_account, signers, clock ); break; } @@ -2292,7 +1667,7 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { fd_pubkey_t const * voter_pubkey = &instruction->inner.authorize.pubkey; fd_vote_authorize_t vote_authorize = instruction->inner.authorize.vote_authorize; - rc = authorize( &me, voter_pubkey, vote_authorize, signers, clock, ctx ); + rc = authorize( ctx, &me, target_version, voter_pubkey, vote_authorize, signers, clock ); break; } @@ -2315,13 +1690,16 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L100 fd_vote_authorize_with_seed_args_t * args = &instruction->inner.authorize_with_seed; - rc = process_authorize_with_seed_instruction( ctx, - &me, - &args->new_authority, - args->authorization_type, - &args->current_authority_derived_key_owner, - args->current_authority_derived_key_seed, - args->current_authority_derived_key_seed_len ); + rc = process_authorize_with_seed_instruction( + ctx, + target_version, + &me, + &args->new_authority, + args->authorization_type, + &args->current_authority_derived_key_owner, + args->current_authority_derived_key_seed, + args->current_authority_derived_key_seed_len + ); break; } @@ -2359,13 +1737,15 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { } // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L119 - rc = process_authorize_with_seed_instruction( ctx, - &me, - new_authority, - args->authorization_type, - &args->current_authority_derived_key_owner, - args->current_authority_derived_key_seed, - args->current_authority_derived_key_seed_len ); + rc = process_authorize_with_seed_instruction( + ctx, + target_version, + &me, + new_authority, + args->authorization_type, + &args->current_authority_derived_key_owner, + args->current_authority_derived_key_seed, + args->current_authority_derived_key_seed_len ); break; } @@ -2391,7 +1771,7 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { if( FD_UNLIKELY( rc ) ) return rc; // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L135 - rc = update_validator_identity( &me, node_pubkey, signers, ctx ); + rc = update_validator_identity( ctx, target_version, &me, node_pubkey, signers ); break; } @@ -2414,12 +1794,15 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { } // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L145 - rc = update_commission( &me, - instruction->inner.update_commission, - signers, - epoch_schedule, - clock, - ctx ); + rc = update_commission( + ctx, + target_version, + &me, + instruction->inner.update_commission, + signers, + epoch_schedule, + clock + ); break; } @@ -2473,7 +1856,15 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { if( FD_UNLIKELY( !clock ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR; fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( ctx->sysvar_cache ); /* guaranteed to succeed */ - rc = process_vote_with_account( &me, slot_hashes, clock, vote, signers, ctx ); + rc = process_vote_with_account( + ctx, + &me, + target_version, + slot_hashes, + clock, + vote, + signers + ); fd_sysvar_cache_slot_hashes_leave_const( ctx->sysvar_cache, slot_hashes ); break; @@ -2527,7 +1918,15 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L173 fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( ctx->sysvar_cache ); - rc = process_vote_state_update( &me, slot_hashes, clock, vote_state_update, signers, ctx ); + rc = process_vote_state_update( + ctx, + &me, + target_version, + slot_hashes, + clock, + vote_state_update, + signers + ); fd_sysvar_cache_slot_hashes_leave_const( ctx->sysvar_cache, slot_hashes ); break; @@ -2589,7 +1988,15 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_processor.rs#L187 fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( ctx->sysvar_cache ); /* guaranteed to succeed */ - rc = process_vote_state_update( &me, slot_hashes, clock, &vote_update, signers, ctx ); + rc = process_vote_state_update( + ctx, + &me, + target_version, + slot_hashes, + clock, + &vote_update, + signers + ); fd_sysvar_cache_slot_hashes_leave_const( ctx->sysvar_cache, slot_hashes ); break; @@ -2622,7 +2029,15 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( ctx->sysvar_cache ); FD_TEST( slot_hashes ); - rc = process_tower_sync( &me, slot_hashes, clock, tower_sync, signers, ctx ); + rc = process_tower_sync( + ctx, + &me, + target_version, + slot_hashes, + clock, + tower_sync, + signers + ); fd_sysvar_cache_slot_hashes_leave_const( ctx->sysvar_cache, slot_hashes ); break; @@ -2650,13 +2065,16 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { if( FD_UNLIKELY( !clock_sysvar ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR; - rc = withdraw( ctx, - &me, - instruction->inner.withdraw, - 1UL, - signers, - rent_sysvar, - clock_sysvar ); + rc = withdraw( + ctx, + &me, + target_version, + instruction->inner.withdraw, + 1UL, + signers, + rent_sysvar, + clock_sysvar + ); break; } @@ -2700,12 +2118,15 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock_read( ctx->sysvar_cache, &clock_ ); if( FD_UNLIKELY( !clock ) ) return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR; - rc = authorize( &me, - voter_pubkey, - instruction->inner.authorize_checked, - signers, - clock, - ctx ); + rc = authorize( + ctx, + &me, + target_version, + voter_pubkey, + instruction->inner.authorize_checked, + signers, + clock + ); break; } @@ -2741,10 +2162,10 @@ fd_vote_state_versions_is_correct_and_initialized( fd_txn_account_t * vote_accou } fd_vote_state_versioned_t * -fd_vote_get_state( fd_txn_account_t const * self, +fd_vote_fd_vsv_get_state( fd_txn_account_t const * self, uchar * mem /* out */ ) { - int err = get_state( self, mem ); + int err = fd_vsv_get_state( self, mem ); return err ? NULL : (fd_vote_state_versioned_t *)mem; } @@ -2752,7 +2173,7 @@ void fd_vote_convert_to_current( fd_vote_state_versioned_t * self, uchar * authorized_voters_mem, uchar * landed_votes_mem ) { - convert_to_current( self, authorized_voters_mem, landed_votes_mem ); + fd_vsv_try_convert_to_v3( self, authorized_voters_mem, landed_votes_mem ); } void @@ -2779,3 +2200,10 @@ fd_vote_store_account( fd_txn_account_t * vote_account, fd_txn_account_get_data_len( vote_account ) ); fd_bank_vote_states_end_locking_modify( bank ); } + +fd_vote_state_versioned_t * +fd_vote_get_state( fd_txn_account_t const * self, + uchar * mem ) { + int err = fd_vsv_get_state( self, mem ); + return err ? NULL : (fd_vote_state_versioned_t *)mem; +} diff --git a/src/flamenco/runtime/program/fd_vote_program.h b/src/flamenco/runtime/program/fd_vote_program.h index a8d5a618d3c..46e7cc59ed7 100644 --- a/src/flamenco/runtime/program/fd_vote_program.h +++ b/src/flamenco/runtime/program/fd_vote_program.h @@ -12,6 +12,87 @@ #include "../context/fd_exec_instr_ctx.h" #include "../fd_bank.h" +/* Some vote instruction types are dynamically sized: + - tower_sync_switch (contains deque of fd_vote_lockout_t) + - tower_sync (contains deque of fd_vote_lockout_t) + - compact_vote_state_update_switch (vector of fd_lockout_offset_t) + - compact_vote_state_update (vector of fd_lockout_offset_t) + - authorize_checked_with_seed (char vector of current_authority_derived_key_seed) + - authorize_with_seed (char vector of current_authority_derived_key_seed) + - update_vote_state_switch (contains deque of fd_vote_lockout_t) + - update_vote_state (contains deque of fd_vote_lockout_t) + - vote_switch (deque of slot numbers) + - vote (deque of slot numbers) + All other vote instruction types are statically sized. + + A loose bound on the max amount of encoded fd_vote_lockout_t + possible is 1232 bytes/(12 bytes/per lockout) = 102 lockouts. So + the worst case bound for the deque of fd_vote_lockout is + 32 + (102 * sizeof(fd_vote_lockout_t)) = 1644 bytes. + + The worst case vector of fd_lockout_offset_t is one where each + encoded element is 2 bytes. This means that we can have 1232/2 = + 616 elements. They are represented as being 16 bytes each, so the + total footprint would be 9856 bytes. + + The deque of slot numbers is a vector of ulong, which is 8 bytes. + So the worst case is 1232 bytes/8 bytes = 154 elements. So, the + total footprint is 32 + (154 * 8 bytes) = 1264 bytes. + + The worst case char vector is 1232 bytes as each element is 1 byte + up to the txn MTU. + + With this, that means that the compact_vote_state_update_switch + can have the largest worst case footprint where the struct is + 104 bytes (sizeof(fd_compact_vote_state_update_switch_t) + the + worst case lockout vector of 616 elements. */ +#define FD_LOCKOUT_OFFSET_FOOTPRINT (9856UL) +#define FD_VOTE_INSTRUCTION_FOOTPRINT (sizeof(fd_vote_instruction_t) + FD_LOCKOUT_OFFSET_FOOTPRINT) + +/* TODO: This is the value as generated by fd_types bincode decoding of + fd_vote_state_versioned_t. This should eventually be replaced. */ +#define FD_VOTE_STATE_VERSIONED_FOOTPRINT (9248UL) + +/* The footprint of a fd_vote_authorized_voters_t struct is defined as a + fd_vote_authorized_voters_t followed by a pool and then a treap. */ +#define FD_AUTHORIZED_VOTERS_ALIGN (128UL) +#define FD_AUTHORIZED_VOTERS_FOOTPRINT (4888UL) + +/* TODO: These footprints are currently overprovisioned due to test + fixtures which currently violate protocol invariants. */ + +/* The footprint of the landed votes is determined by a deque with max + cnt of 31. The footprint is as follows: + alignof(DEQUE_T) == alignof(fd_landed_vote_t) == 8 + sizeof(DEQUE_T) == sizeof(fd_landed_vote_t) == 24 + return fd_ulong_align_up( fd_ulong_align_up( 32UL, alignof(DEQUE_T) ) + sizeof(DEQUE_T)*max, alignof(DEQUE_(private_t)) ); + return fd_ulong_align_up( fd_ulong_align_up( 32UL, 8UL ) ) + 24UL*31UL, 8UL ); + return fd_ulong_align_up( 32UL + 744, 8UL ) == 776 */ +#define FD_LANDED_VOTES_ALIGN (32UL) +#define FD_LANDED_VOTES_FOOTPRINT (FD_VOTE_STATE_VERSIONED_FOOTPRINT) + +/* The calculation for the landed votes footprint is the same as the + calculation for the landed votes but the sizeof(fd_vote_lockout_t) + is 16 bytes: + return fd_ulong_align_up( 32UL + 16UL * 31UL, 8UL ) == 528UL */ +#define FD_VOTE_LOCKOUTS_ALIGN (32UL) +#define FD_VOTE_LOCKOUTS_FOOTPRINT (FD_VOTE_STATE_VERSIONED_FOOTPRINT) + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L35 +#define MAX_LOCKOUT_HISTORY 31UL + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L36 +#define MAX_EPOCH_CREDITS_HISTORY 64UL + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L48 +#define VOTE_CREDITS_MAXIMUM_PER_SLOT 16 + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L45 +#define VOTE_CREDITS_GRACE_SLOTS 2 + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L597-L598 */ +#define DEFAULT_BLOCK_REVENUE_COMMISSION_BPS (10000UL) + /* Vote program custom error codes */ #define FD_VOTE_ERR_VOTE_TOO_OLD ( 0) @@ -37,6 +118,12 @@ #define FD_VOTE_STATE_V2_SZ (3731UL) #define FD_VOTE_STATE_V3_SZ (3762UL) +#define FD_VOTE_STATE_V4_SZ (3762UL) + +/* Target vote state versions + https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L639-L645 */ +#define VOTE_STATE_TARGET_VERSION_V3 (0) +#define VOTE_STATE_TARGET_VERSION_V4 (1) FD_PROTOTYPES_BEGIN diff --git a/src/flamenco/runtime/program/vote/Local.mk b/src/flamenco/runtime/program/vote/Local.mk new file mode 100644 index 00000000000..7a1acf88bf8 --- /dev/null +++ b/src/flamenco/runtime/program/vote/Local.mk @@ -0,0 +1,17 @@ +$(call add-hdrs,fd_authorized_voters.h) +$(call add-objs,fd_authorized_voters,fd_flamenco) + +$(call add-hdrs,fd_vote_common.h) +$(call add-objs,fd_vote_common,fd_flamenco) + +$(call add-hdrs,fd_vote_lockout.h) +$(call add-objs,fd_vote_lockout,fd_flamenco) + +$(call add-hdrs,fd_vote_state_versioned.h) +$(call add-objs,fd_vote_state_versioned,fd_flamenco) + +$(call add-hdrs,fd_vote_state_v3.h) +$(call add-objs,fd_vote_state_v3,fd_flamenco) + +$(call add-hdrs,fd_vote_state_v4.h) +$(call add-objs,fd_vote_state_v4,fd_flamenco) diff --git a/src/flamenco/runtime/program/vote/fd_authorized_voters.c b/src/flamenco/runtime/program/vote/fd_authorized_voters.c new file mode 100644 index 00000000000..da994205232 --- /dev/null +++ b/src/flamenco/runtime/program/vote/fd_authorized_voters.c @@ -0,0 +1,164 @@ +#include "fd_authorized_voters.h" +#include "fd_vote_state_v3.h" +#include "fd_vote_state_v4.h" + +/**********************************************************************/ +/* impl AuthorizedVoters */ +/**********************************************************************/ + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L17 +fd_vote_authorized_voters_t * +fd_authorized_voters_new( ulong epoch, + fd_pubkey_t const * pubkey, + uchar * mem ) { + + FD_SCRATCH_ALLOC_INIT( l, mem ); + fd_vote_authorized_voters_t * authorized_voters = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_align(), sizeof(fd_vote_authorized_voters_t) ); + void * pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_pool_align(), fd_vote_authorized_voters_pool_footprint( FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); + void * treap_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_treap_align(), fd_vote_authorized_voters_treap_footprint( FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); + + authorized_voters->pool = fd_vote_authorized_voters_pool_join( fd_vote_authorized_voters_pool_new( pool_mem, FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); + authorized_voters->treap = fd_vote_authorized_voters_treap_join( fd_vote_authorized_voters_treap_new( treap_mem, FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); + if( 0 == fd_vote_authorized_voters_pool_free( authorized_voters->pool ) ) { + FD_LOG_ERR(( "Authorized_voter pool is empty" )); + } + fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_pool_ele_acquire( authorized_voters->pool ); + ele->epoch = epoch; + ele->pubkey = *pubkey; + ele->prio = (ulong)&ele->pubkey; + fd_vote_authorized_voters_treap_ele_insert( authorized_voters->treap, ele, authorized_voters->pool ); + return authorized_voters; +} + +// Helper to create an empty AuthorizedVoters structure (for default/uninitialized states) +fd_vote_authorized_voters_t * +fd_authorized_voters_new_empty( uchar * mem ) { + FD_SCRATCH_ALLOC_INIT( l, mem ); + fd_vote_authorized_voters_t * authorized_voters = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_align(), sizeof(fd_vote_authorized_voters_t) ); + void * pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_pool_align(), fd_vote_authorized_voters_pool_footprint( FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); + void * treap_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_authorized_voters_treap_align(), fd_vote_authorized_voters_treap_footprint( FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); + + authorized_voters->pool = fd_vote_authorized_voters_pool_join( fd_vote_authorized_voters_pool_new( pool_mem, FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); + authorized_voters->treap = fd_vote_authorized_voters_treap_join( fd_vote_authorized_voters_treap_new( treap_mem, FD_VOTE_AUTHORIZED_VOTERS_MIN ) ); + return authorized_voters; +} + +int +fd_authorized_voters_is_empty( fd_vote_authorized_voters_t * self ) { + return fd_vote_authorized_voters_treap_ele_cnt( self->treap ) == 0; +} + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L80 +int +fd_authorized_voters_contains( fd_vote_authorized_voters_t * self, ulong epoch ) { + return !!fd_vote_authorized_voters_treap_ele_query( self->treap, epoch, self->pool ); +} + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L72 +fd_vote_authorized_voter_t * +fd_authorized_voters_last( fd_vote_authorized_voters_t * self ) { + fd_vote_authorized_voters_treap_rev_iter_t iter = + fd_vote_authorized_voters_treap_rev_iter_init( self->treap, self->pool ); + return fd_vote_authorized_voters_treap_rev_iter_ele( iter, self->pool ); +} + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L43 +void +fd_authorized_voters_purge_authorized_voters( fd_vote_authorized_voters_t * self, + ulong current_epoch ) { + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L46 + ulong expired_keys[ FD_VOTE_AUTHORIZED_VOTERS_MIN ]; + ulong key_cnt = 0; + for( fd_vote_authorized_voters_treap_fwd_iter_t iter = + fd_vote_authorized_voters_treap_fwd_iter_init( self->treap, self->pool ); + !fd_vote_authorized_voters_treap_fwd_iter_done( iter ); + iter = fd_vote_authorized_voters_treap_fwd_iter_next( iter, self->pool ) ) { + fd_vote_authorized_voter_t * ele = + fd_vote_authorized_voters_treap_fwd_iter_ele( iter, self->pool ); + if( ele->epoch < current_epoch ) expired_keys[key_cnt++] = ele->epoch; + } + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L52 + for( ulong i = 0; i < key_cnt; i++ ) { + fd_vote_authorized_voter_t * ele = + fd_vote_authorized_voters_treap_ele_query( self->treap, expired_keys[i], self->pool ); + fd_vote_authorized_voters_treap_ele_remove( self->treap, ele, self->pool ); + fd_vote_authorized_voters_pool_ele_release( self->pool, ele ); + } + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L60 + FD_TEST( !fd_authorized_voters_is_empty( self ) ); + +} + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L91 +fd_vote_authorized_voter_t * +fd_authorized_voters_get_or_calculate_authorized_voter_for_epoch( fd_vote_authorized_voters_t * self, + ulong epoch, + int * existed ) { + *existed = 0; + ulong latest_epoch = 0; + fd_vote_authorized_voter_t * res = + fd_vote_authorized_voters_treap_ele_query( self->treap, epoch, self->pool ); + // "predecessor" would be more big-O optimal here, but mirroring labs logic + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L93 + if( FD_UNLIKELY( !res ) ) { + for( fd_vote_authorized_voters_treap_fwd_iter_t iter = + fd_vote_authorized_voters_treap_fwd_iter_init( self->treap, self->pool ); + !fd_vote_authorized_voters_treap_fwd_iter_done( iter ); + iter = fd_vote_authorized_voters_treap_fwd_iter_next( iter, self->pool ) ) { + fd_vote_authorized_voter_t * ele = + fd_vote_authorized_voters_treap_fwd_iter_ele( iter, self->pool ); + if( ele->epoch < epoch && ( latest_epoch == 0 || ele->epoch > latest_epoch ) ) { + latest_epoch = ele->epoch; + res = ele; + } + } + *existed = 0; + return res; + } else { + *existed = 1; + return res; + } + return res; +} + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L28 +fd_vote_authorized_voter_t * +fd_authorized_voters_get_and_cache_authorized_voter_for_epoch( fd_vote_authorized_voters_t * self, + ulong epoch ) { + int existed = 0; + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L29 + fd_vote_authorized_voter_t * res = + fd_authorized_voters_get_or_calculate_authorized_voter_for_epoch( self, epoch, &existed ); + if( !res ) return NULL; + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L32 + if( !existed ) { + /* insert cannot fail because !existed */ + if( 0 == fd_vote_authorized_voters_pool_free( self->pool) ) { + FD_LOG_ERR(( "Authorized_voter pool is empty" )); + } + fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_pool_ele_acquire( self->pool ); + ele->epoch = epoch; + ele->pubkey = res->pubkey; + ele->prio = (ulong)&res->pubkey; + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L33 + fd_vote_authorized_voters_treap_ele_insert( self->treap, ele, self->pool ); + } + return res; +} + +int +fd_authorized_voters_get_and_update_authorized_voter( fd_vote_state_versioned_t * self, + ulong current_epoch, + fd_pubkey_t ** pubkey /* out */ ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_current: + return fd_vote_state_v3_get_and_update_authorized_voter( &self->inner.current, current_epoch, pubkey ); + case fd_vote_state_versioned_enum_v4: + return fd_vote_state_v4_get_and_update_authorized_voter( &self->inner.v4, current_epoch, pubkey ); + default: + __builtin_unreachable(); + } +} diff --git a/src/flamenco/runtime/program/vote/fd_authorized_voters.h b/src/flamenco/runtime/program/vote/fd_authorized_voters.h new file mode 100644 index 00000000000..ed3723ff91c --- /dev/null +++ b/src/flamenco/runtime/program/vote/fd_authorized_voters.h @@ -0,0 +1,55 @@ +#ifndef HEADER_fd_src_flamenco_runtime_program_vote_fd_authorized_voters_h +#define HEADER_fd_src_flamenco_runtime_program_vote_fd_authorized_voters_h + +#include "../../../types/fd_types.h" +#include "../../../../util/fd_util_base.h" + +/**********************************************************************/ +/* impl AuthorizedVoters */ +/**********************************************************************/ + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L17 +fd_vote_authorized_voters_t * +fd_authorized_voters_new( ulong epoch, + fd_pubkey_t const * pubkey, + uchar * mem ); + +// Helper to create an empty AuthorizedVoters structure (for default/uninitialized states) +fd_vote_authorized_voters_t * +fd_authorized_voters_new_empty( uchar * mem ); + +int +fd_authorized_voters_is_empty( fd_vote_authorized_voters_t * self ); + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L80 +int +fd_authorized_voters_contains( fd_vote_authorized_voters_t * self, ulong epoch ); + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L72 +fd_vote_authorized_voter_t * +fd_authorized_voters_last( fd_vote_authorized_voters_t * self ); + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L43 +void +fd_authorized_voters_purge_authorized_voters( fd_vote_authorized_voters_t * self, + ulong current_epoch ); + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L91 +fd_vote_authorized_voter_t * +fd_authorized_voters_get_or_calculate_authorized_voter_for_epoch( fd_vote_authorized_voters_t * self, + ulong epoch, + int * existed ); + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/authorized_voters.rs#L28 +fd_vote_authorized_voter_t * +fd_authorized_voters_get_and_cache_authorized_voter_for_epoch( fd_vote_authorized_voters_t * self, + ulong epoch ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L707-L715 */ +int +fd_authorized_voters_get_and_update_authorized_voter( fd_vote_state_versioned_t * self, + ulong current_epoch, + fd_pubkey_t ** pubkey /* out */ ); + +#endif /* HEADER_fd_src_flamenco_runtime_program_vote_fd_authorized_voters_h */ + diff --git a/src/flamenco/runtime/program/vote/fd_vote_common.c b/src/flamenco/runtime/program/vote/fd_vote_common.c new file mode 100644 index 00000000000..654a424719e --- /dev/null +++ b/src/flamenco/runtime/program/vote/fd_vote_common.c @@ -0,0 +1,78 @@ +#include "fd_vote_common.h" +#include "../fd_vote_program.h" +#include "../fd_program_util.h" + +int +fd_vote_verify_authorized_signer( fd_pubkey_t const * authorized, + fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ) { + // https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L989 + return fd_signers_contains( signers, authorized ) ? + FD_EXECUTOR_INSTR_SUCCESS : + FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; +} + +int +fd_vote_signature_verify( fd_pubkey_t * epoch_authorized_voter, + int authorized_withdrawer_signer, + fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ) { + return authorized_withdrawer_signer ? 0 : fd_vote_verify_authorized_signer( epoch_authorized_voter, signers ); +} + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L668 +uchar +fd_vote_compute_vote_latency( ulong voted_for_slot, ulong current_slot ) { + return (uchar)fd_ulong_min( fd_ulong_sat_sub( current_slot, voted_for_slot ), UCHAR_MAX ); +} + +ulong +fd_vote_credits_for_vote_at_index( fd_landed_vote_t const * votes, + ulong index ) { + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L679 + fd_landed_vote_t const * landed_vote = deq_fd_landed_vote_t_peek_index_const( votes, index ); + ulong latency = landed_vote == NULL ? 0 : landed_vote->latency; + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L683 + ulong max_credits = VOTE_CREDITS_MAXIMUM_PER_SLOT; + + // If latency is 0, this means that the Lockout was created and stored from a software version + // that did not store vote latencies; in this case, 1 credit is awarded + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L691 + if( FD_UNLIKELY( latency == 0 ) ) { + return 1; + } + + ulong diff = 0; + int cf = fd_ulong_checked_sub( latency, VOTE_CREDITS_GRACE_SLOTS, &diff ); + if( cf != 0 || diff == 0 ) { + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L697 + return max_credits; + } + + ulong credits = 0; + cf = fd_ulong_checked_sub( max_credits, diff, &credits ); + if( cf != 0 || credits == 0 ) { + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L705 + return 1; + } + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L707 + return credits; +} + +uchar +fd_vote_contains_slot( fd_landed_vote_t const * votes, + ulong slot ) { + /* Logic is copied from slice::binary_search_by() in Rust. While not fully optimized, + it aims to achieve fuzzing conformance for both sorted and unsorted inputs. */ + ulong size = deq_fd_landed_vote_t_cnt( votes ); + if( FD_UNLIKELY( size==0UL ) ) return 0; + + ulong base = 0UL; + while( size>1UL ) { + ulong half = size / 2UL; + ulong mid = base + half; + ulong mid_slot = deq_fd_landed_vote_t_peek_index_const( votes, mid )->lockout.slot; + base = (slotlockout.slot==slot; +} diff --git a/src/flamenco/runtime/program/vote/fd_vote_common.h b/src/flamenco/runtime/program/vote/fd_vote_common.h new file mode 100644 index 00000000000..3145b141133 --- /dev/null +++ b/src/flamenco/runtime/program/vote/fd_vote_common.h @@ -0,0 +1,32 @@ +#ifndef HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_common_h +#define HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_common_h + +#include "../../../types/fd_types.h" +#include "../../fd_executor.h" + +// https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L985 +int +fd_vote_verify_authorized_signer( fd_pubkey_t const * authorized, + fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ); + +// lambda function: https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L873 +int +fd_vote_signature_verify( fd_pubkey_t * epoch_authorized_voter, + int authorized_withdrawer_signer, + fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ); + +uchar +fd_vote_compute_vote_latency( ulong voted_for_slot, ulong current_slot ); + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L673 +ulong +fd_vote_credits_for_vote_at_index( fd_landed_vote_t const * votes, + ulong index ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L773-L778 */ +uchar +fd_vote_contains_slot( fd_landed_vote_t const * votes, + ulong slot ); + +#endif /* HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_common_h */ + diff --git a/src/flamenco/runtime/program/vote/fd_vote_lockout.c b/src/flamenco/runtime/program/vote/fd_vote_lockout.c new file mode 100644 index 00000000000..963ba064961 --- /dev/null +++ b/src/flamenco/runtime/program/vote/fd_vote_lockout.c @@ -0,0 +1,65 @@ +#include "fd_vote_lockout.h" +#include "../fd_vote_program.h" + +/**********************************************************************/ +/* impl Lockout */ +/**********************************************************************/ + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L104 +ulong +fd_vote_lockout_get_lockout( fd_vote_lockout_t * self ) { + /* Confirmation count can never be greater than MAX_LOCKOUT_HISTORY, preventing overflow. + Although Agave does not consider overflow, we do for fuzzing conformance. */ + ulong confirmation_count = fd_ulong_min( self->confirmation_count, MAX_LOCKOUT_HISTORY ); + return 1UL<slot, fd_vote_lockout_get_lockout( self ) ); +} + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L114 +ulong +fd_vote_lockout_is_locked_out_at_slot( fd_vote_lockout_t * self, ulong slot ) { + return fd_vote_lockout_last_locked_out_slot( self ) >= slot; +} + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L122 +void +fd_vote_lockout_increase_confirmation_count( fd_vote_lockout_t * self, uint by ) { + self->confirmation_count = fd_uint_sat_add( self->confirmation_count, by ); +} + +fd_landed_vote_t * +fd_vote_landed_votes_from_lockouts( fd_vote_lockout_t * lockouts, + uchar * mem ) { + if( !lockouts ) return NULL; + + /* Allocate MAX_LOCKOUT_HISTORY (sane case) by default. In case the + vote account is corrupt, allocate as many entries are needed. */ + + ulong cnt = deq_fd_vote_lockout_t_cnt( lockouts ); + cnt = fd_ulong_max( cnt, MAX_LOCKOUT_HISTORY ); + + fd_landed_vote_t * landed_votes = deq_fd_landed_vote_t_join( deq_fd_landed_vote_t_new( mem, cnt ) ); + if( FD_UNLIKELY( !landed_votes ) ) { + FD_LOG_CRIT(( "failed to join landed votes" )); + } + + for( deq_fd_vote_lockout_t_iter_t iter = deq_fd_vote_lockout_t_iter_init( lockouts ); + !deq_fd_vote_lockout_t_iter_done( lockouts, iter ); + iter = deq_fd_vote_lockout_t_iter_next( lockouts, iter ) ) { + fd_vote_lockout_t const * ele = deq_fd_vote_lockout_t_iter_ele_const( lockouts, iter ); + + fd_landed_vote_t * elem = deq_fd_landed_vote_t_push_tail_nocopy( landed_votes ); + fd_landed_vote_new( elem ); + + elem->latency = 0; + elem->lockout.slot = ele->slot; + elem->lockout.confirmation_count = ele->confirmation_count; + } + + return landed_votes; +} diff --git a/src/flamenco/runtime/program/vote/fd_vote_lockout.h b/src/flamenco/runtime/program/vote/fd_vote_lockout.h new file mode 100644 index 00000000000..4afb6a328f7 --- /dev/null +++ b/src/flamenco/runtime/program/vote/fd_vote_lockout.h @@ -0,0 +1,33 @@ +#ifndef HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_lockout_h +#define HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_lockout_h + +#include "../../../types/fd_types.h" +#include "../../../../util/fd_util_base.h" + +/**********************************************************************/ +/* impl Lockout */ +/**********************************************************************/ + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L104 +ulong +fd_vote_lockout_get_lockout( fd_vote_lockout_t * self ); + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L110 +ulong +fd_vote_lockout_last_locked_out_slot( fd_vote_lockout_t * self ); + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L114 +ulong +fd_vote_lockout_is_locked_out_at_slot( fd_vote_lockout_t * self, ulong slot ); + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L122 +void +fd_vote_lockout_increase_confirmation_count( fd_vote_lockout_t * self, uint by ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L1009-L1011 */ +fd_landed_vote_t * +fd_vote_landed_votes_from_lockouts( fd_vote_lockout_t * lockouts, + uchar * mem ); + +#endif /* HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_lockout_h */ + diff --git a/src/flamenco/runtime/program/vote/fd_vote_state_v3.c b/src/flamenco/runtime/program/vote/fd_vote_state_v3.c new file mode 100644 index 00000000000..3e7849840d3 --- /dev/null +++ b/src/flamenco/runtime/program/vote/fd_vote_state_v3.c @@ -0,0 +1,219 @@ +#include "fd_vote_state_v3.h" +#include "fd_authorized_voters.h" +#include "fd_vote_common.h" +#include "fd_vote_state_versioned.h" +#include "../fd_vote_program.h" +#include "../../fd_runtime.h" + +/**********************************************************************/ +/* impl From for VoteState1_14_11 */ +/**********************************************************************/ + +/* from_vote_state_1_14_11 converts a "current" vote state object into + the older "v1.14.11" version. This destroys the "current" object in + the process. */ + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L67 +static void +from_vote_state_1_14_11( fd_vote_state_t * vote_state, + fd_vote_state_1_14_11_t * vote_state_1_14_11, /* out */ + uchar * vote_lockout_mem ) { + vote_state_1_14_11->node_pubkey = vote_state->node_pubkey; /* copy */ + vote_state_1_14_11->authorized_withdrawer = vote_state->authorized_withdrawer; /* copy */ + vote_state_1_14_11->commission = vote_state->commission; /* copy */ + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L72 + if( vote_state->votes ) { + vote_state_1_14_11->votes = deq_fd_vote_lockout_t_join( + deq_fd_vote_lockout_t_new( vote_lockout_mem, deq_fd_landed_vote_t_cnt( vote_state->votes ) ) ); + for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( vote_state->votes ); + !deq_fd_landed_vote_t_iter_done( vote_state->votes, iter ); + iter = deq_fd_landed_vote_t_iter_next( vote_state->votes, iter ) ) { + fd_landed_vote_t const * landed_vote = deq_fd_landed_vote_t_iter_ele_const( vote_state->votes, iter ); + deq_fd_vote_lockout_t_push_tail_wrap( vote_state_1_14_11->votes, landed_vote->lockout ); + } + } + + vote_state_1_14_11->has_root_slot = vote_state->has_root_slot; /* copy */ + vote_state_1_14_11->root_slot = vote_state->root_slot; /* copy */ + vote_state_1_14_11->authorized_voters = vote_state->authorized_voters; /* move */ + vote_state_1_14_11->prior_voters = vote_state->prior_voters; /* deep copy */ + vote_state_1_14_11->epoch_credits = vote_state->epoch_credits; /* move */ + vote_state_1_14_11->last_timestamp = vote_state->last_timestamp; /* deep copy */ + + /* Clear moved objects */ + vote_state->authorized_voters.treap = NULL; + vote_state->authorized_voters.pool = NULL; + vote_state->epoch_credits = NULL; + +} + +/**********************************************************************/ +/* VoteStateV3 */ +/**********************************************************************/ + +/* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v3.rs#L65-L73 */ +void +fd_vote_program_v3_create_new( fd_vote_init_t * const vote_init, + fd_sol_sysvar_clock_t const * clock, + uchar * authorized_voters_mem, + fd_vote_state_versioned_t * versioned /* out */ ) { + versioned->discriminant = fd_vote_state_versioned_enum_current; + + fd_vote_state_t * vote_state = &versioned->inner.current; + vote_state->node_pubkey = vote_init->node_pubkey; + vote_state->authorized_voters = *fd_authorized_voters_new( clock->epoch, &vote_init->authorized_voter, authorized_voters_mem ); + vote_state->authorized_withdrawer = vote_init->authorized_withdrawer; + vote_state->commission = vote_init->commission; + vote_state->prior_voters.idx = 31; + vote_state->prior_voters.is_empty = 1; +} + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L414-L434 */ +int +fd_vote_state_v3_set_vote_account_state( fd_exec_instr_ctx_t const * ctx, + fd_borrowed_account_t * vote_account, + fd_vote_state_versioned_t * versioned, + uchar * vote_lockout_mem ) { + /* This is a horrible conditional expression in Agave. + The terms were broken up into their own variables. */ + fd_vote_state_t * v3_vote_state = &versioned->inner.current; + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L420-L424 */ + fd_rent_t const rent = fd_sysvar_cache_rent_read_nofail( ctx->sysvar_cache ); + int resize_needed = fd_borrowed_account_get_data_len( vote_account ) < FD_VOTE_STATE_V3_SZ; + int resize_rent_exempt = fd_rent_exempt_minimum_balance( &rent, FD_VOTE_STATE_V3_SZ ) <= fd_borrowed_account_get_lamports( vote_account ); + + /* The resize operation itself is part of the horrible conditional, + but behind a short-circuit operator. */ + int resize_failed = 0; + if( resize_needed && resize_rent_exempt ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L422-L424 */ + resize_failed = + fd_borrowed_account_set_data_length( vote_account, FD_VOTE_STATE_V3_SZ ) != FD_EXECUTOR_INSTR_SUCCESS; + } + + if( FD_UNLIKELY( resize_needed && ( !resize_rent_exempt || resize_failed ) ) ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L426-L430 */ + fd_vote_state_versioned_t v1_14_11; + fd_vote_state_versioned_new_disc( &v1_14_11, fd_vote_state_versioned_enum_v1_14_11 ); + from_vote_state_1_14_11( v3_vote_state, &v1_14_11.inner.v1_14_11, vote_lockout_mem ); + return fd_vsv_set_state( vote_account, &v1_14_11 ); + } + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L432-L433 */ + return fd_vsv_set_state( vote_account, versioned ); +} + +/* This is more than just a deserialization - this function attempts + to deserialize whatever vote state version the vote account has, + and then tries to convert it into a v3 vote account (unless its a + v4 account, where it fails automatically). + https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v3.rs#L119-L124 */ +int +fd_vote_state_v3_deserialize( fd_borrowed_account_t const * vote_account, + uchar * vote_state_mem, + uchar * authorized_voters_mem, + uchar * landed_votes_mem ) { + /* deserialize_into_ptr is essentially a call to get_state + + try_convert_to_v3. It's written a little more verbosely in Agave + as they try to optimize the decoding steps. + https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v3.rs#L162-L202 */ + int rc = fd_vsv_get_state( vote_account->acct, vote_state_mem ); + if( FD_UNLIKELY( rc ) ) return rc; + + fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)vote_state_mem; + return fd_vsv_try_convert_to_v3( versioned, authorized_voters_mem, landed_votes_mem ); +} + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L828 +int +fd_vote_state_v3_get_and_update_authorized_voter( fd_vote_state_t * self, + ulong current_epoch, + fd_pubkey_t ** pubkey /* out */ ) { + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L832 + fd_vote_authorized_voter_t * authorized_voter = + fd_authorized_voters_get_and_cache_authorized_voter_for_epoch( &self->authorized_voters, + current_epoch ); + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L835 + if( FD_UNLIKELY( !authorized_voter ) ) return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; + *pubkey = &authorized_voter->pubkey; + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L837 + fd_authorized_voters_purge_authorized_voters( &self->authorized_voters, current_epoch ); + return FD_EXECUTOR_INSTR_SUCCESS; +} + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L263-L321 */ +int +fd_vote_state_v3_set_new_authorized_voter( fd_exec_instr_ctx_t * ctx, + fd_vote_state_t * self, + fd_pubkey_t const * authorized_pubkey, + ulong current_epoch, + ulong target_epoch, + /* "verify" closure */ int authorized_withdrawer_signer, + /* "verify" closure */ fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ) { + int rc; + fd_pubkey_t * epoch_authorized_voter = NULL; + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L778 + rc = fd_vote_state_v3_get_and_update_authorized_voter( self, current_epoch, &epoch_authorized_voter ); + if( FD_UNLIKELY( rc ) ) return rc; + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L779 + rc = fd_vote_signature_verify( epoch_authorized_voter, authorized_withdrawer_signer, signers ); + if( FD_UNLIKELY( rc ) ) return rc; + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L786 + if( FD_UNLIKELY( fd_authorized_voters_contains( &self->authorized_voters, target_epoch ) ) ) { + ctx->txn_out->err.custom_err = FD_VOTE_ERR_TOO_SOON_TO_REAUTHORIZE; + return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; + } + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L791 + fd_vote_authorized_voter_t * latest_authorized = + fd_authorized_voters_last( &self->authorized_voters ); + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L794 + if( FD_UNLIKELY( ( !latest_authorized ) ) ) return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; + ulong latest_epoch = latest_authorized->epoch; + fd_pubkey_t * latest_authorized_pubkey = &latest_authorized->pubkey; + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L799 + if( 0 != memcmp( latest_authorized_pubkey, authorized_pubkey, sizeof( fd_pubkey_t ) ) ) { + fd_vote_prior_voters_t * prior_voters = &self->prior_voters; + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L801 + ulong epoch_of_last_authorized_switch = 0UL; + if( (!prior_voters->is_empty) & (prior_voters->idx < 32) ) { + epoch_of_last_authorized_switch = prior_voters->buf[prior_voters->idx].epoch_end; + } + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L810 + if( target_epoch <= latest_epoch ) + return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L815 + prior_voters->idx += 1UL; + prior_voters->idx %= 32UL; + prior_voters->buf[prior_voters->idx] = + ( fd_vote_prior_voter_t ){ .pubkey = *latest_authorized_pubkey, + .epoch_start = epoch_of_last_authorized_switch, + .epoch_end = target_epoch }; + prior_voters->is_empty = 0; + } + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L822 + if( 0 == fd_vote_authorized_voters_pool_free( self->authorized_voters.pool) ) { + FD_LOG_ERR(( "Authorized_voter pool is empty" )); + } + + fd_vote_authorized_voter_t * ele = + fd_vote_authorized_voters_pool_ele_acquire( self->authorized_voters.pool ); + ele->epoch = target_epoch; + ele->pubkey = *authorized_pubkey; + ele->prio = (ulong)&ele->pubkey; + fd_vote_authorized_voters_treap_ele_insert( + self->authorized_voters.treap, ele, self->authorized_voters.pool ); + + return 0; +} + diff --git a/src/flamenco/runtime/program/vote/fd_vote_state_v3.h b/src/flamenco/runtime/program/vote/fd_vote_state_v3.h new file mode 100644 index 00000000000..d27bb180419 --- /dev/null +++ b/src/flamenco/runtime/program/vote/fd_vote_state_v3.h @@ -0,0 +1,51 @@ +#ifndef HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_state_v3_h +#define HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_state_v3_h + +#include "../../fd_borrowed_account.h" +#include "../../fd_executor.h" +#include "../../../types/fd_types.h" +#include "../../sysvar/fd_sysvar.h" + +/* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v3.rs#L65-L73 */ +void +fd_vote_program_v3_create_new( fd_vote_init_t * const vote_init, + fd_sol_sysvar_clock_t const * clock, + uchar * authorized_voters_mem, + fd_vote_state_versioned_t * versioned /* out */ ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L414-L434 */ +int +fd_vote_state_v3_set_vote_account_state( fd_exec_instr_ctx_t const * ctx, + fd_borrowed_account_t * vote_account, + fd_vote_state_versioned_t * versioned, + uchar * vote_lockout_mem ); + +/* This is more than just a deserialization - this function attempts + to deserialize whatever vote state version the vote account has, + and then tries to convert it into a v3 vote account (unless its a + v4 account, where it fails automatically). + https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v3.rs#L119-L124 */ +int +fd_vote_state_v3_deserialize( fd_borrowed_account_t const * vote_account, + uchar * vote_state_mem, + uchar * authorized_voters_mem, + uchar * landed_votes_mem ); + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L828 +int +fd_vote_state_v3_get_and_update_authorized_voter( fd_vote_state_t * self, + ulong current_epoch, + fd_pubkey_t ** pubkey /* out */ ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L263-L321 */ +int +fd_vote_state_v3_set_new_authorized_voter( fd_exec_instr_ctx_t * ctx, + fd_vote_state_t * self, + fd_pubkey_t const * authorized_pubkey, + ulong current_epoch, + ulong target_epoch, + /* "verify" closure */ int authorized_withdrawer_signer, + /* "verify" closure */ fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ); + +#endif /* HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_state_v3_h */ + diff --git a/src/flamenco/runtime/program/vote/fd_vote_state_v4.c b/src/flamenco/runtime/program/vote/fd_vote_state_v4.c new file mode 100644 index 00000000000..370e021a2d3 --- /dev/null +++ b/src/flamenco/runtime/program/vote/fd_vote_state_v4.c @@ -0,0 +1,117 @@ +#include "fd_vote_state_v4.h" +#include "fd_authorized_voters.h" +#include "fd_vote_state_versioned.h" +#include "fd_vote_common.h" +#include "../fd_vote_program.h" +#include "../../fd_runtime.h" + +/**********************************************************************/ +/* VoteStateV4 */ +/**********************************************************************/ + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L600-L619 */ +void +fd_vote_state_v4_create_new( fd_pubkey_t const * vote_pubkey, + fd_vote_init_t const * vote_init, + fd_sol_sysvar_clock_t const * clock, + uchar * authorized_voters_mem, + fd_vote_state_versioned_t * versioned /* out */ ) { + versioned->discriminant = fd_vote_state_versioned_enum_v4; + + fd_vote_state_v4_t * vote_state = &versioned->inner.v4; + vote_state->node_pubkey = vote_init->node_pubkey; + vote_state->authorized_voters = *fd_authorized_voters_new(clock->epoch, &vote_init->authorized_voter, authorized_voters_mem); + vote_state->authorized_withdrawer = vote_init->authorized_withdrawer; + vote_state->inflation_rewards_commission_bps = ((ushort)vote_init->commission) * 100; + vote_state->inflation_rewards_collector = *vote_pubkey; + vote_state->block_revenue_collector = vote_init->node_pubkey; + vote_state->block_revenue_commission_bps = DEFAULT_BLOCK_REVENUE_COMMISSION_BPS; +} + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L576-L595 */ +int +fd_vote_state_v4_set_vote_account_state( fd_exec_instr_ctx_t const * ctx, + fd_borrowed_account_t * vote_account, + fd_vote_state_versioned_t * versioned ) { + /* This is a horrible conditional expression in Agave. + The terms were broken up into their own variables. */ + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L582-L586 */ + fd_rent_t const rent = fd_sysvar_cache_rent_read_nofail( ctx->sysvar_cache ); + int resize_needed = fd_borrowed_account_get_data_len( vote_account ) < FD_VOTE_STATE_V4_SZ; + int resize_rent_exempt = fd_rent_exempt_minimum_balance( &rent, FD_VOTE_STATE_V4_SZ ) <= fd_borrowed_account_get_lamports( vote_account ); + + /* The resize operation itself is part of the horrible conditional, + but behind a short-circuit operator. */ + int resize_failed = 0; + if( resize_needed && resize_rent_exempt ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L584-L586 */ + resize_failed = + fd_borrowed_account_set_data_length( vote_account, FD_VOTE_STATE_V4_SZ ) != FD_EXECUTOR_INSTR_SUCCESS; + } + + if( FD_UNLIKELY( resize_needed && ( !resize_rent_exempt || resize_failed ) ) ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L590 */ + return FD_EXECUTOR_INSTR_ERR_ACC_NOT_RENT_EXEMPT; + } + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L593 */ + return fd_vsv_set_state( vote_account, versioned ); +} + +int +fd_vote_state_v4_get_and_update_authorized_voter( fd_vote_state_v4_t * self, + ulong current_epoch, + fd_pubkey_t ** pubkey /* out */ ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L327-L330 */ + fd_vote_authorized_voter_t * authorized_voter = + fd_authorized_voters_get_and_cache_authorized_voter_for_epoch( &self->authorized_voters, + current_epoch ); + if( FD_UNLIKELY( !authorized_voter ) ) return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; + *pubkey = &authorized_voter->pubkey; + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L331-L332 */ + fd_authorized_voters_purge_authorized_voters( &self->authorized_voters, fd_ulong_sat_sub( current_epoch, 1UL ) ); + return FD_EXECUTOR_INSTR_SUCCESS; +} + +int +fd_vote_state_v4_set_new_authorized_voter( fd_exec_instr_ctx_t * ctx, + fd_vote_state_v4_t * self, + fd_pubkey_t const * authorized_pubkey, + ulong current_epoch, + ulong target_epoch, + /* "verify" closure */ int authorized_withdrawer_signer, + /* "verify" closure */ fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ) { + int rc; + fd_pubkey_t * epoch_authorized_voter = NULL; + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L462 */ + rc = fd_vote_state_v4_get_and_update_authorized_voter( self, current_epoch, &epoch_authorized_voter ); + if( FD_UNLIKELY( rc ) ) return rc; + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L463 */ + rc = fd_vote_signature_verify( epoch_authorized_voter, authorized_withdrawer_signer, signers ); + if( FD_UNLIKELY( rc ) ) return rc; + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L470-L472 */ + if( FD_UNLIKELY( fd_authorized_voters_contains( &self->authorized_voters, target_epoch ) ) ) { + ctx->txn_out->err.custom_err = FD_VOTE_ERR_TOO_SOON_TO_REAUTHORIZE; + return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; + } + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L474-L475 */ + if( 0 == fd_vote_authorized_voters_pool_free( self->authorized_voters.pool) ) { + FD_LOG_ERR(( "Authorized_voter pool is empty" )); + } + + fd_vote_authorized_voter_t * ele = + fd_vote_authorized_voters_pool_ele_acquire( self->authorized_voters.pool ); + ele->epoch = target_epoch; + ele->pubkey = *authorized_pubkey; + ele->prio = (ulong)&ele->pubkey; + fd_vote_authorized_voters_treap_ele_insert( + self->authorized_voters.treap, ele, self->authorized_voters.pool ); + + return 0; +} diff --git a/src/flamenco/runtime/program/vote/fd_vote_state_v4.h b/src/flamenco/runtime/program/vote/fd_vote_state_v4.h new file mode 100644 index 00000000000..f552a52e856 --- /dev/null +++ b/src/flamenco/runtime/program/vote/fd_vote_state_v4.h @@ -0,0 +1,40 @@ +#ifndef HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_state_v4_h +#define HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_state_v4_h + +#include "../../fd_borrowed_account.h" +#include "../../fd_executor.h" +#include "../../../types/fd_types.h" +#include "../../sysvar/fd_sysvar.h" + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L600-L619 */ +void +fd_vote_state_v4_create_new( fd_pubkey_t const * vote_pubkey, + fd_vote_init_t const * vote_init, + fd_sol_sysvar_clock_t const * clock, + uchar * authorized_voters_mem, + fd_vote_state_versioned_t * versioned /* out */ ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L576-L595 */ +int +fd_vote_state_v4_set_vote_account_state( fd_exec_instr_ctx_t const * ctx, + fd_borrowed_account_t * vote_account, + fd_vote_state_versioned_t * versioned ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L323-L334 */ +int +fd_vote_state_v4_get_and_update_authorized_voter( fd_vote_state_v4_t * self, + ulong current_epoch, + fd_pubkey_t ** pubkey /* out */ ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L450-L478 */ +int +fd_vote_state_v4_set_new_authorized_voter( fd_exec_instr_ctx_t * ctx, + fd_vote_state_v4_t * self, + fd_pubkey_t const * authorized_pubkey, + ulong current_epoch, + ulong target_epoch, + /* "verify" closure */ int authorized_withdrawer_signer, + /* "verify" closure */ fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ); + +#endif /* HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_state_v4_h */ + diff --git a/src/flamenco/runtime/program/vote/fd_vote_state_versioned.c b/src/flamenco/runtime/program/vote/fd_vote_state_versioned.c new file mode 100644 index 00000000000..e542daa2535 --- /dev/null +++ b/src/flamenco/runtime/program/vote/fd_vote_state_versioned.c @@ -0,0 +1,682 @@ +#include "../fd_vote_program.h" +#include "fd_vote_state_versioned.h" +#include "fd_vote_common.h" +#include "fd_vote_lockout.h" +#include "fd_vote_state_v3.h" +#include "fd_vote_state_v4.h" +#include "fd_authorized_voters.h" +#include "../../fd_runtime.h" + + +/***** +TODO: + * Use const in all the getters (see fd_vsv_get_root_slot) + * Update permalinks + * Consistency in switch statements (use FD LOG ERR instead of builtin unreachable?) + * Rename prefix for static methods to fd_vsv +*/ + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L780-L785 */ +static inline fd_vote_lockout_t * +last_lockout( fd_vote_state_versioned_t * self ) { + fd_landed_vote_t * votes = NULL; + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_current: + votes = self->inner.current.votes; + break; + case fd_vote_state_versioned_enum_v4: + votes = self->inner.v4.votes; + break; + default: + __builtin_unreachable(); + } + + if( deq_fd_landed_vote_t_empty( votes ) ) return NULL; + fd_landed_vote_t * last_vote = deq_fd_landed_vote_t_peek_tail( votes ); + return &last_vote->lockout; +} + +/**********************************************************************/ +/* Getters */ +/**********************************************************************/ + +/* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1074 */ +int +fd_vsv_get_state( fd_txn_account_t const * self, + uchar * res ) { + + fd_bincode_decode_ctx_t decode = { + .data = fd_txn_account_get_data( self ), + .dataend = fd_txn_account_get_data( self ) + fd_txn_account_get_data_len( self ), + }; + + ulong total_sz = 0UL; + int err = fd_vote_state_versioned_decode_footprint( &decode, &total_sz ); + if( FD_UNLIKELY( err ) ) { + return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; + } + + FD_TEST( total_sz<=FD_VOTE_STATE_VERSIONED_FOOTPRINT ); + + fd_vote_state_versioned_decode( res, &decode ); + + return FD_EXECUTOR_INSTR_SUCCESS; +} + +/* Returns a const pointer to the authorized withdrawer for the + appropriate vote state version.*/ +fd_pubkey_t const * +fd_vsv_get_authorized_withdrawer( fd_vote_state_versioned_t * self ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_v0_23_5: + return &self->inner.v0_23_5.authorized_withdrawer; + case fd_vote_state_versioned_enum_v1_14_11: + return &self->inner.v1_14_11.authorized_withdrawer; + case fd_vote_state_versioned_enum_current: + return &self->inner.current.authorized_withdrawer; + case fd_vote_state_versioned_enum_v4: + return &self->inner.v4.authorized_withdrawer; + default: + __builtin_unreachable(); + } +} + +uchar +fd_vsv_get_commission( fd_vote_state_versioned_t * self ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_current: + return self->inner.current.commission; + case fd_vote_state_versioned_enum_v4: + return (uchar)(self->inner.v4.inflation_rewards_commission_bps/100); + default: + __builtin_unreachable(); + } +} + +fd_vote_epoch_credits_t const * +fd_vsv_get_epoch_credits( fd_vote_state_versioned_t * self ) { + return fd_vsv_get_epoch_credits_mutable( self ); +} + +fd_landed_vote_t const * +fd_vsv_get_votes( fd_vote_state_versioned_t * self ) { + return fd_vsv_get_votes_mutable( self ); +} + +ulong const * +fd_vsv_get_last_voted_slot( fd_vote_state_versioned_t * self ) { + fd_vote_lockout_t * last_lockout_ = last_lockout( self ); + if( FD_UNLIKELY( !last_lockout_ ) ) return NULL; + return &last_lockout_->slot; +} + +ulong const * +fd_vsv_get_root_slot( fd_vote_state_versioned_t * self ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_current: + if( !self->inner.current.has_root_slot ) return NULL; + return &self->inner.current.root_slot; + case fd_vote_state_versioned_enum_v4: + if( !self->inner.v4.has_root_slot ) return NULL; + return &self->inner.v4.root_slot; + default: + FD_LOG_ERR(( "unsupported vote state version: %u", self->discriminant )); + } +} + +fd_vote_block_timestamp_t const * +fd_vsv_get_last_timestamp( fd_vote_state_versioned_t * self ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_current: + return &self->inner.current.last_timestamp; + case fd_vote_state_versioned_enum_v4: + return &self->inner.v4.last_timestamp; + default: + __builtin_unreachable(); + } +} + +/**********************************************************************/ +/* Mutable getters */ +/**********************************************************************/ + +fd_vote_epoch_credits_t * +fd_vsv_get_epoch_credits_mutable( fd_vote_state_versioned_t * self ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_current: + return self->inner.current.epoch_credits; + case fd_vote_state_versioned_enum_v4: + return self->inner.v4.epoch_credits; + default: + __builtin_unreachable(); + } +} + +fd_landed_vote_t * +fd_vsv_get_votes_mutable( fd_vote_state_versioned_t * self ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_current: + return self->inner.current.votes; + case fd_vote_state_versioned_enum_v4: + return self->inner.v4.votes; + default: + __builtin_unreachable(); + } +} + +/**********************************************************************/ +/* Setters */ +/**********************************************************************/ + +int +fd_vsv_set_state( fd_borrowed_account_t * self, + fd_vote_state_versioned_t * state ) { + /* https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L974 */ + uchar * data = NULL; + ulong dlen = 0UL; + int err = fd_borrowed_account_get_data_mut( self, &data, &dlen ); + if( FD_UNLIKELY( err ) ) { + return err; + } + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/src/transaction_context.rs#L978 + ulong serialized_size = fd_vote_state_versioned_size( state ); + if( FD_UNLIKELY( serialized_size > dlen ) ) + return FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL; + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/src/transaction_context.rs#L983 + fd_bincode_encode_ctx_t encode = + { .data = data, + .dataend = data + dlen }; + do { + int err = fd_vote_state_versioned_encode( state, &encode ); + if( FD_UNLIKELY( err ) ) FD_LOG_ERR(( "fd_vote_state_versioned_encode failed (%d)", err )); + } while(0); + + return FD_EXECUTOR_INSTR_SUCCESS; +} + +int +fd_vsv_set_vote_account_state( fd_exec_instr_ctx_t const * ctx, + fd_borrowed_account_t * vote_account, + fd_vote_state_versioned_t * versioned, + uchar * vote_lockout_mem ) { + switch( versioned->discriminant ) { + case fd_vote_state_versioned_enum_current: + return fd_vote_state_v3_set_vote_account_state( ctx, vote_account, versioned, vote_lockout_mem ); + case fd_vote_state_versioned_enum_v4: + return fd_vote_state_v4_set_vote_account_state( ctx, vote_account, versioned ); + default: + __builtin_unreachable(); + } +} + +void +fd_vsv_set_authorized_withdrawer( fd_vote_state_versioned_t * self, + fd_pubkey_t const * authorized_withdrawer ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_current: { + self->inner.current.authorized_withdrawer = *authorized_withdrawer; + break; + } + case fd_vote_state_versioned_enum_v4: { + self->inner.v4.authorized_withdrawer = *authorized_withdrawer; + break; + } + default: + __builtin_unreachable(); + } +} + +int +fd_vsv_set_new_authorized_voter( fd_exec_instr_ctx_t * ctx, + fd_vote_state_versioned_t * self, + fd_pubkey_t const * authorized_pubkey, + ulong current_epoch, + ulong target_epoch, + /* "verify" closure */ int authorized_withdrawer_signer, + /* "verify" closure */ fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_current: + return fd_vote_state_v3_set_new_authorized_voter( + ctx, + &self->inner.current, + authorized_pubkey, + current_epoch, + target_epoch, + authorized_withdrawer_signer, + signers + ); + case fd_vote_state_versioned_enum_v4: + return fd_vote_state_v4_set_new_authorized_voter( + ctx, + &self->inner.v4, + authorized_pubkey, + current_epoch, + target_epoch, + authorized_withdrawer_signer, + signers + ); + default: + FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant )); + } +} + +void +fd_vsv_set_node_pubkey( fd_vote_state_versioned_t * self, + fd_pubkey_t const * node_pubkey ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_current: + self->inner.current.node_pubkey = *node_pubkey; + break; + case fd_vote_state_versioned_enum_v4: + self->inner.v4.node_pubkey = *node_pubkey; + break; + } +} + +void +fd_vsv_set_block_revenue_collector( fd_vote_state_versioned_t * self, + fd_pubkey_t const * block_revenue_collector ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_v4: + self->inner.v4.block_revenue_collector = *block_revenue_collector; + break; + } +} + +void +fd_vsv_set_commission( fd_vote_state_versioned_t * self, + uchar commission ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_current: + self->inner.current.commission = commission; + break; + case fd_vote_state_versioned_enum_v4: + self->inner.v4.inflation_rewards_commission_bps = commission*100; + break; + default: + __builtin_unreachable(); + } +} + +void +fd_vsv_set_root_slot( fd_vote_state_versioned_t * self, ulong * root_slot ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_current: + self->inner.current.has_root_slot = (root_slot!=NULL); + if( FD_LIKELY( root_slot ) ) { + self->inner.current.root_slot = *root_slot; + } + break; + case fd_vote_state_versioned_enum_v4: + self->inner.v4.has_root_slot = (root_slot!=NULL); + if( FD_LIKELY( root_slot ) ) { + self->inner.v4.root_slot = *root_slot; + } + break; + } +} + +static void +fd_vsv_set_last_timestamp( fd_vote_state_versioned_t * self, + fd_vote_block_timestamp_t const * last_timestamp ) { + switch( self->discriminant ) { + case fd_vote_state_versioned_enum_current: + self->inner.current.last_timestamp = *last_timestamp; + break; + case fd_vote_state_versioned_enum_v4: + self->inner.v4.last_timestamp = *last_timestamp; + break; + default: + __builtin_unreachable(); + } +} + +/**********************************************************************/ +/* General functions */ +/**********************************************************************/ + +// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L855 +static void +double_lockouts( fd_vote_state_versioned_t * self ) { + fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( self ); + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L856 + ulong stack_depth = deq_fd_landed_vote_t_cnt( votes ); + ulong i = 0; + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L857 + for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( votes ); + !deq_fd_landed_vote_t_iter_done( votes, iter ); + iter = deq_fd_landed_vote_t_iter_next( votes, iter ) ) { + fd_landed_vote_t * v = deq_fd_landed_vote_t_iter_ele( votes, iter ); + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L860 + if( stack_depth > + fd_ulong_checked_add_expect( + i, + (ulong)v->lockout.confirmation_count, + "`confirmation_count` and tower_size should be bounded by `MAX_LOCKOUT_HISTORY`" ) ) + { + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L864 + fd_vote_lockout_increase_confirmation_count( &v->lockout, 1 ); + } + i++; + } +} + +void +fd_vsv_increment_credits( fd_vote_state_versioned_t * self, + ulong epoch, + ulong credits ) { + fd_vote_epoch_credits_t * epoch_credits = fd_vsv_get_epoch_credits_mutable( self ); + + /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L286-L305 */ + if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_empty( epoch_credits ) ) ) { + /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L286-L288 */ + deq_fd_vote_epoch_credits_t_push_tail_wrap( + epoch_credits, + ( fd_vote_epoch_credits_t ){ .epoch = epoch, .credits = 0, .prev_credits = 0 } ); + } else if( FD_LIKELY( epoch != + deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->epoch ) ) { + /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L290 */ + fd_vote_epoch_credits_t * last = deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits ); + + ulong credits = last->credits; + ulong prev_credits = last->prev_credits; + + /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L292-L299 */ + if( FD_LIKELY( credits!=prev_credits ) ) { + if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_cnt( epoch_credits )>=MAX_EPOCH_CREDITS_HISTORY ) ) { + /* Although Agave performs a `.remove(0)` AFTER the call to + `.push()`, there is an edge case where the epoch credits is + full, making the call to `_push_tail()` unsafe. Since Agave's + structures are dynamically allocated, it is safe for them to + simply call `.push()` and then popping afterwards. We have to + reverse the order of operations to maintain correct behavior + and avoid overflowing the deque. + https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L303 */ + deq_fd_vote_epoch_credits_t_pop_head( epoch_credits ); + } + + /* This will not fail because we already popped if we're at + capacity, since the epoch_credits deque is allocated with a + minimum capacity of MAX_EPOCH_CREDITS_HISTORY. */ + deq_fd_vote_epoch_credits_t_push_tail( + epoch_credits, + ( fd_vote_epoch_credits_t ){ + .epoch = epoch, .credits = credits, .prev_credits = credits } ); + } else { + /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L297-L298 */ + deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->epoch = epoch; + + /* Here we can perform the same deque size check and pop if + we're beyond the maximum epoch credits len. */ + if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_cnt( epoch_credits )>MAX_EPOCH_CREDITS_HISTORY ) ) { + deq_fd_vote_epoch_credits_t_pop_head( epoch_credits ); + } + } + } + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L663 + deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->credits = fd_ulong_sat_add( + deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->credits, credits ); +} + +int +fd_vsv_process_timestamp( fd_exec_instr_ctx_t * ctx, + fd_vote_state_versioned_t * self, + ulong slot, + long timestamp ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L160 */ + fd_vote_block_timestamp_t const * last_timestamp = fd_vsv_get_last_timestamp( self ); + if( FD_UNLIKELY( + ( slotslot || timestamptimestamp ) || + ( slot==last_timestamp->slot && + ( slot!=last_timestamp->slot || timestamp!=last_timestamp->timestamp ) && + last_timestamp->slot!=0UL ) ) ) { + ctx->txn_out->err.custom_err = FD_VOTE_ERR_TIMESTAMP_TOO_OLD; + return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; + } + + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L168 */ + fd_vote_block_timestamp_t new_timestamp = { + .slot = slot, + .timestamp = timestamp, + }; + fd_vsv_set_last_timestamp( self, &new_timestamp ); + return FD_EXECUTOR_INSTR_SUCCESS; +} + +void +fd_vsv_pop_expired_votes( fd_vote_state_versioned_t * self, ulong next_vote_slot ) { + fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( self ); + + while( !deq_fd_landed_vote_t_empty( votes ) ) { + fd_landed_vote_t * vote = deq_fd_landed_vote_t_peek_tail( votes ); + if( !( fd_vote_lockout_is_locked_out_at_slot( &vote->lockout, next_vote_slot ) ) ) { + deq_fd_landed_vote_t_pop_tail( votes ); + } else { + break; + } + } +} + +void +fd_vsv_process_next_vote_slot( fd_vote_state_versioned_t * self, + ulong next_vote_slot, + ulong epoch, + ulong current_slot ) { + ulong const * last_voted_slot_ = fd_vsv_get_last_voted_slot( self ); + if( FD_UNLIKELY( last_voted_slot_ && next_vote_slot <= *last_voted_slot_ ) ) return; + + fd_vsv_pop_expired_votes( self, next_vote_slot ); + + fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( self ); + + fd_landed_vote_t landed_vote = { + .latency = fd_vote_compute_vote_latency( next_vote_slot, current_slot ), + .lockout = ( fd_vote_lockout_t ){ .slot = next_vote_slot } + }; + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L623 + if( FD_UNLIKELY( deq_fd_landed_vote_t_cnt( votes ) == MAX_LOCKOUT_HISTORY ) ) { + ulong credits = fd_vote_credits_for_vote_at_index( votes, 0 ); + fd_landed_vote_t landed_vote = deq_fd_landed_vote_t_pop_head( votes ); + fd_vsv_set_root_slot( self, &landed_vote.lockout.slot ); + + fd_vsv_increment_credits( self, epoch, credits ); + } + + // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L634 + deq_fd_landed_vote_t_push_tail_wrap( votes, landed_vote ); + double_lockouts( self ); +} + +int +fd_vsv_try_convert_to_v3( fd_vote_state_versioned_t * self, + uchar * authorized_voters_mem, + uchar * landed_votes_mem ) { + switch( self->discriminant ) { + /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L47-L73 */ + case fd_vote_state_versioned_enum_v0_23_5: { + fd_vote_state_0_23_5_t * state = &self->inner.v0_23_5; + // Check if uninitialized (authorized_voter is all zeros) + int is_uninitialized = 1; + for( ulong i = 0; i < sizeof(fd_pubkey_t); i++ ) { + if( state->authorized_voter.uc[i] != 0 ) { + is_uninitialized = 0; + break; + } + } + + fd_vote_authorized_voters_t * authorized_voters; + if( is_uninitialized ) { + // Create empty AuthorizedVoters (default), initialized but with no entries + authorized_voters = fd_authorized_voters_new_empty( authorized_voters_mem ); + } else { + authorized_voters = fd_authorized_voters_new( + state->authorized_voter_epoch, &state->authorized_voter, authorized_voters_mem ); + } + + /* Temporary to hold current */ + /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L54-L72 */ + fd_vote_state_t current = { + .node_pubkey = state->node_pubkey, + .authorized_withdrawer = state->authorized_withdrawer, + .commission = state->commission, + .votes = fd_vote_landed_votes_from_lockouts( state->votes, landed_votes_mem ), + .has_root_slot = state->has_root_slot, + .root_slot = state->root_slot, + .authorized_voters = *authorized_voters, + .prior_voters = (fd_vote_prior_voters_t) { + .idx = 31UL, + .is_empty = 1, + }, + .epoch_credits = state->epoch_credits, + .last_timestamp = state->last_timestamp, + }; + + /* Emplace new vote state into target */ + self->discriminant = fd_vote_state_versioned_enum_current; + self->inner.current = current; + + return FD_EXECUTOR_INSTR_SUCCESS; + } + /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L75-L91 */ + case fd_vote_state_versioned_enum_v1_14_11: { + fd_vote_state_1_14_11_t * state = &self->inner.v1_14_11; + + /* Temporary to hold current */ + fd_vote_state_t current = { + .node_pubkey = state->node_pubkey, + .authorized_withdrawer = state->authorized_withdrawer, + .commission = state->commission, + .votes = fd_vote_landed_votes_from_lockouts( state->votes, landed_votes_mem ), + .has_root_slot = state->has_root_slot, + .root_slot = state->root_slot, + .authorized_voters = state->authorized_voters, + .prior_voters = state->prior_voters, + .epoch_credits = state->epoch_credits, + .last_timestamp = state->last_timestamp + }; + + /* Emplace new vote state into target */ + self->discriminant = fd_vote_state_versioned_enum_current; + self->inner.current = current; + + return FD_EXECUTOR_INSTR_SUCCESS; + } + /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L93 */ + case fd_vote_state_versioned_enum_current: + return FD_EXECUTOR_INSTR_SUCCESS; + /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L96 */ + case fd_vote_state_versioned_enum_v4: + return FD_EXECUTOR_INSTR_ERR_INVALID_ARG; + default: + __builtin_unreachable(); + } +} + +int +fd_vsv_try_convert_to_v4( fd_vote_state_versioned_t * self, + fd_pubkey_t const * vote_pubkey, + uchar * landed_votes_mem ) { + switch( self->discriminant ) { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L971-L974 */ + case fd_vote_state_versioned_enum_v0_23_5: { + return FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT; + } + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L975-L989 */ + case fd_vote_state_versioned_enum_v1_14_11: { + fd_vote_state_1_14_11_t * state = &self->inner.v1_14_11; + fd_vote_state_v4_t v4 = { + .node_pubkey = state->node_pubkey, + .authorized_withdrawer = state->authorized_withdrawer, + .inflation_rewards_collector = *vote_pubkey, + .block_revenue_collector = state->node_pubkey, + .inflation_rewards_commission_bps = fd_ushort_sat_mul( state->commission, 100 ), + .block_revenue_commission_bps = DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, + .pending_delegator_rewards = 0, + .has_bls_pubkey_compressed = 0, + .votes = fd_vote_landed_votes_from_lockouts( state->votes, landed_votes_mem ), + .has_root_slot = state->has_root_slot, + .root_slot = state->root_slot, + .authorized_voters = state->authorized_voters, + .epoch_credits = state->epoch_credits, + .last_timestamp = state->last_timestamp + }; + + /* Emplace new vote state into target */ + self->discriminant = fd_vote_state_versioned_enum_v4; + self->inner.v4 = v4; + + return FD_EXECUTOR_INSTR_SUCCESS; + } + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L990-L1004 */ + case fd_vote_state_versioned_enum_current: { + fd_vote_state_t * state = &self->inner.current; + fd_vote_state_v4_t v4 = { + .node_pubkey = state->node_pubkey, + .authorized_withdrawer = state->authorized_withdrawer, + .inflation_rewards_collector = *vote_pubkey, + .block_revenue_collector = state->node_pubkey, + .inflation_rewards_commission_bps = fd_ushort_sat_mul( state->commission, 100 ), + .block_revenue_commission_bps = DEFAULT_BLOCK_REVENUE_COMMISSION_BPS, + .pending_delegator_rewards = 0, + .has_bls_pubkey_compressed = 0, + .votes = state->votes, + .has_root_slot = state->has_root_slot, + .root_slot = state->root_slot, + .authorized_voters = state->authorized_voters, + .epoch_credits = state->epoch_credits, + .last_timestamp = state->last_timestamp + }; + + /* Emplace new vote state into target */ + self->discriminant = fd_vote_state_versioned_enum_v4; + self->inner.v4 = v4; + + return FD_EXECUTOR_INSTR_SUCCESS; + } + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L1005 */ + case fd_vote_state_versioned_enum_v4: + return FD_EXECUTOR_INSTR_SUCCESS; + default: + __builtin_unreachable(); + } +} + +int +fd_vsv_deinitialize_vote_account_state( fd_exec_instr_ctx_t * ctx, + fd_borrowed_account_t * vote_account, + int target_version, + uchar * vote_lockout_mem ) { + switch( target_version ) { + case VOTE_STATE_TARGET_VERSION_V3: { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L878 */ + fd_vote_state_versioned_t versioned; + fd_vote_state_versioned_new_disc( &versioned, fd_vote_state_versioned_enum_current ); + versioned.inner.current.prior_voters.idx = 31; + versioned.inner.current.prior_voters.is_empty = 1; + return fd_vote_state_v3_set_vote_account_state( + ctx, + vote_account, + &versioned, + vote_lockout_mem + ); + } + case VOTE_STATE_TARGET_VERSION_V4: { + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L881-L883 */ + uchar * data; + ulong dlen; + int rc = fd_borrowed_account_get_data_mut( vote_account, &data, &dlen ); + if( FD_UNLIKELY( rc ) ) return rc; + fd_memset( data, 0, dlen ); + return FD_EXECUTOR_INSTR_SUCCESS; + } + default: + FD_LOG_ERR(( "unsupported target version" )); + } +} + + diff --git a/src/flamenco/runtime/program/vote/fd_vote_state_versioned.h b/src/flamenco/runtime/program/vote/fd_vote_state_versioned.h new file mode 100644 index 00000000000..522a2719992 --- /dev/null +++ b/src/flamenco/runtime/program/vote/fd_vote_state_versioned.h @@ -0,0 +1,149 @@ +#ifndef HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_state_versioned_h +#define HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_state_versioned_h + +#include "../../fd_borrowed_account.h" +#include "../../../types/fd_types.h" + +/**********************************************************************/ +/* Getters */ +/**********************************************************************/ + +/* https://github.com/anza-xyz/agave/blob/v2.0.1/programs/vote/src/vote_state/mod.rs#L1074 */ +int +fd_vsv_get_state( fd_txn_account_t const * self, + uchar * res ); + +fd_pubkey_t const * +fd_vsv_get_authorized_withdrawer( fd_vote_state_versioned_t * self ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L717-L722 */ +uchar +fd_vsv_get_commission( fd_vote_state_versioned_t * self ); + +fd_vote_epoch_credits_t const * +fd_vsv_get_epoch_credits( fd_vote_state_versioned_t * self ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L752-L757 */ +fd_landed_vote_t const * +fd_vsv_get_votes( fd_vote_state_versioned_t * self ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L787-L792 */ +ulong const * +fd_vsv_get_last_voted_slot( fd_vote_state_versioned_t * self ); + +ulong const * +fd_vsv_get_root_slot( fd_vote_state_versioned_t * self ); + +fd_vote_block_timestamp_t const * +fd_vsv_get_last_timestamp( fd_vote_state_versioned_t * self ); + +/**********************************************************************/ +/* Mutable getters */ +/**********************************************************************/ + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L815-L820 */ +fd_vote_epoch_credits_t * +fd_vsv_get_epoch_credits_mutable( fd_vote_state_versioned_t * self ); + +fd_landed_vote_t * +fd_vsv_get_votes_mutable( fd_vote_state_versioned_t * self ); + +/**********************************************************************/ +/* Setters */ +/**********************************************************************/ + +int +fd_vsv_set_state( fd_borrowed_account_t * self, + fd_vote_state_versioned_t * state ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L673-L678 */ +void +fd_vsv_set_authorized_withdrawer( fd_vote_state_versioned_t * self, + fd_pubkey_t const * authorized_withdrawer ); + +/* Sets the authorized withdrawer for the appropriate vote state + version. Only supported for v3 and v4 vote states. */ +int +fd_vsv_set_new_authorized_voter( fd_exec_instr_ctx_t * ctx, + fd_vote_state_versioned_t * self, + fd_pubkey_t const * authorized_pubkey, + ulong current_epoch, + ulong target_epoch, + /* "verify" closure */ int authorized_withdrawer_signer, + /* "verify" closure */ fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L738-L743 */ +void +fd_vsv_set_node_pubkey( fd_vote_state_versioned_t * self, + fd_pubkey_t const * node_pubkey ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L745-L750 */ +void +fd_vsv_set_block_revenue_collector( fd_vote_state_versioned_t * self, + fd_pubkey_t const * block_revenue_collector ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L724-L729 */ +void +fd_vsv_set_commission( fd_vote_state_versioned_t * self, + uchar commission ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L386-L388 */ +void +fd_vsv_set_root_slot( fd_vote_state_versioned_t * self, ulong * root_slot ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L843-L851 */ +int +fd_vsv_set_vote_account_state( fd_exec_instr_ctx_t const * ctx, + fd_borrowed_account_t * vote_account, + fd_vote_state_versioned_t * versioned, + uchar * vote_lockout_mem ); + +/**********************************************************************/ +/* General functions */ +/**********************************************************************/ + +/* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L282-L309 */ +void +fd_vsv_increment_credits( fd_vote_state_versioned_t * self, + ulong epoch, + ulong credits ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L159-L170 */ +int +fd_vsv_process_timestamp( fd_exec_instr_ctx_t * ctx, + fd_vote_state_versioned_t * self, + ulong slot, + long timestamp ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L172-L180 */ +void +fd_vsv_pop_expired_votes( fd_vote_state_versioned_t * self, ulong next_vote_slot ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L638-L651 */ +void +fd_vsv_process_next_vote_slot( fd_vote_state_versioned_t * self, + ulong next_vote_slot, + ulong epoch, + ulong current_slot ); + +/* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L40-L98 */ +int +fd_vsv_try_convert_to_v3( fd_vote_state_versioned_t * self, + uchar * authorized_voters_mem, + uchar * landed_votes_mem ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L966-L1007 */ +int +fd_vsv_try_convert_to_v4( fd_vote_state_versioned_t * self, + fd_pubkey_t const * vote_pubkey, + uchar * landed_votes_mem ); + +/* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L872C12-L886 */ +int +fd_vsv_deinitialize_vote_account_state( fd_exec_instr_ctx_t * ctx, + fd_borrowed_account_t * vote_account, + int target_version, + uchar * vote_lockout_mem ); + +#endif /* HEADER_fd_src_flamenco_runtime_program_vote_fd_vote_state_versioned_h */ + diff --git a/src/flamenco/stakes/fd_vote_states.c b/src/flamenco/stakes/fd_vote_states.c index fe4b6d40203..cb6a97d6842 100644 --- a/src/flamenco/stakes/fd_vote_states.c +++ b/src/flamenco/stakes/fd_vote_states.c @@ -292,6 +292,12 @@ fd_vote_states_update_from_account( fd_vote_states_t * vote_states, last_vote_timestamp = vsv->inner.current.last_timestamp.timestamp; last_vote_slot = vsv->inner.current.last_timestamp.slot; break; + case fd_vote_state_versioned_enum_v4: + node_account = vsv->inner.v4.node_pubkey; + commission = (uchar)fd_ushort_min( vsv->inner.v4.inflation_rewards_commission_bps/100, UCHAR_MAX ); + last_vote_timestamp = vsv->inner.v4.last_timestamp.timestamp; + last_vote_slot = vsv->inner.v4.last_timestamp.slot; + break; default: __builtin_unreachable(); } diff --git a/src/flamenco/types/fd_fuzz_types.h b/src/flamenco/types/fd_fuzz_types.h index dbc2ab2800f..ce728c491e6 100644 --- a/src/flamenco/types/fd_fuzz_types.h +++ b/src/flamenco/types/fd_fuzz_types.h @@ -465,6 +465,14 @@ void *fd_landed_vote_generate( void *mem, void **alloc_mem, fd_rng_t * rng ) { return mem; } +void *fd_bls_pubkey_compressed_generate( void *mem, void **alloc_mem, fd_rng_t * rng ) { + fd_bls_pubkey_compressed_t *self = (fd_bls_pubkey_compressed_t *) mem; + *alloc_mem = (uchar *) *alloc_mem + sizeof(fd_bls_pubkey_compressed_t); + fd_bls_pubkey_compressed_new(mem); + LLVMFuzzerMutate( self->buf, 48, 48 ); + return mem; +} + void *fd_vote_state_0_23_5_generate( void *mem, void **alloc_mem, fd_rng_t * rng ) { fd_vote_state_0_23_5_t *self = (fd_vote_state_0_23_5_t *) mem; *alloc_mem = (uchar *) *alloc_mem + sizeof(fd_vote_state_0_23_5_t); @@ -586,6 +594,48 @@ void *fd_vote_state_generate( void *mem, void **alloc_mem, fd_rng_t * rng ) { return mem; } +void *fd_vote_state_v4_generate( void *mem, void **alloc_mem, fd_rng_t * rng ) { + fd_vote_state_v4_t *self = (fd_vote_state_v4_t *) mem; + *alloc_mem = (uchar *) *alloc_mem + sizeof(fd_vote_state_v4_t); + fd_vote_state_v4_new(mem); + fd_pubkey_generate( &self->node_pubkey, alloc_mem, rng ); + fd_pubkey_generate( &self->authorized_withdrawer, alloc_mem, rng ); + fd_pubkey_generate( &self->inflation_rewards_collector, alloc_mem, rng ); + fd_pubkey_generate( &self->block_revenue_collector, alloc_mem, rng ); + self->inflation_rewards_commission_bps = fd_rng_ushort( rng ); + self->block_revenue_commission_bps = fd_rng_ushort( rng ); + self->pending_delegator_rewards = fd_rng_ulong( rng ); + { + self->has_bls_pubkey_compressed = fd_rng_uchar( rng ) % 2; + if( self->has_bls_pubkey_compressed ) { + fd_bls_pubkey_compressed_generate( &self->bls_pubkey_compressed, alloc_mem, rng ); + } + } + ulong votes_len = fd_rng_ulong( rng ) % 8; + ulong votes_max = fd_ulong_max( votes_len, 32 ); + self->votes = deq_fd_landed_vote_t_join_new( alloc_mem, votes_max ); + for( ulong i=0; i < votes_len; i++ ) { + fd_landed_vote_t * elem = deq_fd_landed_vote_t_push_tail_nocopy( self->votes ); + fd_landed_vote_generate( elem, alloc_mem, rng ); + } + { + self->has_root_slot = fd_rng_uchar( rng ) % 2; + if( self->has_root_slot ) { + LLVMFuzzerMutate( (uchar *)&(self->root_slot), sizeof(ulong), sizeof(ulong) ); + } + } + fd_vote_authorized_voters_generate( &self->authorized_voters, alloc_mem, rng ); + ulong epoch_credits_len = fd_rng_ulong( rng ) % 8; + ulong epoch_credits_max = fd_ulong_max( epoch_credits_len, 64 ); + self->epoch_credits = deq_fd_vote_epoch_credits_t_join_new( alloc_mem, epoch_credits_max ); + for( ulong i=0; i < epoch_credits_len; i++ ) { + fd_vote_epoch_credits_t * elem = deq_fd_vote_epoch_credits_t_push_tail_nocopy( self->epoch_credits ); + fd_vote_epoch_credits_generate( elem, alloc_mem, rng ); + } + fd_vote_block_timestamp_generate( &self->last_timestamp, alloc_mem, rng ); + return mem; +} + void fd_vote_state_versioned_inner_generate( fd_vote_state_versioned_inner_t * self, void **alloc_mem, uint discriminant, fd_rng_t * rng ) { switch (discriminant) { case 0: { @@ -600,13 +650,17 @@ void fd_vote_state_versioned_inner_generate( fd_vote_state_versioned_inner_t * s fd_vote_state_generate( &self->current, alloc_mem, rng ); break; } + case 3: { + fd_vote_state_v4_generate( &self->v4, alloc_mem, rng ); + break; + } } } void *fd_vote_state_versioned_generate( void *mem, void **alloc_mem, fd_rng_t * rng ) { fd_vote_state_versioned_t *self = (fd_vote_state_versioned_t *) mem; *alloc_mem = (uchar *) *alloc_mem + sizeof(fd_vote_state_versioned_t); fd_vote_state_versioned_new(mem); - self->discriminant = fd_rng_uint( rng ) % 3; + self->discriminant = fd_rng_uint( rng ) % 4; fd_vote_state_versioned_inner_generate( &self->inner, alloc_mem, self->discriminant, rng ); return mem; } diff --git a/src/flamenco/types/fd_types.c b/src/flamenco/types/fd_types.c index dd1bc5f8410..d9811eb1119 100644 --- a/src/flamenco/types/fd_types.c +++ b/src/flamenco/types/fd_types.c @@ -2437,6 +2437,35 @@ void fd_landed_vote_walk( void * w, fd_landed_vote_t const * self, fd_types_walk fd_vote_lockout_walk( w, &self->lockout, fun, "lockout", level, 0 ); fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_landed_vote", level--, 0 ); } +int fd_bls_pubkey_compressed_encode( fd_bls_pubkey_compressed_t const * self, fd_bincode_encode_ctx_t * ctx ) { + int err; + err = fd_bincode_bytes_encode( self->buf, 48, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +static inline int fd_bls_pubkey_compressed_decode_footprint_inner( fd_bincode_decode_ctx_t * ctx, ulong * total_sz ) { + if( (ulong)ctx->data + 48UL > (ulong)ctx->dataend ) { return FD_BINCODE_ERR_OVERFLOW; }; + ctx->data = (void *)( (ulong)ctx->data + 48UL ); + return 0; +} +static void fd_bls_pubkey_compressed_decode_inner( void * struct_mem, void * * alloc_mem, fd_bincode_decode_ctx_t * ctx ) { + fd_bls_pubkey_compressed_t * self = (fd_bls_pubkey_compressed_t *)struct_mem; + fd_bincode_bytes_decode_unsafe( self->buf, 48, ctx ); +} +void * fd_bls_pubkey_compressed_decode( void * mem, fd_bincode_decode_ctx_t * ctx ) { + fd_bls_pubkey_compressed_t * self = (fd_bls_pubkey_compressed_t *)mem; + fd_bls_pubkey_compressed_new( self ); + void * alloc_region = (uchar *)mem + sizeof(fd_bls_pubkey_compressed_t); + void * * alloc_mem = &alloc_region; + fd_bls_pubkey_compressed_decode_inner( mem, alloc_mem, ctx ); + return self; +} +void fd_bls_pubkey_compressed_walk( void * w, fd_bls_pubkey_compressed_t const * self, fd_types_walk_fn_t fun, const char *name, uint level, uint varint ) { + (void) varint; + fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_bls_pubkey_compressed", level++, 0 ); + fun(w, self->buf, "buf", FD_FLAMENCO_TYPE_UCHAR, "uchar", level, 0 ); + fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_bls_pubkey_compressed", level--, 0 ); +} int fd_vote_state_0_23_5_encode( fd_vote_state_0_23_5_t const * self, fd_bincode_encode_ctx_t * ctx ) { int err; err = fd_pubkey_encode( &self->node_pubkey, ctx ); @@ -3234,6 +3263,291 @@ ulong fd_vote_state_size( fd_vote_state_t const * self ) { return size; } +int fd_vote_state_v4_encode( fd_vote_state_v4_t const * self, fd_bincode_encode_ctx_t * ctx ) { + int err; + err = fd_pubkey_encode( &self->node_pubkey, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_pubkey_encode( &self->authorized_withdrawer, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_pubkey_encode( &self->inflation_rewards_collector, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_pubkey_encode( &self->block_revenue_collector, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint16_encode( self->inflation_rewards_commission_bps, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint16_encode( self->block_revenue_commission_bps, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->pending_delegator_rewards, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_bool_encode( self->has_bls_pubkey_compressed, ctx ); + if( FD_UNLIKELY( err ) ) return err; + if( self->has_bls_pubkey_compressed ) { + err = fd_bls_pubkey_compressed_encode( &self->bls_pubkey_compressed, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + if( self->votes ) { + ulong votes_len = deq_fd_landed_vote_t_cnt( self->votes ); + err = fd_bincode_uint64_encode( votes_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( self->votes ); !deq_fd_landed_vote_t_iter_done( self->votes, iter ); iter = deq_fd_landed_vote_t_iter_next( self->votes, iter ) ) { + fd_landed_vote_t const * ele = deq_fd_landed_vote_t_iter_ele_const( self->votes, iter ); + err = fd_landed_vote_encode( ele, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + } else { + ulong votes_len = 0; + err = fd_bincode_uint64_encode( votes_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + err = fd_bincode_bool_encode( self->has_root_slot, ctx ); + if( FD_UNLIKELY( err ) ) return err; + if( self->has_root_slot ) { + err = fd_bincode_uint64_encode( self->root_slot, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + err = fd_vote_authorized_voters_encode( &self->authorized_voters, ctx ); + if( FD_UNLIKELY( err ) ) return err; + if( self->epoch_credits ) { + ulong epoch_credits_len = deq_fd_vote_epoch_credits_t_cnt( self->epoch_credits ); + err = fd_bincode_uint64_encode( epoch_credits_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( self->epoch_credits ); !deq_fd_vote_epoch_credits_t_iter_done( self->epoch_credits, iter ); iter = deq_fd_vote_epoch_credits_t_iter_next( self->epoch_credits, iter ) ) { + fd_vote_epoch_credits_t const * ele = deq_fd_vote_epoch_credits_t_iter_ele_const( self->epoch_credits, iter ); + err = fd_vote_epoch_credits_encode( ele, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + } else { + ulong epoch_credits_len = 0; + err = fd_bincode_uint64_encode( epoch_credits_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + err = fd_vote_block_timestamp_encode( &self->last_timestamp, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +static int fd_vote_state_v4_decode_footprint_inner( fd_bincode_decode_ctx_t * ctx, ulong * total_sz ) { + if( ctx->data>=ctx->dataend ) { return FD_BINCODE_ERR_OVERFLOW; }; + int err = 0; + err = fd_pubkey_decode_footprint_inner( ctx, total_sz ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_pubkey_decode_footprint_inner( ctx, total_sz ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_pubkey_decode_footprint_inner( ctx, total_sz ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_pubkey_decode_footprint_inner( ctx, total_sz ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint16_decode_footprint( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_bincode_uint16_decode_footprint( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_bincode_uint64_decode_footprint( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + { + uchar o; + err = fd_bincode_bool_decode( &o, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( o ) { + err = fd_bls_pubkey_compressed_decode_footprint_inner( ctx, total_sz ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } + } + ulong votes_len; + err = fd_bincode_uint64_decode( &votes_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + ulong votes_max = fd_ulong_max( votes_len, 32 ); + *total_sz += deq_fd_landed_vote_t_align() + deq_fd_landed_vote_t_footprint( votes_max ); + ulong votes_sz; + if( FD_UNLIKELY( __builtin_umull_overflow( votes_len, 13, &votes_sz ) ) ) return FD_BINCODE_ERR_UNDERFLOW; + err = fd_bincode_bytes_decode_footprint( votes_sz, ctx ); + if( FD_UNLIKELY( err ) ) return err; + { + uchar o; + err = fd_bincode_bool_decode( &o, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( o ) { + err = fd_bincode_uint64_decode_footprint( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } + } + err = fd_vote_authorized_voters_decode_footprint_inner( ctx, total_sz ); + if( FD_UNLIKELY( err ) ) return err; + ulong epoch_credits_len; + err = fd_bincode_uint64_decode( &epoch_credits_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + ulong epoch_credits_max = fd_ulong_max( epoch_credits_len, 64 ); + *total_sz += deq_fd_vote_epoch_credits_t_align() + deq_fd_vote_epoch_credits_t_footprint( epoch_credits_max ); + ulong epoch_credits_sz; + if( FD_UNLIKELY( __builtin_umull_overflow( epoch_credits_len, 24, &epoch_credits_sz ) ) ) return FD_BINCODE_ERR_UNDERFLOW; + err = fd_bincode_bytes_decode_footprint( epoch_credits_sz, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_vote_block_timestamp_decode_footprint_inner( ctx, total_sz ); + if( FD_UNLIKELY( err ) ) return err; + return 0; +} +int fd_vote_state_v4_decode_footprint( fd_bincode_decode_ctx_t * ctx, ulong * total_sz ) { + *total_sz += sizeof(fd_vote_state_v4_t); + void const * start_data = ctx->data; + int err = fd_vote_state_v4_decode_footprint_inner( ctx, total_sz ); + if( ctx->data>ctx->dataend ) { return FD_BINCODE_ERR_OVERFLOW; }; + ctx->data = start_data; + return err; +} +static void fd_vote_state_v4_decode_inner( void * struct_mem, void * * alloc_mem, fd_bincode_decode_ctx_t * ctx ) { + fd_vote_state_v4_t * self = (fd_vote_state_v4_t *)struct_mem; + fd_pubkey_decode_inner( &self->node_pubkey, alloc_mem, ctx ); + fd_pubkey_decode_inner( &self->authorized_withdrawer, alloc_mem, ctx ); + fd_pubkey_decode_inner( &self->inflation_rewards_collector, alloc_mem, ctx ); + fd_pubkey_decode_inner( &self->block_revenue_collector, alloc_mem, ctx ); + fd_bincode_uint16_decode_unsafe( &self->inflation_rewards_commission_bps, ctx ); + fd_bincode_uint16_decode_unsafe( &self->block_revenue_commission_bps, ctx ); + fd_bincode_uint64_decode_unsafe( &self->pending_delegator_rewards, ctx ); + { + uchar o; + fd_bincode_bool_decode_unsafe( &o, ctx ); + self->has_bls_pubkey_compressed = !!o; + if( o ) { + fd_bls_pubkey_compressed_new( &self->bls_pubkey_compressed ); + fd_bls_pubkey_compressed_decode_inner( &self->bls_pubkey_compressed, alloc_mem, ctx ); + } + } + ulong votes_len; + fd_bincode_uint64_decode_unsafe( &votes_len, ctx ); + ulong votes_max = fd_ulong_max( votes_len, 32 ); + self->votes = deq_fd_landed_vote_t_join_new( alloc_mem, votes_max ); + for( ulong i=0; i < votes_len; i++ ) { + fd_landed_vote_t * elem = deq_fd_landed_vote_t_push_tail_nocopy( self->votes ); + fd_landed_vote_new( elem ); + fd_landed_vote_decode_inner( elem, alloc_mem, ctx ); + } + { + uchar o; + fd_bincode_bool_decode_unsafe( &o, ctx ); + self->has_root_slot = !!o; + if( o ) { + fd_bincode_uint64_decode_unsafe( &self->root_slot, ctx ); + } + } + fd_vote_authorized_voters_decode_inner( &self->authorized_voters, alloc_mem, ctx ); + ulong epoch_credits_len; + fd_bincode_uint64_decode_unsafe( &epoch_credits_len, ctx ); + ulong epoch_credits_max = fd_ulong_max( epoch_credits_len, 64 ); + self->epoch_credits = deq_fd_vote_epoch_credits_t_join_new( alloc_mem, epoch_credits_max ); + for( ulong i=0; i < epoch_credits_len; i++ ) { + fd_vote_epoch_credits_t * elem = deq_fd_vote_epoch_credits_t_push_tail_nocopy( self->epoch_credits ); + fd_vote_epoch_credits_new( elem ); + fd_vote_epoch_credits_decode_inner( elem, alloc_mem, ctx ); + } + fd_vote_block_timestamp_decode_inner( &self->last_timestamp, alloc_mem, ctx ); +} +void * fd_vote_state_v4_decode( void * mem, fd_bincode_decode_ctx_t * ctx ) { + fd_vote_state_v4_t * self = (fd_vote_state_v4_t *)mem; + fd_vote_state_v4_new( self ); + void * alloc_region = (uchar *)mem + sizeof(fd_vote_state_v4_t); + void * * alloc_mem = &alloc_region; + fd_vote_state_v4_decode_inner( mem, alloc_mem, ctx ); + return self; +} +void fd_vote_state_v4_new(fd_vote_state_v4_t * self) { + fd_memset( self, 0, sizeof(fd_vote_state_v4_t) ); + fd_pubkey_new( &self->node_pubkey ); + fd_pubkey_new( &self->authorized_withdrawer ); + fd_pubkey_new( &self->inflation_rewards_collector ); + fd_pubkey_new( &self->block_revenue_collector ); + fd_vote_authorized_voters_new( &self->authorized_voters ); + fd_vote_block_timestamp_new( &self->last_timestamp ); +} +void fd_vote_state_v4_walk( void * w, fd_vote_state_v4_t const * self, fd_types_walk_fn_t fun, const char *name, uint level, uint varint ) { + (void) varint; + fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_vote_state_v4", level++, 0 ); + fd_pubkey_walk( w, &self->node_pubkey, fun, "node_pubkey", level, 0 ); + fd_pubkey_walk( w, &self->authorized_withdrawer, fun, "authorized_withdrawer", level, 0 ); + fd_pubkey_walk( w, &self->inflation_rewards_collector, fun, "inflation_rewards_collector", level, 0 ); + fd_pubkey_walk( w, &self->block_revenue_collector, fun, "block_revenue_collector", level, 0 ); + fun( w, &self->inflation_rewards_commission_bps, "inflation_rewards_commission_bps", FD_FLAMENCO_TYPE_USHORT, "ushort", level, 0 ); + fun( w, &self->block_revenue_commission_bps, "block_revenue_commission_bps", FD_FLAMENCO_TYPE_USHORT, "ushort", level, 0 ); + fun( w, &self->pending_delegator_rewards, "pending_delegator_rewards", FD_FLAMENCO_TYPE_ULONG, "ulong", level, 0 ); + if( !self->has_bls_pubkey_compressed ) { + fun( w, NULL, "bls_pubkey_compressed", FD_FLAMENCO_TYPE_NULL, "bls_pubkey_compressed", level, 0 ); + } else { + fd_bls_pubkey_compressed_walk( w, &self->bls_pubkey_compressed, fun, "bls_pubkey_compressed", level, 0 ); + } + + /* Walk deque */ + fun( w, self->votes, "votes", FD_FLAMENCO_TYPE_ARR, "votes", level++, 0 ); + if( self->votes ) { + for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( self->votes ); + !deq_fd_landed_vote_t_iter_done( self->votes, iter ); + iter = deq_fd_landed_vote_t_iter_next( self->votes, iter ) ) { + fd_landed_vote_t * ele = deq_fd_landed_vote_t_iter_ele( self->votes, iter ); + fd_landed_vote_walk(w, ele, fun, "votes", level, 0 ); + } + } + fun( w, self->votes, "votes", FD_FLAMENCO_TYPE_ARR_END, "votes", level--, 0 ); + /* Done walking deque */ + + if( !self->has_root_slot ) { + fun( w, NULL, "root_slot", FD_FLAMENCO_TYPE_NULL, "ulong", level, 0 ); + } else { + fun( w, &self->root_slot, "root_slot", FD_FLAMENCO_TYPE_ULONG, "ulong", level, 0 ); + } + fd_vote_authorized_voters_walk( w, &self->authorized_voters, fun, "authorized_voters", level, 0 ); + + /* Walk deque */ + fun( w, self->epoch_credits, "epoch_credits", FD_FLAMENCO_TYPE_ARR, "epoch_credits", level++, 0 ); + if( self->epoch_credits ) { + for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( self->epoch_credits ); + !deq_fd_vote_epoch_credits_t_iter_done( self->epoch_credits, iter ); + iter = deq_fd_vote_epoch_credits_t_iter_next( self->epoch_credits, iter ) ) { + fd_vote_epoch_credits_t * ele = deq_fd_vote_epoch_credits_t_iter_ele( self->epoch_credits, iter ); + fd_vote_epoch_credits_walk(w, ele, fun, "epoch_credits", level, 0 ); + } + } + fun( w, self->epoch_credits, "epoch_credits", FD_FLAMENCO_TYPE_ARR_END, "epoch_credits", level--, 0 ); + /* Done walking deque */ + + fd_vote_block_timestamp_walk( w, &self->last_timestamp, fun, "last_timestamp", level, 0 ); + fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_vote_state_v4", level--, 0 ); +} +ulong fd_vote_state_v4_size( fd_vote_state_v4_t const * self ) { + ulong size = 0; + size += fd_pubkey_size( &self->node_pubkey ); + size += fd_pubkey_size( &self->authorized_withdrawer ); + size += fd_pubkey_size( &self->inflation_rewards_collector ); + size += fd_pubkey_size( &self->block_revenue_collector ); + size += sizeof(ushort); + size += sizeof(ushort); + size += sizeof(ulong); + size += sizeof(char); + if( self->has_bls_pubkey_compressed ) { + size += fd_bls_pubkey_compressed_size( &self->bls_pubkey_compressed ); + } + if( self->votes ) { + size += sizeof(ulong); + for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( self->votes ); !deq_fd_landed_vote_t_iter_done( self->votes, iter ); iter = deq_fd_landed_vote_t_iter_next( self->votes, iter ) ) { + fd_landed_vote_t * ele = deq_fd_landed_vote_t_iter_ele( self->votes, iter ); + size += fd_landed_vote_size( ele ); + } + } else { + size += sizeof(ulong); + } + size += sizeof(char); + if( self->has_root_slot ) { + size += sizeof(ulong); + } + size += fd_vote_authorized_voters_size( &self->authorized_voters ); + if( self->epoch_credits ) { + size += sizeof(ulong); + for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( self->epoch_credits ); !deq_fd_vote_epoch_credits_t_iter_done( self->epoch_credits, iter ); iter = deq_fd_vote_epoch_credits_t_iter_next( self->epoch_credits, iter ) ) { + fd_vote_epoch_credits_t * ele = deq_fd_vote_epoch_credits_t_iter_ele( self->epoch_credits, iter ); + size += fd_vote_epoch_credits_size( ele ); + } + } else { + size += sizeof(ulong); + } + size += fd_vote_block_timestamp_size( &self->last_timestamp ); + return size; +} + FD_FN_PURE uchar fd_vote_state_versioned_is_v0_23_5(fd_vote_state_versioned_t const * self) { return self->discriminant == 0; } @@ -3243,6 +3557,9 @@ FD_FN_PURE uchar fd_vote_state_versioned_is_v1_14_11(fd_vote_state_versioned_t c FD_FN_PURE uchar fd_vote_state_versioned_is_current(fd_vote_state_versioned_t const * self) { return self->discriminant == 2; } +FD_FN_PURE uchar fd_vote_state_versioned_is_v4(fd_vote_state_versioned_t const * self) { + return self->discriminant == 3; +} void fd_vote_state_versioned_inner_new( fd_vote_state_versioned_inner_t * self, uint discriminant ); int fd_vote_state_versioned_inner_decode_footprint( uint discriminant, fd_bincode_decode_ctx_t * ctx, ulong * total_sz ) { int err; @@ -3262,6 +3579,11 @@ int fd_vote_state_versioned_inner_decode_footprint( uint discriminant, fd_bincod if( FD_UNLIKELY( err ) ) return err; return FD_BINCODE_SUCCESS; } + case 3: { + err = fd_vote_state_v4_decode_footprint_inner( ctx, total_sz ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; + } default: return FD_BINCODE_ERR_ENCODING; } } @@ -3294,6 +3616,10 @@ static void fd_vote_state_versioned_inner_decode_inner( fd_vote_state_versioned_ fd_vote_state_decode_inner( &self->current, alloc_mem, ctx ); break; } + case 3: { + fd_vote_state_v4_decode_inner( &self->v4, alloc_mem, ctx ); + break; + } } } static void fd_vote_state_versioned_decode_inner( void * struct_mem, void * * alloc_mem, fd_bincode_decode_ctx_t * ctx ) { @@ -3323,6 +3649,10 @@ void fd_vote_state_versioned_inner_new( fd_vote_state_versioned_inner_t * self, fd_vote_state_new( &self->current ); break; } + case 3: { + fd_vote_state_v4_new( &self->v4 ); + break; + } default: break; // FD_LOG_ERR(( "unhandled type")); } } @@ -3354,6 +3684,11 @@ void fd_vote_state_versioned_walk( void * w, fd_vote_state_versioned_t const * s fd_vote_state_walk( w, &self->inner.current, fun, "current", level, 0 ); break; } + case 3: { + fun( w, self, "v4", FD_FLAMENCO_TYPE_ENUM_DISC, "discriminant", level, 0 ); + fd_vote_state_v4_walk( w, &self->inner.v4, fun, "v4", level, 0 ); + break; + } } fun( w, self, name, FD_FLAMENCO_TYPE_ENUM_END, "fd_vote_state_versioned", level--, 0 ); } @@ -3373,6 +3708,10 @@ ulong fd_vote_state_versioned_size( fd_vote_state_versioned_t const * self ) { size += fd_vote_state_size( &self->inner.current ); break; } + case 3: { + size += fd_vote_state_v4_size( &self->inner.v4 ); + break; + } } return size; } @@ -3395,6 +3734,11 @@ int fd_vote_state_versioned_inner_encode( fd_vote_state_versioned_inner_t const if( FD_UNLIKELY( err ) ) return err; break; } + case 3: { + err = fd_vote_state_v4_encode( &self->v4, ctx ); + if( FD_UNLIKELY( err ) ) return err; + break; + } } return FD_BINCODE_SUCCESS; } diff --git a/src/flamenco/types/fd_types.h b/src/flamenco/types/fd_types.h index 1a388243c08..de55f249e3b 100644 --- a/src/flamenco/types/fd_types.h +++ b/src/flamenco/types/fd_types.h @@ -456,6 +456,14 @@ struct fd_landed_vote { typedef struct fd_landed_vote fd_landed_vote_t; #define FD_LANDED_VOTE_ALIGN alignof(fd_landed_vote_t) +/* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v4.rs#L52-L56 */ +/* Encoded Size: Fixed (48 bytes) */ +struct fd_bls_pubkey_compressed { + uchar buf[48]; +}; +typedef struct fd_bls_pubkey_compressed fd_bls_pubkey_compressed_t; +#define FD_BLS_PUBKEY_COMPRESSED_ALIGN alignof(fd_bls_pubkey_compressed_t) + #define DEQUE_NAME deq_fd_vote_lockout_t #define DEQUE_T fd_vote_lockout_t #include "../../util/tmpl/fd_deque_dynamic.c" @@ -589,10 +597,33 @@ struct fd_vote_state { typedef struct fd_vote_state fd_vote_state_t; #define FD_VOTE_STATE_ALIGN alignof(fd_vote_state_t) +/* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v4.rs#L30-L71 */ +/* Encoded Size: Dynamic */ +struct fd_vote_state_v4 { + fd_pubkey_t node_pubkey; + fd_pubkey_t authorized_withdrawer; + fd_pubkey_t inflation_rewards_collector; + fd_pubkey_t block_revenue_collector; + ushort inflation_rewards_commission_bps; + ushort block_revenue_commission_bps; + ulong pending_delegator_rewards; + fd_bls_pubkey_compressed_t bls_pubkey_compressed; + uchar has_bls_pubkey_compressed; + fd_landed_vote_t * votes; /* fd_deque_dynamic (min cnt 32) */ + ulong root_slot; + uchar has_root_slot; + fd_vote_authorized_voters_t authorized_voters; + fd_vote_epoch_credits_t * epoch_credits; /* fd_deque_dynamic (min cnt 64) */ + fd_vote_block_timestamp_t last_timestamp; +}; +typedef struct fd_vote_state_v4 fd_vote_state_v4_t; +#define FD_VOTE_STATE_V4_ALIGN alignof(fd_vote_state_v4_t) + union fd_vote_state_versioned_inner { fd_vote_state_0_23_5_t v0_23_5; fd_vote_state_1_14_11_t v1_14_11; fd_vote_state_t current; + fd_vote_state_v4_t v4; }; typedef union fd_vote_state_versioned_inner fd_vote_state_versioned_inner_t; @@ -2288,6 +2319,18 @@ static inline int fd_landed_vote_decode_footprint( fd_bincode_decode_ctx_t * ctx } void * fd_landed_vote_decode( void * mem, fd_bincode_decode_ctx_t * ctx ); +static inline void fd_bls_pubkey_compressed_new( fd_bls_pubkey_compressed_t * self ) { fd_memset( self, 0, sizeof(fd_bls_pubkey_compressed_t) ); } +int fd_bls_pubkey_compressed_encode( fd_bls_pubkey_compressed_t const * self, fd_bincode_encode_ctx_t * ctx ); +void fd_bls_pubkey_compressed_walk( void * w, fd_bls_pubkey_compressed_t const * self, fd_types_walk_fn_t fun, const char *name, uint level, uint varint ); +static inline ulong fd_bls_pubkey_compressed_size( fd_bls_pubkey_compressed_t const * self ) { (void)self; return 48UL; } +static inline ulong fd_bls_pubkey_compressed_align( void ) { return FD_BLS_PUBKEY_COMPRESSED_ALIGN; } +static inline int fd_bls_pubkey_compressed_decode_footprint( fd_bincode_decode_ctx_t * ctx, ulong * total_sz ) { + *total_sz += sizeof(fd_bls_pubkey_compressed_t); + if( (ulong)ctx->data + 48UL > (ulong)ctx->dataend ) { return FD_BINCODE_ERR_OVERFLOW; }; + return 0; +} +void * fd_bls_pubkey_compressed_decode( void * mem, fd_bincode_decode_ctx_t * ctx ); + void fd_vote_state_0_23_5_new( fd_vote_state_0_23_5_t * self ); int fd_vote_state_0_23_5_encode( fd_vote_state_0_23_5_t const * self, fd_bincode_encode_ctx_t * ctx ); void fd_vote_state_0_23_5_walk( void * w, fd_vote_state_0_23_5_t const * self, fd_types_walk_fn_t fun, const char *name, uint level, uint varint ); @@ -2320,6 +2363,14 @@ static inline ulong fd_vote_state_align( void ) { return FD_VOTE_STATE_ALIGN; } int fd_vote_state_decode_footprint( fd_bincode_decode_ctx_t * ctx, ulong * total_sz ); void * fd_vote_state_decode( void * mem, fd_bincode_decode_ctx_t * ctx ); +void fd_vote_state_v4_new( fd_vote_state_v4_t * self ); +int fd_vote_state_v4_encode( fd_vote_state_v4_t const * self, fd_bincode_encode_ctx_t * ctx ); +void fd_vote_state_v4_walk( void * w, fd_vote_state_v4_t const * self, fd_types_walk_fn_t fun, const char *name, uint level, uint varint ); +ulong fd_vote_state_v4_size( fd_vote_state_v4_t const * self ); +static inline ulong fd_vote_state_v4_align( void ) { return FD_VOTE_STATE_V4_ALIGN; } +int fd_vote_state_v4_decode_footprint( fd_bincode_decode_ctx_t * ctx, ulong * total_sz ); +void * fd_vote_state_v4_decode( void * mem, fd_bincode_decode_ctx_t * ctx ); + void fd_vote_state_versioned_new_disc( fd_vote_state_versioned_t * self, uint discriminant ); void fd_vote_state_versioned_new( fd_vote_state_versioned_t * self ); int fd_vote_state_versioned_encode( fd_vote_state_versioned_t const * self, fd_bincode_encode_ctx_t * ctx ); @@ -2332,10 +2383,12 @@ void * fd_vote_state_versioned_decode( void * mem, fd_bincode_decode_ctx_t * ctx FD_FN_PURE uchar fd_vote_state_versioned_is_v0_23_5( fd_vote_state_versioned_t const * self ); FD_FN_PURE uchar fd_vote_state_versioned_is_v1_14_11( fd_vote_state_versioned_t const * self ); FD_FN_PURE uchar fd_vote_state_versioned_is_current( fd_vote_state_versioned_t const * self ); +FD_FN_PURE uchar fd_vote_state_versioned_is_v4( fd_vote_state_versioned_t const * self ); enum { fd_vote_state_versioned_enum_v0_23_5 = 0, fd_vote_state_versioned_enum_v1_14_11 = 1, fd_vote_state_versioned_enum_current = 2, +fd_vote_state_versioned_enum_v4 = 3, }; void fd_vote_state_update_new( fd_vote_state_update_t * self ); int fd_vote_state_update_encode( fd_vote_state_update_t const * self, fd_bincode_encode_ctx_t * ctx ); diff --git a/src/flamenco/types/fd_types.json b/src/flamenco/types/fd_types.json index e89f5876fb7..920d06fdb0f 100644 --- a/src/flamenco/types/fd_types.json +++ b/src/flamenco/types/fd_types.json @@ -378,6 +378,14 @@ ], "comment": "https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/programs/vote/src/vote_state/mod.rs#L268" }, + { + "name": "bls_pubkey_compressed", + "type": "struct", + "fields": [ + { "name": "buf", "type": "array", "element": "uchar", "length": "48" } + ], + "comment": "https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v4.rs#L52-L56" + }, { "name": "vote_state_0_23_5", "type": "struct", @@ -442,6 +450,26 @@ ], "comment": "https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/programs/vote/src/vote_state/mod.rs#L310" }, + { + "name": "vote_state_v4", + "type": "struct", + "fields": [ + { "name": "node_pubkey", "type": "pubkey" }, + { "name": "authorized_withdrawer", "type": "pubkey" }, + { "name": "inflation_rewards_collector", "type": "pubkey" }, + { "name": "block_revenue_collector", "type": "pubkey" }, + { "name": "inflation_rewards_commission_bps", "type": "ushort" }, + { "name": "block_revenue_commission_bps", "type": "ushort" }, + { "name": "pending_delegator_rewards", "type": "ulong" }, + { "name": "bls_pubkey_compressed", "type": "option", "element": "bls_pubkey_compressed", "flat": true }, + { "name": "votes", "type": "deque", "element": "landed_vote", "min": 32 }, + { "name": "root_slot", "type": "option", "element": "ulong", "flat": true }, + { "name": "authorized_voters", "type": "vote_authorized_voters" }, + { "name": "epoch_credits", "type": "deque", "element": "vote_epoch_credits", "min": 64 }, + { "name": "last_timestamp", "type": "vote_block_timestamp" } + ], + "comment": "https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v4.rs#L30-L71" + }, { "name": "vote_state_versioned", "type": "enum", @@ -449,7 +477,8 @@ "variants": [ { "name": "v0_23_5", "type": "vote_state_0_23_5" }, { "name": "v1_14_11", "type": "vote_state_1_14_11" }, - { "name": "current", "type": "vote_state" } + { "name": "current", "type": "vote_state" }, + { "name": "v4", "type": "vote_state_v4"} ], "comment": "https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/programs/vote/src/vote_state/vote_state_versions.rs#L4" }, diff --git a/src/flamenco/types/fd_types_reflect_generated.c b/src/flamenco/types/fd_types_reflect_generated.c index 582246d3c58..f920aad1322 100644 --- a/src/flamenco/types/fd_types_reflect_generated.c +++ b/src/flamenco/types/fd_types_reflect_generated.c @@ -3,7 +3,7 @@ #include "fd_types_custom.h" #include "fd_types_reflect_private.h" #pragma GCC diagnostic ignored "-Wpedantic" -ulong fd_types_vt_list_cnt = 149; +ulong fd_types_vt_list_cnt = 151; fd_types_vt_t const fd_types_vt_list[] = { { .name="fd_hash", .name_len=7, .align=FD_HASH_ALIGN, .new_=(void *)fd_hash_new, .decode=(void *)fd_hash_decode, .size=(void *)fd_hash_size, .walk=(void *)fd_hash_walk, .decode_footprint=(void *)fd_hash_decode_footprint, .encode=(void *)fd_hash_encode }, { .name="fd_pubkey", .name_len=9, .align=FD_PUBKEY_ALIGN, .new_=(void *)fd_pubkey_new, .decode=(void *)fd_pubkey_decode, .size=(void *)fd_pubkey_size, .walk=(void *)fd_pubkey_walk, .decode_footprint=(void *)fd_pubkey_decode_footprint, .encode=(void *)fd_pubkey_encode }, @@ -43,10 +43,12 @@ fd_types_vt_t const fd_types_vt_list[] = { { .name="fd_vote_prior_voters", .name_len=20, .align=FD_VOTE_PRIOR_VOTERS_ALIGN, .new_=(void *)fd_vote_prior_voters_new, .decode=(void *)fd_vote_prior_voters_decode, .size=(void *)fd_vote_prior_voters_size, .walk=(void *)fd_vote_prior_voters_walk, .decode_footprint=(void *)fd_vote_prior_voters_decode_footprint, .encode=(void *)fd_vote_prior_voters_encode }, { .name="fd_vote_prior_voters_0_23_5", .name_len=27, .align=FD_VOTE_PRIOR_VOTERS_0_23_5_ALIGN, .new_=(void *)fd_vote_prior_voters_0_23_5_new, .decode=(void *)fd_vote_prior_voters_0_23_5_decode, .size=(void *)fd_vote_prior_voters_0_23_5_size, .walk=(void *)fd_vote_prior_voters_0_23_5_walk, .decode_footprint=(void *)fd_vote_prior_voters_0_23_5_decode_footprint, .encode=(void *)fd_vote_prior_voters_0_23_5_encode }, { .name="fd_landed_vote", .name_len=14, .align=FD_LANDED_VOTE_ALIGN, .new_=(void *)fd_landed_vote_new, .decode=(void *)fd_landed_vote_decode, .size=(void *)fd_landed_vote_size, .walk=(void *)fd_landed_vote_walk, .decode_footprint=(void *)fd_landed_vote_decode_footprint, .encode=(void *)fd_landed_vote_encode }, + { .name="fd_bls_pubkey_compressed", .name_len=24, .align=FD_BLS_PUBKEY_COMPRESSED_ALIGN, .new_=(void *)fd_bls_pubkey_compressed_new, .decode=(void *)fd_bls_pubkey_compressed_decode, .size=(void *)fd_bls_pubkey_compressed_size, .walk=(void *)fd_bls_pubkey_compressed_walk, .decode_footprint=(void *)fd_bls_pubkey_compressed_decode_footprint, .encode=(void *)fd_bls_pubkey_compressed_encode }, { .name="fd_vote_state_0_23_5", .name_len=20, .align=FD_VOTE_STATE_0_23_5_ALIGN, .new_=(void *)fd_vote_state_0_23_5_new, .decode=(void *)fd_vote_state_0_23_5_decode, .size=(void *)fd_vote_state_0_23_5_size, .walk=(void *)fd_vote_state_0_23_5_walk, .decode_footprint=(void *)fd_vote_state_0_23_5_decode_footprint, .encode=(void *)fd_vote_state_0_23_5_encode }, { .name="fd_vote_authorized_voters", .name_len=25, .align=FD_VOTE_AUTHORIZED_VOTERS_ALIGN, .new_=(void *)fd_vote_authorized_voters_new, .decode=(void *)fd_vote_authorized_voters_decode, .size=(void *)fd_vote_authorized_voters_size, .walk=(void *)fd_vote_authorized_voters_walk, .decode_footprint=(void *)fd_vote_authorized_voters_decode_footprint, .encode=(void *)fd_vote_authorized_voters_encode }, { .name="fd_vote_state_1_14_11", .name_len=21, .align=FD_VOTE_STATE_1_14_11_ALIGN, .new_=(void *)fd_vote_state_1_14_11_new, .decode=(void *)fd_vote_state_1_14_11_decode, .size=(void *)fd_vote_state_1_14_11_size, .walk=(void *)fd_vote_state_1_14_11_walk, .decode_footprint=(void *)fd_vote_state_1_14_11_decode_footprint, .encode=(void *)fd_vote_state_1_14_11_encode }, { .name="fd_vote_state", .name_len=13, .align=FD_VOTE_STATE_ALIGN, .new_=(void *)fd_vote_state_new, .decode=(void *)fd_vote_state_decode, .size=(void *)fd_vote_state_size, .walk=(void *)fd_vote_state_walk, .decode_footprint=(void *)fd_vote_state_decode_footprint, .encode=(void *)fd_vote_state_encode }, + { .name="fd_vote_state_v4", .name_len=16, .align=FD_VOTE_STATE_V4_ALIGN, .new_=(void *)fd_vote_state_v4_new, .decode=(void *)fd_vote_state_v4_decode, .size=(void *)fd_vote_state_v4_size, .walk=(void *)fd_vote_state_v4_walk, .decode_footprint=(void *)fd_vote_state_v4_decode_footprint, .encode=(void *)fd_vote_state_v4_encode }, { .name="fd_vote_state_versioned", .name_len=23, .align=FD_VOTE_STATE_VERSIONED_ALIGN, .new_=(void *)fd_vote_state_versioned_new, .decode=(void *)fd_vote_state_versioned_decode, .size=(void *)fd_vote_state_versioned_size, .walk=(void *)fd_vote_state_versioned_walk, .decode_footprint=(void *)fd_vote_state_versioned_decode_footprint, .encode=(void *)fd_vote_state_versioned_encode }, { .name="fd_vote_state_update", .name_len=20, .align=FD_VOTE_STATE_UPDATE_ALIGN, .new_=(void *)fd_vote_state_update_new, .decode=(void *)fd_vote_state_update_decode, .size=(void *)fd_vote_state_update_size, .walk=(void *)fd_vote_state_update_walk, .decode_footprint=(void *)fd_vote_state_update_decode_footprint, .encode=(void *)fd_vote_state_update_encode }, { .name="fd_compact_vote_state_update", .name_len=28, .align=FD_COMPACT_VOTE_STATE_UPDATE_ALIGN, .new_=(void *)fd_compact_vote_state_update_new, .decode=(void *)fd_compact_vote_state_update_decode, .size=(void *)fd_compact_vote_state_update_size, .walk=(void *)fd_compact_vote_state_update_walk, .decode_footprint=(void *)fd_compact_vote_state_update_decode_footprint, .encode=(void *)fd_compact_vote_state_update_encode }, diff --git a/src/util/bits/fd_sat.h b/src/util/bits/fd_sat.h index 9c85a2edef9..636c27a7860 100644 --- a/src/util/bits/fd_sat.h +++ b/src/util/bits/fd_sat.h @@ -101,6 +101,18 @@ fd_uint_sat_sub( uint x, uint y ) { return fd_uint_if( cf, 0U, res ); } +FD_FN_CONST static inline ushort +fd_ushort_sat_add( ushort x, ushort y ) { + uint res = x+y; + return fd_ushort_if( res>USHORT_MAX, USHORT_MAX, (ushort)res ); +} + +FD_FN_CONST static inline ushort +fd_ushort_sat_mul( ushort x, ushort y ) { + uint res = x*y; + return fd_ushort_if( res>USHORT_MAX, USHORT_MAX, (ushort)res ); +} + FD_FN_CONST static inline double fd_double_sat_add( double x, double y ) { // What does rust do here?