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 600 : #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 600 : #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 438 : fd_txn_instr_t const * instr ) { 26 438 : fd_acct_addr_t const * txn_accs = fd_txn_get_acct_addrs( TXN( txn_in->txn ), txn_in->txn->payload ); 27 438 : 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 438 : 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 345 : if( FD_UNLIKELY( FD_FEATURE_ACTIVE_BANK( bank, bls_pubkey_management_in_vote_account ) && 37 345 : fd_pubkey_eq( program_pubkey, &fd_solana_vote_program_id ) ) ) { 38 108 : return FD_PROGRAM_KIND_MIGRATING_BUILTIN; 39 108 : } 40 237 : return FD_PROGRAM_KIND_BUILTIN; 41 345 : } 42 : 43 93 : uchar migrated_yet; 44 93 : 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 93 : 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 93 : 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 93 : return FD_PROGRAM_KIND_NOT_BUILTIN; 58 93 : } 59 : 60 : FD_FN_PURE static inline int 61 : is_compute_budget_instruction( fd_txn_t const * txn, 62 : uchar const * txn_payload, 63 435 : fd_txn_instr_t const * instr ) { 64 435 : fd_acct_addr_t const * txn_accs = fd_txn_get_acct_addrs( txn, txn_payload ); 65 435 : fd_acct_addr_t const * program_pubkey = &txn_accs[ instr->program_id ]; 66 435 : return !memcmp(program_pubkey, fd_solana_compute_budget_program_id.key, sizeof(fd_pubkey_t)); 67 435 : } 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 600 : 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 600 : return fd_ulong_sat_add( fd_ulong_sat_mul( num_builtin_instrs, MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT ), 80 600 : fd_ulong_sat_mul( num_non_builtin_instrs, DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT ) ); 81 : 82 600 : } 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 612 : fd_sanitize_compute_unit_limits( fd_txn_out_t * txn_out ) { 98 612 : 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 612 : 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 609 : if( !details->has_compute_units_limit_update ) { 110 600 : details->compute_unit_limit = calculate_default_compute_unit_limit( details->num_builtin_instrs, 111 600 : details->num_non_builtin_instrs ); 112 600 : } 113 609 : details->compute_unit_limit = fd_ulong_min( FD_MAX_COMPUTE_UNIT_LIMIT, details->compute_unit_limit ); 114 609 : 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 609 : 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 609 : return FD_RUNTIME_EXECUTE_SUCCESS; 126 609 : } 127 : 128 : void 129 : fd_compute_budget_count_instrs( fd_bank_t const * bank, 130 : fd_txn_in_t const * txn_in, 131 618 : fd_compute_budget_details_t * details ) { 132 1056 : for( ushort i=0; i<TXN( txn_in->txn )->instr_cnt; i++ ) { 133 : /* Only `FD_PROGRAM_KIND_BUILTIN` gets charged as a builtin instruction */ 134 438 : if( get_program_kind( bank, txn_in, &TXN( txn_in->txn )->instr[i] )==FD_PROGRAM_KIND_BUILTIN ) details->num_builtin_instrs++; 135 201 : else details->num_non_builtin_instrs++; 136 438 : } 137 618 : } 138 : 139 : /* Like Agave, this function is called during transaction verification 140 : and is responsible for simply reading and decoding the compute budget 141 : instruction data. Throws an error if any compute budget instruction 142 : in the transaction has invalid instruction data, or if there are duplicate 143 : compute budget instructions. 144 : 145 : NOTE: At this point, the transaction context has NOT been fully 146 : initialized (namely, the accounts). The accounts are NOT safe to access. 147 : 148 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/compute-budget-instruction/src/compute_budget_instruction_details.rs#L54-L99 */ 149 : int 150 : fd_executor_compute_budget_program_execute_instructions( fd_bank_t const * bank, 151 : fd_txn_in_t const * txn_in, 152 603 : fd_txn_out_t * txn_out ) { 153 603 : fd_compute_budget_details_t * details = &txn_out->details.compute_budget; 154 603 : fd_compute_budget_count_instrs( bank, txn_in, details ); 155 : 156 1038 : for( ushort i=0; i<TXN( txn_in->txn )->instr_cnt; i++ ) { 157 435 : fd_txn_instr_t const * instr = &TXN( txn_in->txn )->instr[i]; 158 : 159 435 : if( !is_compute_budget_instruction( TXN( txn_in->txn ), txn_in->txn->payload, instr ) ) { 160 420 : continue; 161 420 : } 162 : 163 15 : uchar const * data = (uchar const *)txn_in->txn->payload + instr->data_off; 164 : 165 15 : fd_compute_budget_instr_t instruction[1]; 166 15 : if( FD_UNLIKELY( fd_compute_budget_instr_decode( data, instr->data_sz, instruction ) ) ) { 167 0 : FD_TXN_ERR_FOR_LOG_INSTR( txn_out, FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA, i ); 168 0 : return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR; 169 0 : } 170 : 171 15 : switch( instruction->discriminant ) { 172 0 : case FD_COMPUTE_BUDGET_INSTR_DISC_REQUEST_HEAP_FRAME: { 173 0 : if( FD_UNLIKELY( details->has_requested_heap_size ) ) { 174 0 : return FD_RUNTIME_TXN_ERR_DUPLICATE_INSTRUCTION; 175 0 : } 176 : 177 0 : details->has_requested_heap_size = 1; 178 0 : details->heap_size = instruction->request_heap_frame; 179 0 : details->requested_heap_size_instr_index = i; 180 0 : break; 181 0 : } 182 6 : case FD_COMPUTE_BUDGET_INSTR_DISC_SET_COMPUTE_UNIT_LIMIT: { 183 6 : if( FD_UNLIKELY( details->has_compute_units_limit_update ) ) { 184 0 : return FD_RUNTIME_TXN_ERR_DUPLICATE_INSTRUCTION; 185 0 : } 186 : 187 6 : details->has_compute_units_limit_update = 1; 188 6 : details->compute_unit_limit = instruction->set_compute_unit_limit; 189 6 : break; 190 6 : } 191 0 : case FD_COMPUTE_BUDGET_INSTR_DISC_SET_COMPUTE_UNIT_PRICE: { 192 0 : if( FD_UNLIKELY( details->has_compute_units_price_update ) ) { 193 0 : return FD_RUNTIME_TXN_ERR_DUPLICATE_INSTRUCTION; 194 0 : } 195 : 196 0 : details->has_compute_units_price_update = 1; 197 0 : details->compute_unit_price = instruction->set_compute_unit_price; 198 0 : break; 199 0 : } 200 9 : case FD_COMPUTE_BUDGET_INSTR_DISC_SET_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: { 201 9 : if( FD_UNLIKELY( details->has_loaded_accounts_data_size_limit_update ) ) { 202 0 : return FD_RUNTIME_TXN_ERR_DUPLICATE_INSTRUCTION; 203 0 : } 204 : 205 9 : details->has_loaded_accounts_data_size_limit_update = 1; 206 9 : details->loaded_accounts_data_size_limit = instruction->set_loaded_accounts_data_size_limit; 207 9 : break; 208 9 : } 209 0 : default: { 210 0 : FD_TXN_ERR_FOR_LOG_INSTR( txn_out, FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA, i ); 211 0 : return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR; 212 9 : } 213 15 : } 214 15 : } 215 : 216 603 : return FD_RUNTIME_EXECUTE_SUCCESS; 217 603 : } 218 : 219 : int 220 6 : fd_compute_budget_program_execute( fd_exec_instr_ctx_t * ctx ) { 221 6 : FD_EXEC_CU_UPDATE( ctx, DEFAULT_COMPUTE_UNITS ); 222 6 : return FD_EXECUTOR_INSTR_SUCCESS; 223 6 : }