Line data Source code
1 : #include "fd_compute_budget_program.h" 2 : 3 : #include "../fd_runtime_err.h" 4 : #include "../fd_runtime.h" 5 : #include "../fd_system_ids.h" 6 : #include "../fd_executor.h" 7 : #include "fd_builtin_programs.h" 8 : #include "../fd_compute_budget_details.h" 9 : 10 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/execution_budget.rs#L44 */ 11 165 : #define DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT (200000UL) 12 : 13 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/programs/compute-budget/src/lib.rs#L4 */ 14 : #define DEFAULT_COMPUTE_UNITS (150UL) 15 : 16 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/program-runtime/src/execution_budget.rs#L47 */ 17 165 : #define MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT (3000UL) 18 : 19 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/compute-budget-instruction/src/builtin_programs_filter.rs#L38 */ 20 : 21 : FD_FN_PURE static inline uchar 22 : get_program_kind( fd_bank_t const * bank, 23 : fd_txn_in_t const * txn_in, 24 129 : fd_txn_instr_t const * instr ) { 25 129 : fd_acct_addr_t const * txn_accs = fd_txn_get_acct_addrs( TXN( txn_in->txn ), txn_in->txn->payload ); 26 129 : fd_pubkey_t const * program_pubkey = fd_type_pun_const( &txn_accs[ instr->program_id ] ); 27 : 28 : /* The program is a standard, non-migrating builtin (e.g. system program) */ 29 129 : if( fd_is_non_migrating_builtin_program( program_pubkey ) ) { 30 : /* bls_pubkey_management_in_vote_account: the vote program is NOT 31 : being migrated to BPF, but is treated as migrated by the cost 32 : model. 33 : 34 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.6/builtins-default-costs/src/lib.rs#L95-L107 */ 35 129 : if( FD_UNLIKELY( FD_FEATURE_ACTIVE_BANK( bank, bls_pubkey_management_in_vote_account ) && 36 129 : fd_pubkey_eq( program_pubkey, &fd_solana_vote_program_id ) ) ) { 37 6 : return FD_PROGRAM_KIND_MIGRATING_BUILTIN; 38 6 : } 39 123 : return FD_PROGRAM_KIND_BUILTIN; 40 129 : } 41 : 42 0 : uchar migrated_yet; 43 0 : uchar is_migrating_program = fd_is_migrating_builtin_program( bank, program_pubkey, &migrated_yet ); 44 : 45 : /* The program has a BPF migration config but has not been migrated yet, so it's still a builtin program */ 46 0 : if( is_migrating_program && !migrated_yet ) { 47 0 : return FD_PROGRAM_KIND_BUILTIN; 48 0 : } 49 : 50 : /* The program has a BPF migration config AND has been migrated */ 51 0 : if( is_migrating_program && migrated_yet ) { 52 0 : return FD_PROGRAM_KIND_MIGRATING_BUILTIN; 53 0 : } 54 : 55 : /* The program is some other program kind, i.e. not a builtin */ 56 0 : return FD_PROGRAM_KIND_NOT_BUILTIN; 57 0 : } 58 : 59 : FD_FN_PURE static inline int 60 : is_compute_budget_instruction( fd_txn_t const * txn, 61 : uchar const * txn_payload, 62 129 : fd_txn_instr_t const * instr ) { 63 129 : fd_acct_addr_t const * txn_accs = fd_txn_get_acct_addrs( txn, txn_payload ); 64 129 : fd_acct_addr_t const * program_pubkey = &txn_accs[ instr->program_id ]; 65 129 : return !memcmp(program_pubkey, fd_solana_compute_budget_program_id.key, sizeof(fd_pubkey_t)); 66 129 : } 67 : 68 : /* In our implementation of this function, our parameters map to Agave's as follows: 69 : - `num_builtin_instrs` -> `num_non_migratable_builtin_instructions` + `num_not_migrated` 70 : - `num_non_builtin_instrs` -> `num_non_builtin_instructions` + `num_migrated` 71 : 72 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/compute-budget-instruction/src/compute_budget_instruction_details.rs#L196 */ 73 : 74 : FD_FN_CONST static inline ulong 75 : calculate_default_compute_unit_limit( ulong num_builtin_instrs, 76 165 : ulong num_non_builtin_instrs ) { 77 : /* https://github.com/anza-xyz/agave/blob/v2.1.13/runtime-transaction/src/compute_budget_instruction_details.rs#L227-L234 */ 78 165 : return fd_ulong_sat_add( fd_ulong_sat_mul( num_builtin_instrs, MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT ), 79 165 : fd_ulong_sat_mul( num_non_builtin_instrs, DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT ) ); 80 : 81 165 : } 82 : 83 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/compute-budget-instruction/src/compute_budget_instruction_details.rs#L192 */ 84 : 85 : FD_FN_CONST static inline int 86 6 : sanitize_requested_heap_size( ulong bytes ) { 87 6 : return 88 6 : bytes >= FD_MIN_HEAP_FRAME_BYTES && 89 6 : bytes <= FD_MAX_HEAP_FRAME_BYTES && 90 6 : ( bytes%FD_HEAP_FRAME_BYTES_GRANULARITY )==0UL; 91 6 : } 92 : 93 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/compute-budget-instruction/src/compute_budget_instruction_details.rs#L101-L153 */ 94 : 95 : int 96 183 : fd_sanitize_compute_unit_limits( fd_txn_out_t * txn_out ) { 97 183 : fd_compute_budget_details_t * details = &txn_out->details.compute_budget; 98 : 99 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/compute-budget-instruction/src/compute_budget_instruction_details.rs#L105-L119 */ 100 183 : if( details->has_requested_heap_size ) { 101 6 : if( FD_UNLIKELY( !sanitize_requested_heap_size( details->heap_size ) ) ) { 102 3 : FD_TXN_ERR_FOR_LOG_INSTR( txn_out, FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA, details->requested_heap_size_instr_index ); 103 3 : return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR; 104 3 : } 105 6 : } 106 : 107 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/compute-budget-instruction/src/compute_budget_instruction_details.rs#L121-L128 */ 108 180 : if( !details->has_compute_units_limit_update ) { 109 165 : details->compute_unit_limit = calculate_default_compute_unit_limit( details->num_builtin_instrs, 110 165 : details->num_non_builtin_instrs ); 111 165 : } 112 180 : details->compute_unit_limit = fd_ulong_min( FD_MAX_COMPUTE_UNIT_LIMIT, details->compute_unit_limit ); 113 180 : details->compute_meter = details->compute_unit_limit; 114 : 115 : /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/compute-budget-instruction/src/compute_budget_instruction_details.rs#L136-L145 */ 116 180 : if( details->has_loaded_accounts_data_size_limit_update ) { 117 0 : if( FD_UNLIKELY( details->loaded_accounts_data_size_limit==0UL ) ) { 118 0 : return FD_RUNTIME_TXN_ERR_INVALID_LOADED_ACCOUNTS_DATA_SIZE_LIMIT; 119 0 : } 120 0 : details->loaded_accounts_data_size_limit = fd_ulong_min( FD_VM_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, 121 0 : details->loaded_accounts_data_size_limit ); 122 0 : } 123 : 124 180 : return FD_RUNTIME_EXECUTE_SUCCESS; 125 180 : } 126 : 127 : /* Like Agave, this function is called during transaction verification 128 : and is responsible for simply reading and decoding the compute budget 129 : instruction data. Throws an error if any compute budget instruction 130 : in the transaction has invalid instruction data, or if there are duplicate 131 : compute budget instructions. 132 : 133 : NOTE: At this point, the transaction context has NOT been fully 134 : initialized (namely, the accounts). The accounts are NOT safe to access. 135 : 136 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/compute-budget-instruction/src/compute_budget_instruction_details.rs#L54-L99 */ 137 : int 138 : fd_executor_compute_budget_program_execute_instructions( fd_bank_t const * bank, 139 : fd_txn_in_t const * txn_in, 140 177 : fd_txn_out_t * txn_out ) { 141 177 : fd_compute_budget_details_t * details = &txn_out->details.compute_budget; 142 : 143 306 : for( ushort i=0; i<TXN( txn_in->txn )->instr_cnt; i++ ) { 144 129 : fd_txn_instr_t const * instr = &TXN( txn_in->txn )->instr[i]; 145 : 146 : /* Only `FD_PROGRAM_KIND_BUILTIN` gets charged as a builtin instruction */ 147 129 : uchar program_kind = get_program_kind( bank, txn_in, instr ); 148 129 : if( program_kind==FD_PROGRAM_KIND_BUILTIN ) { 149 123 : details->num_builtin_instrs++; 150 123 : } else { 151 6 : details->num_non_builtin_instrs++; 152 6 : } 153 : 154 129 : if( !is_compute_budget_instruction( TXN( txn_in->txn ), txn_in->txn->payload, instr ) ) { 155 114 : continue; 156 114 : } 157 : 158 15 : uchar const * data = (uchar const *)txn_in->txn->payload + instr->data_off; 159 : 160 15 : fd_compute_budget_instr_t instruction[1]; 161 15 : if( FD_UNLIKELY( fd_compute_budget_instr_decode( data, instr->data_sz, instruction ) ) ) { 162 0 : FD_TXN_ERR_FOR_LOG_INSTR( txn_out, FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA, i ); 163 0 : return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR; 164 0 : } 165 : 166 15 : switch( instruction->discriminant ) { 167 0 : case FD_COMPUTE_BUDGET_INSTR_DISC_REQUEST_HEAP_FRAME: { 168 0 : if( FD_UNLIKELY( details->has_requested_heap_size ) ) { 169 0 : return FD_RUNTIME_TXN_ERR_DUPLICATE_INSTRUCTION; 170 0 : } 171 : 172 0 : details->has_requested_heap_size = 1; 173 0 : details->heap_size = instruction->request_heap_frame; 174 0 : details->requested_heap_size_instr_index = i; 175 0 : break; 176 0 : } 177 15 : case FD_COMPUTE_BUDGET_INSTR_DISC_SET_COMPUTE_UNIT_LIMIT: { 178 15 : if( FD_UNLIKELY( details->has_compute_units_limit_update ) ) { 179 0 : return FD_RUNTIME_TXN_ERR_DUPLICATE_INSTRUCTION; 180 0 : } 181 : 182 15 : details->has_compute_units_limit_update = 1; 183 15 : details->compute_unit_limit = instruction->set_compute_unit_limit; 184 15 : break; 185 15 : } 186 0 : case FD_COMPUTE_BUDGET_INSTR_DISC_SET_COMPUTE_UNIT_PRICE: { 187 0 : if( FD_UNLIKELY( details->has_compute_units_price_update ) ) { 188 0 : return FD_RUNTIME_TXN_ERR_DUPLICATE_INSTRUCTION; 189 0 : } 190 : 191 0 : details->has_compute_units_price_update = 1; 192 0 : details->compute_unit_price = instruction->set_compute_unit_price; 193 0 : break; 194 0 : } 195 0 : case FD_COMPUTE_BUDGET_INSTR_DISC_SET_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: { 196 0 : if( FD_UNLIKELY( details->has_loaded_accounts_data_size_limit_update ) ) { 197 0 : return FD_RUNTIME_TXN_ERR_DUPLICATE_INSTRUCTION; 198 0 : } 199 : 200 0 : details->has_loaded_accounts_data_size_limit_update = 1; 201 0 : details->loaded_accounts_data_size_limit = instruction->set_loaded_accounts_data_size_limit; 202 0 : break; 203 0 : } 204 0 : default: { 205 0 : FD_TXN_ERR_FOR_LOG_INSTR( txn_out, FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA, i ); 206 0 : return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR; 207 0 : } 208 15 : } 209 15 : } 210 : 211 177 : return FD_RUNTIME_EXECUTE_SUCCESS; 212 177 : } 213 : 214 : int 215 15 : fd_compute_budget_program_execute( fd_exec_instr_ctx_t * ctx ) { 216 15 : FD_EXEC_CU_UPDATE( ctx, DEFAULT_COMPUTE_UNITS ); 217 15 : return FD_EXECUTOR_INSTR_SUCCESS; 218 15 : }