Line data Source code
1 : #include "fd_vote_state_v3.h" 2 : #include "fd_authorized_voters.h" 3 : #include "fd_vote_common.h" 4 : #include "fd_vote_state_versioned.h" 5 : #include "../fd_vote_program.h" 6 : #include "../../fd_runtime.h" 7 : 8 : /* to_vote_state_1_14_11 converts a "v3" vote state object into the 9 : older "v1.14.11" version. This destroys the "v3" object in the 10 : process. 11 : https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L67 */ 12 : static void 13 : to_vote_state_1_14_11( fd_vote_state_v3_t * vote_state, 14 : fd_vote_state_1_14_11_t * vote_state_1_14_11, /* out */ 15 0 : uchar * vote_lockout_mem ) { 16 0 : vote_state_1_14_11->node_pubkey = vote_state->node_pubkey; /* copy */ 17 0 : vote_state_1_14_11->authorized_withdrawer = vote_state->authorized_withdrawer; /* copy */ 18 0 : vote_state_1_14_11->commission = vote_state->commission; /* copy */ 19 : 20 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L72 21 0 : if( vote_state->votes ) { 22 0 : vote_state_1_14_11->votes = deq_fd_vote_lockout_t_join( 23 0 : deq_fd_vote_lockout_t_new( vote_lockout_mem, deq_fd_landed_vote_t_cnt( vote_state->votes ) ) ); 24 0 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( vote_state->votes ); 25 0 : !deq_fd_landed_vote_t_iter_done( vote_state->votes, iter ); 26 0 : iter = deq_fd_landed_vote_t_iter_next( vote_state->votes, iter ) ) { 27 0 : fd_landed_vote_t const * landed_vote = deq_fd_landed_vote_t_iter_ele_const( vote_state->votes, iter ); 28 0 : deq_fd_vote_lockout_t_push_tail_wrap( vote_state_1_14_11->votes, landed_vote->lockout ); 29 0 : } 30 0 : } 31 : 32 0 : vote_state_1_14_11->has_root_slot = vote_state->has_root_slot; /* copy */ 33 0 : vote_state_1_14_11->root_slot = vote_state->root_slot; /* copy */ 34 0 : vote_state_1_14_11->authorized_voters = vote_state->authorized_voters; /* move */ 35 0 : vote_state_1_14_11->prior_voters = vote_state->prior_voters; /* deep copy */ 36 0 : vote_state_1_14_11->epoch_credits = vote_state->epoch_credits; /* move */ 37 0 : vote_state_1_14_11->last_timestamp = vote_state->last_timestamp; /* deep copy */ 38 : 39 : /* Clear moved objects */ 40 0 : vote_state->authorized_voters.treap = NULL; 41 0 : vote_state->authorized_voters.pool = NULL; 42 0 : vote_state->epoch_credits = NULL; 43 : 44 0 : } 45 : 46 : void 47 : fd_vote_program_v3_create_new( fd_vote_init_t * const vote_init, 48 : fd_sol_sysvar_clock_t const * clock, 49 : uchar * authorized_voters_mem, 50 0 : fd_vote_state_versioned_t * versioned /* out */ ) { 51 0 : versioned->discriminant = fd_vote_state_versioned_enum_v3; 52 : 53 0 : fd_vote_state_v3_t * vote_state = &versioned->inner.v3; 54 0 : vote_state->node_pubkey = vote_init->node_pubkey; 55 0 : vote_state->authorized_voters = *fd_authorized_voters_new( clock->epoch, &vote_init->authorized_voter, authorized_voters_mem ); 56 0 : vote_state->authorized_withdrawer = vote_init->authorized_withdrawer; 57 0 : vote_state->commission = vote_init->commission; 58 0 : vote_state->prior_voters.idx = 31; 59 0 : vote_state->prior_voters.is_empty = 1; 60 0 : } 61 : 62 : int 63 : fd_vote_state_v3_set_vote_account_state( fd_exec_instr_ctx_t const * ctx, 64 : fd_borrowed_account_t * vote_account, 65 : fd_vote_state_versioned_t * versioned, 66 0 : uchar * vote_lockout_mem ) { 67 : /* This is a horrible conditional expression in Agave. 68 : The terms were broken up into their own variables. */ 69 0 : fd_vote_state_v3_t * v3_vote_state = &versioned->inner.v3; 70 : 71 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L420-L424 */ 72 0 : fd_rent_t const rent = fd_sysvar_cache_rent_read_nofail( ctx->sysvar_cache ); 73 0 : int resize_needed = fd_borrowed_account_get_data_len( vote_account ) < FD_VOTE_STATE_V3_SZ; 74 0 : int resize_rent_exempt = fd_rent_exempt_minimum_balance( &rent, FD_VOTE_STATE_V3_SZ ) <= fd_borrowed_account_get_lamports( vote_account ); 75 : 76 : /* The resize operation itself is part of the horrible conditional, 77 : but behind a short-circuit operator. */ 78 0 : int resize_failed = 0; 79 0 : if( resize_needed && resize_rent_exempt ) { 80 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L422-L424 */ 81 0 : resize_failed = 82 0 : fd_borrowed_account_set_data_length( vote_account, FD_VOTE_STATE_V3_SZ ) != FD_EXECUTOR_INSTR_SUCCESS; 83 0 : } 84 : 85 0 : if( FD_UNLIKELY( resize_needed && ( !resize_rent_exempt || resize_failed ) ) ) { 86 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L426-L430 */ 87 0 : fd_vote_state_versioned_t v1_14_11; 88 0 : fd_vote_state_versioned_new_disc( &v1_14_11, fd_vote_state_versioned_enum_v1_14_11 ); 89 0 : to_vote_state_1_14_11( v3_vote_state, &v1_14_11.inner.v1_14_11, vote_lockout_mem ); 90 0 : return fd_vsv_set_state( vote_account, &v1_14_11 ); 91 0 : } 92 : 93 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L432-L433 */ 94 0 : return fd_vsv_set_state( vote_account, versioned ); 95 0 : } 96 : 97 : int 98 : fd_vote_state_v3_deserialize( fd_borrowed_account_t const * vote_account, 99 : uchar * vote_state_mem, 100 : uchar * authorized_voters_mem, 101 0 : uchar * landed_votes_mem ) { 102 : /* deserialize_into_ptr is essentially a call to get_state + 103 : try_convert_to_v3. It's written a little more verbosely in Agave 104 : as they try to optimize the decoding steps. 105 : https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v3.rs#L162-L202 */ 106 0 : int rc = fd_vsv_get_state( vote_account->meta, vote_state_mem ); 107 0 : if( FD_UNLIKELY( rc ) ) return rc; 108 : 109 : /* Unlike vote states v4 decoding, vote state v3 decoding will fail 110 : if the discriminant is > fd_vote_state_versioned_enum_v3. 111 : https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v3.rs#L198 */ 112 0 : fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)vote_state_mem; 113 0 : if( FD_UNLIKELY( versioned->discriminant>fd_vote_state_versioned_enum_v3 ) ) { 114 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; 115 0 : } 116 : 117 0 : return fd_vsv_try_convert_to_v3( versioned, authorized_voters_mem, landed_votes_mem ); 118 0 : } 119 : 120 : int 121 : fd_vote_state_v3_get_and_update_authorized_voter( fd_vote_state_v3_t * self, 122 : ulong current_epoch, 123 0 : fd_pubkey_t ** pubkey /* out */ ) { 124 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L832 125 0 : fd_vote_authorized_voter_t * authorized_voter = fd_authorized_voters_get_and_cache_authorized_voter_for_epoch( 126 0 : &self->authorized_voters, 127 0 : current_epoch 128 0 : ); 129 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L835 130 0 : if( FD_UNLIKELY( !authorized_voter ) ) return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; 131 0 : *pubkey = &authorized_voter->pubkey; 132 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L837 133 0 : fd_authorized_voters_purge_authorized_voters( &self->authorized_voters, current_epoch ); 134 0 : return FD_EXECUTOR_INSTR_SUCCESS; 135 0 : } 136 : 137 : int 138 : fd_vote_state_v3_set_new_authorized_voter( fd_exec_instr_ctx_t * ctx, 139 : fd_vote_state_v3_t * self, 140 : fd_pubkey_t const * authorized_pubkey, 141 : ulong current_epoch, 142 : ulong target_epoch, 143 : int authorized_withdrawer_signer, 144 0 : fd_pubkey_t const * signers[static FD_TXN_SIG_MAX] ) { 145 0 : int rc; 146 0 : fd_pubkey_t * epoch_authorized_voter = NULL; 147 : 148 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L778 149 0 : rc = fd_vote_state_v3_get_and_update_authorized_voter( self, current_epoch, &epoch_authorized_voter ); 150 0 : if( FD_UNLIKELY( rc ) ) return rc; 151 : 152 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L779 153 0 : rc = fd_vote_signature_verify( epoch_authorized_voter, authorized_withdrawer_signer, signers ); 154 0 : if( FD_UNLIKELY( rc ) ) return rc; 155 : 156 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L786 157 0 : if( FD_UNLIKELY( fd_authorized_voters_contains( &self->authorized_voters, target_epoch ) ) ) { 158 0 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_TOO_SOON_TO_REAUTHORIZE; 159 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; 160 0 : } 161 : 162 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L791 163 0 : fd_vote_authorized_voter_t * latest_authorized = 164 0 : fd_authorized_voters_last( &self->authorized_voters ); 165 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L794 166 0 : if( FD_UNLIKELY( ( !latest_authorized ) ) ) return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; 167 0 : ulong latest_epoch = latest_authorized->epoch; 168 0 : fd_pubkey_t * latest_authorized_pubkey = &latest_authorized->pubkey; 169 : 170 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L799 171 0 : if( !fd_pubkey_eq( latest_authorized_pubkey, authorized_pubkey ) ) { 172 0 : fd_vote_prior_voters_t * prior_voters = &self->prior_voters; 173 : 174 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L801 175 0 : ulong epoch_of_last_authorized_switch = 0UL; 176 0 : if( (!prior_voters->is_empty) & (prior_voters->idx < 32) ) { 177 0 : epoch_of_last_authorized_switch = prior_voters->buf[prior_voters->idx].epoch_end; 178 0 : } 179 : 180 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L810 181 0 : if( target_epoch <= latest_epoch ) 182 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; 183 : 184 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L815 185 0 : prior_voters->idx += 1UL; 186 0 : prior_voters->idx %= 32UL; 187 0 : prior_voters->buf[prior_voters->idx] = 188 0 : ( fd_vote_prior_voter_t ){ .pubkey = *latest_authorized_pubkey, 189 0 : .epoch_start = epoch_of_last_authorized_switch, 190 0 : .epoch_end = target_epoch }; 191 0 : prior_voters->is_empty = 0; 192 0 : } 193 : 194 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L822 195 0 : if( FD_UNLIKELY( !fd_vote_authorized_voters_pool_free( self->authorized_voters.pool ) ) ) { 196 0 : FD_LOG_CRIT(( "invariant violation: max authorized voter count of vote account exceeded" )); 197 0 : } 198 : 199 0 : fd_vote_authorized_voter_t * ele = 200 0 : fd_vote_authorized_voters_pool_ele_acquire( self->authorized_voters.pool ); 201 0 : ele->epoch = target_epoch; 202 0 : ele->pubkey = *authorized_pubkey; 203 0 : ele->prio = (ulong)&ele->pubkey; 204 0 : fd_vote_authorized_voters_treap_ele_insert( 205 0 : self->authorized_voters.treap, ele, self->authorized_voters.pool ); 206 : 207 0 : return 0; 208 0 : } 209 :