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