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 7543 : #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 7543 : #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 21623 : fd_txn_instr_t const * instr ) { 26 21623 : fd_acct_addr_t const * txn_accs = fd_txn_get_acct_addrs( TXN( txn_in->txn ), txn_in->txn->payload ); 27 21623 : 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 21623 : 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 12080 : if( FD_UNLIKELY( FD_FEATURE_ACTIVE_BANK( bank, bls_pubkey_management_in_vote_account ) && 37 12080 : fd_pubkey_eq( program_pubkey, &fd_solana_vote_program_id ) ) ) { 38 1721 : return FD_PROGRAM_KIND_MIGRATING_BUILTIN; 39 1721 : } 40 10359 : return FD_PROGRAM_KIND_BUILTIN; 41 12080 : } 42 : 43 9543 : uchar migrated_yet; 44 9543 : 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 9543 : 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 9543 : 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 9543 : return FD_PROGRAM_KIND_NOT_BUILTIN; 58 9543 : } 59 : 60 : FD_FN_PURE static inline int 61 : is_compute_budget_instruction( fd_txn_t const * txn, 62 : uchar const * txn_payload, 63 21535 : fd_txn_instr_t const * instr ) { 64 21535 : fd_acct_addr_t const * txn_accs = fd_txn_get_acct_addrs( txn, txn_payload ); 65 21535 : fd_acct_addr_t const * program_pubkey = &txn_accs[ instr->program_id ]; 66 21535 : return !memcmp(program_pubkey, fd_solana_compute_budget_program_id.key, sizeof(fd_pubkey_t)); 67 21535 : } 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 7543 : 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 7543 : return fd_ulong_sat_add( fd_ulong_sat_mul( num_builtin_instrs, MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT ), 80 7543 : fd_ulong_sat_mul( num_non_builtin_instrs, DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT ) ); 81 : 82 7543 : } 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 403 : sanitize_requested_heap_size( ulong bytes ) { 88 403 : return 89 403 : bytes >= FD_MIN_HEAP_FRAME_BYTES && 90 403 : bytes <= FD_MAX_HEAP_FRAME_BYTES && 91 403 : ( bytes%FD_HEAP_FRAME_BYTES_GRANULARITY )==0UL; 92 403 : } 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 8295 : fd_sanitize_compute_unit_limits( fd_txn_out_t * txn_out ) { 98 8295 : 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 8295 : if( details->has_requested_heap_size ) { 102 403 : if( FD_UNLIKELY( !sanitize_requested_heap_size( details->heap_size ) ) ) { 103 380 : FD_TXN_ERR_FOR_LOG_INSTR( txn_out, FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA, details->requested_heap_size_instr_index ); 104 380 : return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR; 105 380 : } 106 403 : } 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 7915 : if( !details->has_compute_units_limit_update ) { 110 7542 : details->compute_unit_limit = calculate_default_compute_unit_limit( details->num_builtin_instrs, 111 7542 : details->num_non_builtin_instrs ); 112 7542 : } 113 7915 : details->compute_unit_limit = fd_ulong_min( FD_MAX_COMPUTE_UNIT_LIMIT, details->compute_unit_limit ); 114 7915 : 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 7915 : if( details->has_loaded_accounts_data_size_limit_update ) { 118 287 : if( FD_UNLIKELY( details->loaded_accounts_data_size_limit==0UL ) ) { 119 17 : return FD_RUNTIME_TXN_ERR_INVALID_LOADED_ACCOUNTS_DATA_SIZE_LIMIT; 120 17 : } 121 270 : details->loaded_accounts_data_size_limit = fd_ulong_min( FD_VM_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, 122 270 : details->loaded_accounts_data_size_limit ); 123 270 : } 124 : 125 7898 : return FD_RUNTIME_EXECUTE_SUCCESS; 126 7915 : } 127 : 128 : void 129 : fd_compute_budget_count_instrs( fd_bank_t const * bank, 130 : fd_txn_in_t const * txn_in, 131 9174 : fd_compute_budget_details_t * details ) { 132 30796 : 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 21622 : if( get_program_kind( bank, txn_in, &TXN( txn_in->txn )->instr[i] )==FD_PROGRAM_KIND_BUILTIN ) details->num_builtin_instrs++; 135 11262 : else details->num_non_builtin_instrs++; 136 21622 : } 137 9174 : } 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 9174 : fd_txn_out_t * txn_out ) { 153 9174 : fd_compute_budget_details_t * details = &txn_out->details.compute_budget; 154 9174 : fd_compute_budget_count_instrs( bank, txn_in, details ); 155 : 156 30611 : for( ushort i=0; i<TXN( txn_in->txn )->instr_cnt; i++ ) { 157 21534 : fd_txn_instr_t const * instr = &TXN( txn_in->txn )->instr[i]; 158 : 159 21534 : if( !is_compute_budget_instruction( TXN( txn_in->txn ), txn_in->txn->payload, instr ) ) { 160 19739 : continue; 161 19739 : } 162 : 163 1795 : uchar const * data = (uchar const *)txn_in->txn->payload + instr->data_off; 164 : 165 1795 : fd_compute_budget_instr_t instruction[1]; 166 1795 : if( FD_UNLIKELY( fd_compute_budget_instr_decode( data, instr->data_sz, instruction ) ) ) { 167 30 : FD_TXN_ERR_FOR_LOG_INSTR( txn_out, FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA, i ); 168 30 : return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR; 169 30 : } 170 : 171 1765 : switch( instruction->discriminant ) { 172 508 : case FD_COMPUTE_BUDGET_INSTR_DISC_REQUEST_HEAP_FRAME: { 173 508 : if( FD_UNLIKELY( details->has_requested_heap_size ) ) { 174 21 : return FD_RUNTIME_TXN_ERR_DUPLICATE_INSTRUCTION; 175 21 : } 176 : 177 487 : details->has_requested_heap_size = 1; 178 487 : details->heap_size = instruction->request_heap_frame; 179 487 : details->requested_heap_size_instr_index = i; 180 487 : break; 181 508 : } 182 475 : case FD_COMPUTE_BUDGET_INSTR_DISC_SET_COMPUTE_UNIT_LIMIT: { 183 475 : if( FD_UNLIKELY( details->has_compute_units_limit_update ) ) { 184 11 : return FD_RUNTIME_TXN_ERR_DUPLICATE_INSTRUCTION; 185 11 : } 186 : 187 464 : details->has_compute_units_limit_update = 1; 188 464 : details->compute_unit_limit = instruction->set_compute_unit_limit; 189 464 : break; 190 475 : } 191 402 : case FD_COMPUTE_BUDGET_INSTR_DISC_SET_COMPUTE_UNIT_PRICE: { 192 402 : if( FD_UNLIKELY( details->has_compute_units_price_update ) ) { 193 25 : return FD_RUNTIME_TXN_ERR_DUPLICATE_INSTRUCTION; 194 25 : } 195 : 196 377 : details->has_compute_units_price_update = 1; 197 377 : details->compute_unit_price = instruction->set_compute_unit_price; 198 377 : break; 199 402 : } 200 382 : case FD_COMPUTE_BUDGET_INSTR_DISC_SET_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: { 201 382 : if( FD_UNLIKELY( details->has_loaded_accounts_data_size_limit_update ) ) { 202 12 : return FD_RUNTIME_TXN_ERR_DUPLICATE_INSTRUCTION; 203 12 : } 204 : 205 370 : details->has_loaded_accounts_data_size_limit_update = 1; 206 370 : details->loaded_accounts_data_size_limit = instruction->set_loaded_accounts_data_size_limit; 207 370 : break; 208 382 : } 209 2 : default: { 210 2 : FD_TXN_ERR_FOR_LOG_INSTR( txn_out, FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA, i ); 211 2 : return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR; 212 382 : } 213 1765 : } 214 1765 : } 215 : 216 9077 : return FD_RUNTIME_EXECUTE_SUCCESS; 217 9174 : } 218 : 219 : int 220 2817 : fd_compute_budget_program_execute( fd_exec_instr_ctx_t * ctx ) { 221 2817 : FD_EXEC_CU_UPDATE( ctx, DEFAULT_COMPUTE_UNITS ); 222 2817 : return FD_EXECUTOR_INSTR_SUCCESS; 223 2817 : }