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