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