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 17 : uchar * vote_lockout_mem ) { 16 17 : vote_state_1_14_11->node_pubkey = vote_state->node_pubkey; /* copy */ 17 17 : vote_state_1_14_11->authorized_withdrawer = vote_state->authorized_withdrawer; /* copy */ 18 17 : 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 17 : if( vote_state->votes ) { 22 14 : vote_state_1_14_11->votes = deq_fd_vote_lockout_t_join( 23 14 : deq_fd_vote_lockout_t_new( vote_lockout_mem, deq_fd_landed_vote_t_cnt( vote_state->votes ) ) ); 24 14 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( vote_state->votes ); 25 158 : !deq_fd_landed_vote_t_iter_done( vote_state->votes, iter ); 26 144 : iter = deq_fd_landed_vote_t_iter_next( vote_state->votes, iter ) ) { 27 144 : fd_landed_vote_t const * landed_vote = deq_fd_landed_vote_t_iter_ele_const( vote_state->votes, iter ); 28 144 : deq_fd_vote_lockout_t_push_tail_wrap( vote_state_1_14_11->votes, landed_vote->lockout ); 29 144 : } 30 14 : } 31 : 32 17 : vote_state_1_14_11->has_root_slot = vote_state->has_root_slot; /* copy */ 33 17 : vote_state_1_14_11->root_slot = vote_state->root_slot; /* copy */ 34 17 : vote_state_1_14_11->authorized_voters = vote_state->authorized_voters; /* move */ 35 17 : vote_state_1_14_11->prior_voters = vote_state->prior_voters; /* deep copy */ 36 17 : vote_state_1_14_11->epoch_credits = vote_state->epoch_credits; /* move */ 37 17 : vote_state_1_14_11->last_timestamp = vote_state->last_timestamp; /* deep copy */ 38 : 39 : /* Clear moved objects */ 40 17 : vote_state->authorized_voters.treap = NULL; 41 17 : vote_state->authorized_voters.pool = NULL; 42 17 : vote_state->epoch_credits = NULL; 43 : 44 17 : } 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 2 : fd_vote_state_versioned_t * versioned /* out */ ) { 51 2 : versioned->discriminant = fd_vote_state_versioned_enum_v3; 52 : 53 2 : fd_vote_state_v3_t * vote_state = &versioned->inner.v3; 54 2 : vote_state->node_pubkey = vote_init->node_pubkey; 55 2 : vote_state->authorized_voters = *fd_authorized_voters_new( clock->epoch, &vote_init->authorized_voter, authorized_voters_mem ); 56 2 : vote_state->authorized_withdrawer = vote_init->authorized_withdrawer; 57 2 : vote_state->commission = vote_init->commission; 58 2 : vote_state->prior_voters.idx = 31; 59 2 : vote_state->prior_voters.is_empty = 1; 60 2 : } 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 62 : uchar * vote_lockout_mem ) { 67 : /* This is a horrible conditional expression in Agave. 68 : The terms were broken up into their own variables. */ 69 62 : 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 62 : fd_rent_t const * rent = fd_bank_rent_query( ctx->bank ); 73 62 : int resize_needed = fd_borrowed_account_get_data_len( vote_account ) < FD_VOTE_STATE_V3_SZ; 74 62 : 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 62 : int resize_failed = 0; 79 62 : 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 32 : resize_failed = 82 32 : fd_borrowed_account_set_data_length( vote_account, FD_VOTE_STATE_V3_SZ ) != FD_EXECUTOR_INSTR_SUCCESS; 83 32 : } 84 : 85 62 : 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 17 : fd_vote_state_versioned_t v1_14_11; 88 17 : fd_vote_state_versioned_new_disc( &v1_14_11, fd_vote_state_versioned_enum_v1_14_11 ); 89 17 : to_vote_state_1_14_11( v3_vote_state, &v1_14_11.inner.v1_14_11, vote_lockout_mem ); 90 17 : return fd_vsv_set_state( vote_account, &v1_14_11 ); 91 17 : } 92 : 93 : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L432-L433 */ 94 45 : return fd_vsv_set_state( vote_account, versioned ); 95 62 : } 96 : 97 : int 98 : fd_vote_state_v3_deserialize( fd_borrowed_account_t const * vote_account, 99 : uchar * vote_state_mem, 100 1022 : uchar * landed_votes_mem ) { 101 : /* deserialize_into_ptr is essentially a call to get_state + 102 : try_convert_to_v3. It's written a little more verbosely in Agave 103 : as they try to optimize the decoding steps. 104 : https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v3.rs#L162-L202 */ 105 1022 : int rc = fd_vsv_get_state( vote_account->meta, vote_state_mem ); 106 1022 : if( FD_UNLIKELY( rc ) ) return rc; 107 : 108 : /* Unlike vote states v4 decoding, vote state v3 decoding will only 109 : pass for v1_14_11 and v3 vote states. 110 : https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v5.0.0/vote-interface/src/state/vote_state_v3.rs#L157-L164 */ 111 574 : fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)vote_state_mem; 112 574 : if( FD_UNLIKELY( versioned->discriminant!=fd_vote_state_versioned_enum_v1_14_11 && 113 574 : versioned->discriminant!=fd_vote_state_versioned_enum_v3 ) ) { 114 403 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; 115 403 : } 116 : 117 171 : return fd_vsv_try_convert_to_v3( versioned, landed_votes_mem ); 118 574 : } 119 : 120 : int 121 : fd_vote_state_v3_get_and_update_authorized_voter( fd_vote_state_v3_t * self, 122 : ulong current_epoch, 123 39 : 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 39 : fd_vote_authorized_voter_t * authorized_voter = fd_authorized_voters_get_and_cache_authorized_voter_for_epoch( 126 39 : &self->authorized_voters, 127 39 : current_epoch 128 39 : ); 129 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L835 130 39 : if( FD_UNLIKELY( !authorized_voter ) ) return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; 131 37 : *pubkey = &authorized_voter->pubkey; 132 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L837 133 37 : fd_authorized_voters_purge_authorized_voters( &self->authorized_voters, current_epoch ); 134 37 : return FD_EXECUTOR_INSTR_SUCCESS; 135 39 : } 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 : fd_bls_pubkey_compressed_t const * bls_pubkey, 144 : int authorized_withdrawer_signer, 145 : fd_pubkey_t const * signers[ FD_TXN_SIG_MAX ], 146 31 : ulong signers_cnt ) { 147 31 : int rc; 148 31 : fd_pubkey_t * epoch_authorized_voter = NULL; 149 : 150 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-alpha.0/programs/vote/src/vote_state/handler.rs#L287-L292 */ 151 31 : if( FD_UNLIKELY( bls_pubkey!=NULL ) ) { 152 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; 153 0 : } 154 : 155 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L778 156 31 : rc = fd_vote_state_v3_get_and_update_authorized_voter( self, current_epoch, &epoch_authorized_voter ); 157 31 : if( FD_UNLIKELY( rc ) ) return rc; 158 : 159 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L779 160 29 : rc = fd_vote_signature_verify( epoch_authorized_voter, authorized_withdrawer_signer, signers, signers_cnt ); 161 29 : if( FD_UNLIKELY( rc ) ) return rc; 162 : 163 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L786 164 16 : if( FD_UNLIKELY( fd_authorized_voters_contains( &self->authorized_voters, target_epoch ) ) ) { 165 1 : ctx->txn_out->err.custom_err = FD_VOTE_ERR_TOO_SOON_TO_REAUTHORIZE; 166 1 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; 167 1 : } 168 : 169 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L791 170 15 : fd_vote_authorized_voter_t * latest_authorized = 171 15 : fd_authorized_voters_last( &self->authorized_voters ); 172 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L794 173 15 : if( FD_UNLIKELY( ( !latest_authorized ) ) ) return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; 174 15 : ulong latest_epoch = latest_authorized->epoch; 175 15 : fd_pubkey_t * latest_authorized_pubkey = &latest_authorized->pubkey; 176 : 177 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L799 178 15 : if( !fd_pubkey_eq( latest_authorized_pubkey, authorized_pubkey ) ) { 179 14 : fd_vote_prior_voters_t * prior_voters = &self->prior_voters; 180 : 181 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L801 182 14 : ulong epoch_of_last_authorized_switch = 0UL; 183 14 : if( (!prior_voters->is_empty) & (prior_voters->idx < 32) ) { 184 1 : epoch_of_last_authorized_switch = prior_voters->buf[prior_voters->idx].epoch_end; 185 1 : } 186 : 187 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L810 188 14 : if( target_epoch <= latest_epoch ) 189 5 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; 190 : 191 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L815 192 9 : prior_voters->idx += 1UL; 193 9 : prior_voters->idx %= 32UL; 194 9 : prior_voters->buf[prior_voters->idx] = 195 9 : ( fd_vote_prior_voter_t ){ .pubkey = *latest_authorized_pubkey, 196 9 : .epoch_start = epoch_of_last_authorized_switch, 197 9 : .epoch_end = target_epoch }; 198 9 : prior_voters->is_empty = 0; 199 9 : } 200 : 201 : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L822 202 10 : if( FD_UNLIKELY( !fd_vote_authorized_voters_pool_free( self->authorized_voters.pool ) ) ) { 203 0 : FD_LOG_CRIT(( "invariant violation: max authorized voter count of vote account exceeded" )); 204 0 : } 205 : 206 10 : fd_vote_authorized_voter_t * ele = 207 10 : fd_vote_authorized_voters_pool_ele_acquire( self->authorized_voters.pool ); 208 10 : ele->epoch = target_epoch; 209 10 : ele->pubkey = *authorized_pubkey; 210 10 : ele->prio = (ulong)&ele->pubkey; 211 10 : fd_vote_authorized_voters_treap_ele_insert( 212 10 : self->authorized_voters.treap, ele, self->authorized_voters.pool ); 213 : 214 10 : return 0; 215 10 : } 216 :