Line data Source code
1 : #ifndef HEADER_fd_src_flamenco_runtime_context_fd_exec_txn_ctx_h 2 : #define HEADER_fd_src_flamenco_runtime_context_fd_exec_txn_ctx_h 3 : 4 : #include "fd_exec_instr_ctx.h" 5 : #include "../../../util/fd_util_base.h" 6 : #include "../../log_collector/fd_log_collector_base.h" 7 : #include "../../../ballet/txn/fd_txn.h" 8 : #include "../../features/fd_features.h" 9 : #include "../fd_acc_mgr.h" 10 : #include "../fd_txncache.h" 11 : #include "../fd_bank_hash_cmp.h" 12 : #include "../fd_bank.h" 13 : 14 : /* Return data for syscalls */ 15 : 16 : struct fd_txn_return_data { 17 : fd_pubkey_t program_id; 18 : ulong len; 19 : uchar data[1024]; 20 : }; 21 : 22 : typedef struct fd_txn_return_data fd_txn_return_data_t; 23 : 24 : /* fd_exec_txn_ctx_t is the context needed to execute a transaction. */ 25 : 26 : /* Cache of deserialized vote accounts to support iteration after replaying a slot (required for fork choice) */ 27 : struct fd_vote_account_cache_entry { 28 : fd_pubkey_t pubkey; 29 : ulong next; 30 : fd_vote_state_t vote_account; 31 : }; 32 : typedef struct fd_vote_account_cache_entry fd_vote_account_cache_entry_t; 33 : 34 : #define POOL_NAME fd_vote_account_pool 35 : #define POOL_T fd_vote_account_cache_entry_t 36 : #include "../../../util/tmpl/fd_pool.c" 37 : 38 : #define MAP_NAME fd_vote_account_cache 39 : #define MAP_ELE_T fd_vote_account_cache_entry_t 40 : #define MAP_KEY pubkey 41 : #define MAP_KEY_T fd_pubkey_t 42 : #define MAP_KEY_EQ(k0,k1) (!(memcmp((k0)->key,(k1)->key,sizeof(fd_hash_t)))) 43 : #define MAP_KEY_HASH(key,seed) ( ((key)->ui[0]) ^ (seed) ) 44 : #include "../../../util/tmpl/fd_map_chain.c" 45 : 46 : /* An entry in the instruction trace */ 47 : struct fd_exec_instr_trace_entry { 48 : /* Metadata about the instruction */ 49 : fd_instr_info_t * instr_info; 50 : /* Stack height when this instruction was pushed onto the stack (including itself) 51 : https://github.com/anza-xyz/agave/blob/d87e23d8d91c32d5f2be2bb3557c730bee1e9434/sdk/src/transaction_context.rs#L475-L480 */ 52 : ulong stack_height; 53 : }; 54 : typedef struct fd_exec_instr_trace_entry fd_exec_instr_trace_entry_t; 55 : 56 : /* https://github.com/anza-xyz/agave/blob/0d34a1a160129c4293dac248e14231e9e773b4ce/program-runtime/src/compute_budget.rs#L139 */ 57 : #define FD_MAX_INSTRUCTION_TRACE_LENGTH (64UL) 58 : /* https://github.com/anza-xyz/agave/blob/f70ab5598ccd86b216c3928e4397bf4a5b58d723/compute-budget/src/compute_budget.rs#L13 */ 59 0 : #define FD_MAX_INSTRUCTION_STACK_DEPTH (5UL) 60 : 61 : struct fd_exec_txn_ctx { 62 : ulong magic; /* ==FD_EXEC_TXN_CTX_MAGIC */ 63 : 64 : /* TODO: These are fields borrowed from the slot and epoch ctx. This 65 : could be refactored even further. Currently these fields are not 66 : all valid local joins within the scope of txn execution. */ 67 : 68 : uint flags; 69 : 70 : fd_bank_t * bank; 71 : 72 : /* All pointers starting here are valid local joins in txn execution. */ 73 : fd_features_t features; 74 : fd_txncache_t * status_cache; 75 : int enable_exec_recording; 76 : fd_bank_hash_cmp_t * bank_hash_cmp; 77 : fd_funk_txn_t * funk_txn; 78 : fd_funk_t funk[1]; 79 : fd_wksp_t * runtime_pub_wksp; 80 : ulong slot; 81 : 82 : fd_spad_t * spad; /* Sized out to handle the worst case footprint of single transaction execution. */ 83 : fd_wksp_t * spad_wksp; /* Workspace for the spad. */ 84 : /* Fields below here are not guaranteed to be local joins in txn execution. */ 85 : 86 : ulong paid_fees; 87 : ulong compute_unit_limit; /* Compute unit limit for this transaction. */ 88 : ulong compute_unit_price; /* Compute unit price for this transaction. */ 89 : ulong compute_meter; /* Remaining compute units */ 90 : ulong heap_size; /* Heap size for VMs for this transaction. */ 91 : ulong loaded_accounts_data_size_limit; /* Loaded accounts data size limit for this transaction. */ 92 : ulong loaded_accounts_data_size; /* The actual transaction loaded data size */ 93 : uint prioritization_fee_type; /* The type of prioritization fee to use. */ 94 : fd_txn_t const * txn_descriptor; /* Descriptor of the transaction. */ 95 : fd_rawtxn_b_t _txn_raw[1]; /* Raw bytes of the transaction. */ 96 : uint custom_err; /* When a custom error is returned, this is where the numeric value gets stashed */ 97 : uchar instr_stack_sz; /* Current depth of the instruction execution stack. */ 98 : fd_exec_instr_ctx_t instr_stack[FD_MAX_INSTRUCTION_STACK_DEPTH]; /* Instruction execution stack. */ 99 : fd_exec_instr_ctx_t * failed_instr; 100 : int instr_err_idx; 101 : /* During sanitization, v0 transactions are allowed to have up to 256 accounts: 102 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/sdk/program/src/message/versions/v0/mod.rs#L139 103 : Nonetheless, when Agave prepares a sanitized batch for execution and tries to lock accounts, a lower limit is enforced: 104 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L118 105 : That is the limit we are going to use here. */ 106 : ulong accounts_cnt; /* Number of account pubkeys accessed by this transaction. */ 107 : fd_pubkey_t account_keys[ MAX_TX_ACCOUNT_LOCKS ]; /* Array of account pubkeys accessed by this transaction. */ 108 : ulong executable_cnt; /* Number of BPF upgradeable loader accounts. */ 109 : fd_txn_account_t executable_accounts[ MAX_TX_ACCOUNT_LOCKS ]; /* Array of BPF upgradeable loader program data accounts */ 110 : fd_txn_account_t accounts[ MAX_TX_ACCOUNT_LOCKS ]; /* Array of borrowed accounts accessed by this transaction. */ 111 : 112 : /* The next three fields describe Agave's "rollback" accounts, which 113 : are copies of the fee payer and (if applicable) nonce account. If the 114 : transaction fails to load, the fee payer is still debited the transaction fee, 115 : and the nonce account is advanced. The fee payer must also be rolled back to it's 116 : state pre-transaction, plus debited any transaction fees. 117 : 118 : This is a bit of a misnomer but Agave calls it "rollback". 119 : This is the account state that the nonce account should be in when 120 : the txn fails. 121 : It will advance the nonce account, rather than "roll back". 122 : */ 123 : fd_txn_account_t rollback_nonce_account[ 1 ]; 124 : ulong nonce_account_idx_in_txn; /* If the transaction has a nonce account that must be advanced, this would be !=ULONG_MAX. */ 125 : fd_txn_account_t rollback_fee_payer_account[ 1 ]; 126 : 127 : uint num_instructions; /* Counter for number of instructions in txn */ 128 : fd_txn_return_data_t return_data; /* Data returned from `return_data` syscalls */ 129 : fd_vote_account_cache_t * vote_accounts_map; /* Cache of bank's deserialized vote accounts to support fork choice */ 130 : fd_vote_account_cache_entry_t * vote_accounts_pool; /* Memory pool for deserialized vote account cache */ 131 : ulong accounts_resize_delta; /* Transaction level tracking for account resizing */ 132 : fd_hash_t blake_txn_msg_hash; /* Hash of raw transaction message used by the status cache */ 133 : ulong execution_fee; /* Execution fee paid by the fee payer in the transaction */ 134 : ulong priority_fee; /* Priority fee paid by the fee payer in the transaction */ 135 : ulong collected_rent; /* Rent collected from accounts in this transaction */ 136 : 137 : uchar dirty_vote_acc : 1; /* 1 if this transaction maybe modified a vote account */ 138 : uchar dirty_stake_acc : 1; /* 1 if this transaction maybe modified a stake account */ 139 : 140 : fd_capture_ctx_t * capture_ctx; 141 : 142 : /* The instr_infos for the entire transaction are allocated at the start of 143 : the transaction. However, this must preserve a different counter because 144 : the top level instructions must get set up at once. The instruction 145 : error check on a maximum instruction size can be done on the 146 : instr_info_cnt instead of the instr_trace_length because it is a proxy 147 : for the trace_length: the instr_info_cnt gets incremented faster than 148 : the instr_trace_length because it counts all of the top level instructions 149 : first. */ 150 : fd_instr_info_t instr_infos[FD_MAX_INSTRUCTION_TRACE_LENGTH]; 151 : ulong instr_info_cnt; 152 : 153 : /* These instr infos are statically allocated at the beginning of a transaction 154 : and are only written to / referred to within the VM. It's kept 155 : at the transaction level because syscalls like `GetProcessedSiblingInstruction()` 156 : may refer to instructions processed earlier in the transaction. */ 157 : fd_instr_info_t cpi_instr_infos[FD_MAX_INSTRUCTION_TRACE_LENGTH]; 158 : ulong cpi_instr_info_cnt; 159 : 160 : /* Each instr info within `instr_trace` may refer to an `instr_infos` or `cpi_instr_infos` 161 : entry. */ 162 : fd_exec_instr_trace_entry_t instr_trace[FD_MAX_INSTRUCTION_TRACE_LENGTH]; /* Instruction trace */ 163 : ulong instr_trace_length; /* Number of instructions in the trace */ 164 : 165 : fd_log_collector_t log_collector; /* Log collector instance */ 166 : 167 : /* Execution error and type, to match Agave. */ 168 : int exec_err; 169 : int exec_err_kind; 170 : 171 : /* The current instruction index being executed */ 172 : int current_instr_idx; 173 : }; 174 : 175 7665 : #define FD_EXEC_TXN_CTX_ALIGN (alignof(fd_exec_txn_ctx_t)) 176 7665 : #define FD_EXEC_TXN_CTX_FOOTPRINT ( sizeof(fd_exec_txn_ctx_t)) 177 0 : #define FD_EXEC_TXN_CTX_MAGIC (0x9AD93EE71469F4D7UL ) /* random */ 178 : 179 : FD_PROTOTYPES_BEGIN 180 : 181 : /* Error logging handholding assertions */ 182 : 183 : #ifdef FD_RUNTIME_ERR_HANDHOLDING 184 : 185 : /* Asserts that the error and error kind are not populated (zero) */ 186 : #define FD_TXN_TEST_ERR_OVERWRITE( txn_ctx ) \ 187 : FD_TEST( !txn_ctx->exec_err ); \ 188 : FD_TEST( !txn_ctx->exec_err_kind ) 189 : 190 : /* Used prior to a FD_TXN_ERR_FOR_LOG_INSTR call to deliberately 191 : bypass overwrite handholding checks. 192 : Only use this if you know what you're doing. */ 193 : #define FD_TXN_PREPARE_ERR_OVERWRITE( txn_ctx ) \ 194 : txn_ctx->exec_err = 0; \ 195 : txn_ctx->exec_err_kind = 0 196 : 197 : #else 198 : 199 0 : #define FD_TXN_TEST_ERR_OVERWRITE( txn_ctx ) ( ( void )0 ) 200 0 : #define FD_TXN_PREPARE_ERR_OVERWRITE( txn_ctx ) ( ( void )0 ) 201 : 202 : #endif 203 : 204 0 : #define FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, err, idx ) (__extension__({ \ 205 0 : FD_TXN_TEST_ERR_OVERWRITE( txn_ctx ); \ 206 0 : txn_ctx->exec_err = err; \ 207 0 : txn_ctx->exec_err_kind = FD_EXECUTOR_ERR_KIND_INSTR; \ 208 0 : txn_ctx->instr_err_idx = idx; \ 209 0 : })) 210 : 211 : void * 212 : fd_exec_txn_ctx_new( void * mem ); 213 : 214 : fd_exec_txn_ctx_t * 215 : fd_exec_txn_ctx_join( void * mem, fd_spad_t * spad, fd_wksp_t * spad_wksp ); 216 : 217 : void * 218 : fd_exec_txn_ctx_leave( fd_exec_txn_ctx_t * ctx ); 219 : 220 : void * 221 : fd_exec_txn_ctx_delete( void * mem ); 222 : 223 : /* Sets up a basic transaction ctx without a txn descriptor or txn raw. Useful 224 : for mocking transaction context objects for instructions. */ 225 : void 226 : fd_exec_txn_ctx_setup_basic( fd_exec_txn_ctx_t * ctx ); 227 : 228 : /* TODO: the constructors for the txn_ctx needs to be properly consolidated. */ 229 : void 230 : fd_exec_txn_ctx_setup( fd_exec_txn_ctx_t * ctx, 231 : fd_txn_t const * txn_descriptor, 232 : fd_rawtxn_b_t const * txn_raw ); 233 : 234 : void 235 : fd_exec_txn_ctx_teardown( fd_exec_txn_ctx_t * txn_ctx ); 236 : 237 : /* Mirrors Agave function solana_sdk::transaction_context::find_index_of_account 238 : 239 : Backward scan over transaction accounts. 240 : Returns -1 if not found. 241 : 242 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L233-L238 */ 243 : 244 : static inline int 245 : fd_exec_txn_ctx_find_index_of_account( fd_exec_txn_ctx_t const * ctx, 246 0 : fd_pubkey_t const * pubkey ) { 247 0 : for( ulong i=ctx->accounts_cnt; i>0UL; i-- ) { 248 0 : if( 0==memcmp( pubkey, &ctx->account_keys[ i-1UL ], sizeof(fd_pubkey_t) ) ) { 249 0 : return (int)(i-1UL); 250 0 : } 251 0 : } 252 0 : return -1; 253 0 : } 254 : 255 : typedef int fd_txn_account_condition_fn_t ( fd_txn_account_t * acc, 256 : fd_exec_txn_ctx_t const * ctx, 257 : ushort idx ); 258 : 259 : /* Mirrors Agave function solana_sdk::transaction_context::get_account_at_index 260 : 261 : Takes a function pointer to a condition function to check pre-conditions on the 262 : obtained account. If the condition function is NULL, the account is returned without 263 : any pre-condition checks. 264 : 265 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L223-L230 */ 266 : 267 : int 268 : fd_exec_txn_ctx_get_account_at_index( fd_exec_txn_ctx_t * ctx, 269 : ushort idx, 270 : fd_txn_account_t * * account, 271 : fd_txn_account_condition_fn_t * condition ); 272 : 273 : /* A wrapper around fd_exec_txn_ctx_get_account_at_index that obtains an 274 : account from the transaction context by its pubkey. */ 275 : 276 : int 277 : fd_exec_txn_ctx_get_account_with_key( fd_exec_txn_ctx_t * ctx, 278 : fd_pubkey_t const * pubkey, 279 : fd_txn_account_t * * account, 280 : fd_txn_account_condition_fn_t * condition ); 281 : 282 : /* Gets an executable (program data) account via its pubkey. */ 283 : 284 : int 285 : fd_exec_txn_ctx_get_executable_account( fd_exec_txn_ctx_t * ctx, 286 : fd_pubkey_t const * pubkey, 287 : fd_txn_account_t * * account, 288 : fd_txn_account_condition_fn_t * condition ); 289 : 290 : /* Mirrors Agave function solana_sdk::transaction_context::get_key_of_account_at_index 291 : 292 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L212 */ 293 : 294 : int 295 : fd_exec_txn_ctx_get_key_of_account_at_index( fd_exec_txn_ctx_t * ctx, 296 : ushort idx, 297 : fd_pubkey_t const * * key ); 298 : 299 : void 300 : fd_exec_txn_ctx_reset_return_data( fd_exec_txn_ctx_t * ctx ); 301 : 302 : /* In agave, the writable accounts cache is populated by this below function. 303 : This cache is then referenced to determine if a transaction account is 304 : writable or not. 305 : 306 : The overall logic is as follows: an account can be passed 307 : in as writable based on the signature and readonly signature as they are 308 : passed in by the transaction message. However, the account's writable 309 : status will be demoted if either of the two conditions are met: 310 : 1. If the account is in the set of reserved pubkeys 311 : 2. If the account is the program id AND the upgradeable loader account is in 312 : the set of transaction accounts. */ 313 : /* https://github.com/anza-xyz/agave/blob/v2.1.1/sdk/program/src/message/versions/v0/loaded.rs#L137-L150 */ 314 : 315 : int 316 : fd_exec_txn_ctx_account_is_writable_idx( fd_exec_txn_ctx_t const * txn_ctx, ushort idx ); 317 : 318 : /* This flat function does the same as the function above, but uses the 319 : exact arguments needed instead of the full fd_exec_txn_ctx_t */ 320 : 321 : int 322 : fd_exec_txn_account_is_writable_idx_flat( const ulong slot, 323 : const ushort idx, 324 : const fd_pubkey_t * addr_at_idx, 325 : const fd_txn_t * txn_descriptor, 326 : const fd_features_t * features, 327 : const uint bpf_upgradeable_in_txn ); 328 : 329 : /* The bpf_upgradeable_in_txn argument of the above function can be 330 : obtained by the function below */ 331 : uint 332 : fd_txn_account_has_bpf_loader_upgradeable( const fd_pubkey_t * account_keys, 333 : const ulong accounts_cnt ); 334 : 335 : 336 : /* Account pre-condition filtering functions 337 : 338 : Used to filter accounts based on pre-conditions such as existence, is_writable, etc. 339 : when obtaining accounts from the transaction context. Passed as a function pointer. */ 340 : 341 : int 342 : fd_txn_account_check_exists( fd_txn_account_t * acc, 343 : fd_exec_txn_ctx_t const * ctx, 344 : ushort idx ); 345 : 346 : int 347 : fd_txn_account_check_is_writable( fd_txn_account_t * acc, 348 : fd_exec_txn_ctx_t const * ctx, 349 : ushort idx ); 350 : 351 : /* The fee payer is a valid modifiable account if it is passed in as writable 352 : in the message via a valid signature. We ignore if the account has been 353 : demoted or not (see fd_exec_txn_ctx_account_is_writable_idx) for more details. 354 : Agave and Firedancer will reject the fee payer if the transaction message 355 : doesn't have a writable signature. */ 356 : 357 : int 358 : fd_txn_account_check_fee_payer_writable( fd_txn_account_t * acc, 359 : fd_exec_txn_ctx_t const * ctx, 360 : ushort idx ); 361 : 362 : /* Checks if the account is mutable and borrows the account mutably. 363 : 364 : The borrow is an acquired write on the account object. 365 : The caller is responsible for releasing the write via 366 : fd_txn_account_release_write. 367 : 368 : TODO: Agave doesn't need to check if the account is mutable 369 : because it uses Writable/Readable traits for accounts. We 370 : should have a similar concept to abstract away fd_txn_account_t's 371 : const_meta and meta fields. */ 372 : 373 : int 374 : fd_txn_account_check_borrow_mut( fd_txn_account_t * acc, 375 : fd_exec_txn_ctx_t const * ctx, 376 : ushort idx ); 377 : 378 : 379 : FD_PROTOTYPES_END 380 : 381 : #endif /* HEADER_fd_src_flamenco_runtime_context_fd_exec_txn_ctx_h */