Line data Source code
1 : #include "fd_config_program.h" 2 : #include "../fd_borrowed_account.h" 3 : #include "../fd_acc_mgr.h" 4 : #include "../fd_executor.h" 5 : #include "../fd_system_ids.h" 6 : #include "../context/fd_exec_epoch_ctx.h" 7 : #include "../context/fd_exec_slot_ctx.h" 8 : #include "../context/fd_exec_txn_ctx.h" 9 : #include "../context/fd_exec_instr_ctx.h" 10 : 11 : /* Useful links: 12 : 13 : https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs */ 14 : 15 : #define DEFAULT_COMPUTE_UNITS 450UL 16 : 17 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L16 */ 18 : 19 : static int 20 0 : _process_config_instr( fd_exec_instr_ctx_t * ctx ) { 21 : 22 0 : # define ACC_IDX_CONFIG ((uchar)0) 23 : 24 0 : int err; 25 : 26 : /* Deserialize the Config Program instruction data, which consists only of the ConfigKeys 27 : https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L21 */ 28 0 : if( FD_UNLIKELY( ctx->instr->data==NULL ) ) { 29 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; 30 0 : } 31 : 32 0 : fd_bincode_decode_ctx_t decode = { 33 0 : .data = ctx->instr->data, 34 0 : .dataend = ctx->instr->data + ctx->instr->data_sz 35 0 : }; 36 : 37 0 : ulong total_sz = 0UL; 38 0 : int decode_result = fd_config_keys_decode_footprint( &decode, &total_sz ); 39 : /* Fail if the number of bytes consumed by deserialize exceeds the txn MTU 40 : (hardcoded constant by Agave limited_deserialize) */ 41 0 : if( FD_UNLIKELY( decode_result != FD_BINCODE_SUCCESS || 42 0 : (ulong)ctx->instr->data + FD_TXN_MTU < (ulong)decode.data ) ) { 43 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; 44 0 : } 45 : 46 0 : uchar * mem = fd_spad_alloc( ctx->txn_ctx->spad, fd_config_keys_align(), total_sz ); 47 0 : if( FD_UNLIKELY( !mem ) ) { 48 0 : FD_LOG_ERR(( "Unable to allocate memory for config keys" )); 49 0 : } 50 : 51 0 : fd_config_keys_t * key_list = fd_config_keys_decode( mem, &decode ); 52 : 53 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L22-L26 */ 54 : 55 0 : int is_config_account_signer = 0; 56 0 : fd_pubkey_t const * config_account_key = NULL; 57 0 : fd_config_keys_t * current_data = NULL; 58 : 59 : /* https://github.com/anza-xyz/agave/blob/v2.1.4/programs/config/src/config_processor.rs#L26 */ 60 0 : fd_guarded_borrowed_account_t config_acc_rec; 61 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, ACC_IDX_CONFIG, &config_acc_rec ); 62 : 63 0 : config_account_key = config_acc_rec.acct->pubkey; 64 : 65 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L27 */ 66 : 67 0 : is_config_account_signer = fd_instr_acc_is_signer_idx( ctx->instr, ACC_IDX_CONFIG ); 68 : 69 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L29-L31 */ 70 : 71 0 : if( FD_UNLIKELY( 0!=memcmp( &config_acc_rec.acct->const_meta->info.owner, fd_solana_config_program_id.key, sizeof(fd_pubkey_t) ) ) ) { 72 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_OWNER; 73 0 : } 74 : 75 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L33-L40 */ 76 : 77 0 : fd_bincode_decode_ctx_t config_acc_state_decode_context = { 78 0 : .data = config_acc_rec.acct->const_data, 79 0 : .dataend = config_acc_rec.acct->const_data + config_acc_rec.acct->const_meta->dlen, 80 0 : }; 81 0 : total_sz = 0UL; 82 0 : decode_result = fd_config_keys_decode_footprint( &config_acc_state_decode_context, &total_sz ); 83 0 : if( FD_UNLIKELY( decode_result!=FD_BINCODE_SUCCESS ) ) { 84 : //TODO: full log, including err 85 0 : fd_log_collector_msg_literal( ctx, "Unable to deserialize config account" ); 86 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; 87 0 : } 88 : 89 0 : mem = fd_spad_alloc( ctx->txn_ctx->spad, fd_config_keys_align(), total_sz ); 90 0 : if( FD_UNLIKELY( !mem ) ) { 91 0 : FD_LOG_ERR(( "Unable to allocate memory for config account" )); 92 0 : } 93 : 94 0 : current_data = fd_config_keys_decode( mem, &config_acc_state_decode_context ); 95 : 96 : /* https://github.com/anza-xyz/agave/blob/v2.1.4/programs/config/src/config_processor.rs#L43 */ 97 : 98 0 : fd_borrowed_account_drop( &config_acc_rec ); 99 : 100 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L44-L49 */ 101 : 102 0 : fd_pubkey_t * current_signer_keys = fd_spad_alloc( ctx->txn_ctx->spad, 103 0 : alignof(fd_pubkey_t), 104 0 : sizeof(fd_pubkey_t) * current_data->keys_len ); 105 0 : ulong current_signer_key_cnt = 0UL; 106 : 107 0 : for( ulong i=0UL; i < current_data->keys_len; i++ ) { 108 0 : if( current_data->keys[i].signer ) { 109 0 : current_signer_keys[ current_signer_key_cnt++ ] = current_data->keys[i].key; 110 0 : } 111 0 : } 112 : 113 : /* If we have no keys in the account, require the config account to have signed the transaction 114 : https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L50-L56 */ 115 : 116 0 : if( FD_UNLIKELY( current_signer_key_cnt==0UL && !is_config_account_signer ) ) { 117 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 118 0 : } 119 : 120 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L58 */ 121 : 122 0 : ulong counter = 0UL; 123 : /* Invariant: counter <= key_list.keys_len */ 124 : 125 0 : for( ulong i=0UL; i<key_list->keys_len; i++ ) { 126 0 : if( !key_list->keys[i].signer ) continue; 127 0 : fd_pubkey_t const * signer = &key_list->keys[i].key; 128 : 129 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L60 */ 130 : 131 0 : counter++; 132 : 133 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L61 */ 134 : 135 0 : if( 0!=memcmp( signer, config_account_key, sizeof(fd_pubkey_t) ) ) { 136 : 137 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L62-L71 */ 138 : 139 : /* Intentionally don't use the scoping macro here because Anza maps the 140 : error to missing required signature if the try borrow fails */ 141 0 : fd_borrowed_account_t signer_account; 142 0 : int borrow_err = fd_exec_instr_ctx_try_borrow_account( ctx, (uchar)counter, &signer_account ); 143 0 : if( FD_UNLIKELY( borrow_err ) ) { 144 : /* Max msg_sz: 33 - 2 + 45 = 76 < 127 => we can use printf */ 145 0 : fd_log_collector_printf_dangerous_max_127( ctx, 146 0 : "account %s is not in account list", FD_BASE58_ENC_32_ALLOCA( signer ) ); 147 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 148 0 : } 149 : 150 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L72-L79 */ 151 : 152 0 : if( FD_UNLIKELY( !fd_instr_acc_is_signer_idx( ctx->instr, (uchar)counter ) ) ) { 153 : /* Max msg_sz: 33 - 2 + 45 = 76 < 127 => we can use printf */ 154 0 : fd_log_collector_printf_dangerous_max_127( ctx, 155 0 : "account %s signer_key().is_none()", FD_BASE58_ENC_32_ALLOCA( signer ) ); 156 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 157 0 : } 158 : 159 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L80-L87 */ 160 : 161 0 : if( FD_UNLIKELY( 0!=memcmp( signer_account.acct->pubkey, signer, sizeof(fd_pubkey_t) ) ) ) { 162 : /* Max msg_sz: 53 - 3 + 20 = 70 < 127 => we can use printf */ 163 0 : fd_log_collector_printf_dangerous_max_127( ctx, 164 0 : "account[%lu].signer_key() does not match Config data)", counter+1 ); 165 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 166 0 : } 167 : 168 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L89-L98 */ 169 : 170 0 : if( current_data->keys_len>0UL ) { 171 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L90 */ 172 0 : int is_signer = 0; 173 0 : for( ulong j=0UL; j<current_signer_key_cnt; j++ ) { 174 0 : if( 0==memcmp( ¤t_signer_keys[j], signer, sizeof(fd_pubkey_t) ) ) { 175 0 : is_signer = 1; 176 0 : break; 177 0 : } 178 0 : } 179 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L97 */ 180 0 : if( FD_UNLIKELY( !is_signer ) ) { 181 : /* Max msg_sz: 39 - 2 + 45 = 82 < 127 => we can use printf */ 182 0 : fd_log_collector_printf_dangerous_max_127( ctx, 183 0 : "account %s is not in stored signer list", FD_BASE58_ENC_32_ALLOCA( signer ) ); 184 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 185 0 : } 186 0 : } 187 : 188 : /* implicit drop of signer account */ 189 : 190 0 : } else if( !is_config_account_signer ) { 191 : 192 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L101 */ 193 0 : fd_log_collector_msg_literal( ctx, "account[0].signer_key().is_none()" ); 194 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 195 : 196 0 : } 197 0 : } 198 : 199 : /* Disallow duplicate keys 200 : https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L105-L115 201 : 202 : TODO: Agave uses a O(n log n) algorithm here */ 203 0 : for( ulong i = 0; i < key_list->keys_len; i++ ) { 204 0 : for( ulong j = 0; j < key_list->keys_len; j++ ) { 205 0 : if( i == j ) continue; 206 : 207 0 : if( FD_UNLIKELY( memcmp( &key_list->keys[i].key, &key_list->keys[j].key, sizeof(fd_pubkey_t) ) == 0 && 208 0 : key_list->keys[i].signer == key_list->keys[j].signer ) ) { 209 0 : fd_log_collector_msg_literal( ctx, "new config contains duplicate keys" ); 210 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG; 211 0 : } 212 0 : } 213 0 : } 214 : 215 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L118-L126 */ 216 : 217 0 : if( FD_UNLIKELY( current_signer_key_cnt>counter ) ) { 218 : /* Max msg_sz: 35 - 6 + 2*20 = 69 < 127 => we can use printf */ 219 0 : fd_log_collector_printf_dangerous_max_127( ctx, 220 0 : "too few signers: %lu; expected: %lu", counter, current_signer_key_cnt ); 221 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 222 0 : } 223 : 224 : /* Upgrade to writable handle 225 : https://github.com/anza-xyz/agave/blob/v2.1.4/programs/config/src/config_processor.rs#L125-L126 */ 226 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, ACC_IDX_CONFIG, &config_acc_rec ); 227 : 228 : /* Upgrade to writable handle 229 : https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L130-L133 */ 230 : 231 0 : if( FD_UNLIKELY( config_acc_rec.acct->const_meta->dlen<ctx->instr->data_sz ) ) { 232 0 : fd_log_collector_msg_literal( ctx, "instruction data too large" ); 233 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; 234 0 : } 235 : 236 : /* https://github.com/anza-xyz/agave/blob/v2.1.14/programs/config/src/config_processor.rs#L131 */ 237 0 : uchar * data = NULL; 238 0 : ulong dlen = 0UL; 239 0 : err = fd_borrowed_account_get_data_mut( &config_acc_rec, &data, &dlen ); 240 0 : if( FD_UNLIKELY( err ) ) { 241 0 : return err; 242 0 : } 243 : 244 : /* copy_from_slice */ 245 0 : fd_memcpy( data, ctx->instr->data, ctx->instr->data_sz ); 246 : 247 : /* Implicitly dropped */ 248 : 249 0 : return FD_EXECUTOR_INSTR_SUCCESS; 250 0 : # undef ACC_IDX_CONFIG 251 : 252 0 : } 253 : 254 : int 255 0 : fd_config_program_execute( fd_exec_instr_ctx_t * ctx ) { 256 : /* Prevent execution of migrated native programs */ 257 0 : if( FD_UNLIKELY( FD_FEATURE_ACTIVE( ctx->txn_ctx->slot, ctx->txn_ctx->features, migrate_config_program_to_core_bpf ) ) ) { 258 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID; 259 0 : } 260 : 261 : /* https://github.com/solana-labs/solana/blob/v1.17.27/programs/config/src/config_processor.rs#L14 262 : See DEFAULT_COMPUTE_UNITS */ 263 0 : FD_EXEC_CU_UPDATE( ctx, DEFAULT_COMPUTE_UNITS ); 264 : 265 0 : FD_SPAD_FRAME_BEGIN( ctx->txn_ctx->spad ) { 266 : 267 0 : int ret = _process_config_instr( ctx ); 268 0 : return ret; 269 : 270 0 : } FD_SPAD_FRAME_END; 271 0 : }