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 "../../progcache/fd_progcache_user.h"
11 : #include "../fd_compute_budget_details.h"
12 : #include "../../../disco/pack/fd_microblock.h"
13 : #include "../../../disco/pack/fd_pack.h"
14 :
15 : /* Return data for syscalls */
16 :
17 : struct fd_txn_return_data {
18 : fd_pubkey_t program_id;
19 : ulong len;
20 : uchar data[1024];
21 : };
22 :
23 : typedef struct fd_txn_return_data fd_txn_return_data_t;
24 :
25 : /* fd_exec_txn_ctx_t is the context needed to execute a transaction. */
26 :
27 : /* An entry in the instruction trace */
28 : struct fd_exec_instr_trace_entry {
29 : /* Metadata about the instruction */
30 : fd_instr_info_t * instr_info;
31 : /* Stack height when this instruction was pushed onto the stack (including itself)
32 : https://github.com/anza-xyz/agave/blob/d87e23d8d91c32d5f2be2bb3557c730bee1e9434/sdk/src/transaction_context.rs#L475-L480 */
33 : ulong stack_height;
34 : };
35 : typedef struct fd_exec_instr_trace_entry fd_exec_instr_trace_entry_t;
36 :
37 : /* https://github.com/anza-xyz/agave/blob/0d34a1a160129c4293dac248e14231e9e773b4ce/program-runtime/src/compute_budget.rs#L139 */
38 : #define FD_MAX_INSTRUCTION_TRACE_LENGTH (64UL)
39 : /* https://github.com/anza-xyz/agave/blob/f70ab5598ccd86b216c3928e4397bf4a5b58d723/compute-budget/src/compute_budget.rs#L13 */
40 0 : #define FD_MAX_INSTRUCTION_STACK_DEPTH (5UL)
41 :
42 : struct fd_exec_txn_ctx {
43 : /* Input fields: memory, bank, acc db, funk, prog cache, and txn */
44 : fd_bank_t * bank;
45 : fd_txncache_t * status_cache;
46 : fd_funk_t funk[1];
47 : fd_funk_txn_xid_t xid[1];
48 : fd_progcache_t * progcache;
49 : fd_progcache_t _progcache[1];
50 : fd_txn_p_t txn;
51 : fd_exec_stack_t * exec_stack;
52 : fd_exec_accounts_t * exec_accounts;
53 : fd_bank_hash_cmp_t * bank_hash_cmp;
54 :
55 : /* During sanitization, v0 transactions are allowed to have up to 256 accounts:
56 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/sdk/program/src/message/versions/v0/mod.rs#L139
57 : Nonetheless, when Agave prepares a sanitized batch for execution and tries to lock accounts, a lower limit is enforced:
58 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L118
59 : That is the limit we are going to use here. */
60 : struct {
61 : ulong accounts_cnt; /* Number of account pubkeys accessed by this transaction. */
62 : fd_pubkey_t account_keys[ MAX_TX_ACCOUNT_LOCKS ]; /* Array of account pubkeys accessed by this transaction. */
63 : fd_txn_account_t accounts[ MAX_TX_ACCOUNT_LOCKS ]; /* Array of borrowed accounts accessed by this transaction. */
64 : ulong executable_cnt; /* Number of BPF upgradeable loader accounts. */
65 : fd_txn_account_t executable_accounts[ MAX_TX_ACCOUNT_LOCKS ]; /* Array of BPF upgradeable loader program data accounts */
66 : /* The next three fields describe Agave's "rollback" accounts, which
67 : are copies of the fee payer and (if applicable) nonce account. If
68 : the transaction fails to load, the fee payer is still debited the
69 : transaction fee, and the nonce account is advanced. The fee payer
70 : must also be rolled back to its state pre-transaction, plus
71 : debited any transaction fees.
72 : This is a bit of a misnomer but Agave calls it "rollback".
73 : This is the account state that the nonce account should be in when
74 : the txn fails. It will advance the nonce account, rather than "roll
75 : back". */
76 : fd_txn_account_t rollback_nonce[ 1 ];
77 : /* If the transaction has a nonce account that must be advanced,
78 : this would be !=ULONG_MAX. */
79 : ulong nonce_idx_in_txn;
80 : fd_txn_account_t rollback_fee_payer[ 1 ];
81 :
82 : } accounts;
83 :
84 : struct {
85 : uchar stack_sz; /* Current depth of the instruction execution stack. */
86 : fd_exec_instr_ctx_t stack[FD_MAX_INSTRUCTION_STACK_DEPTH]; /* Instruction execution stack. */
87 : /* The memory for all of the instructions in the transaction
88 : (including CPI instructions) are preallocated. However, the order
89 : in which the instructions are executed does not match the order in
90 : which they are allocated. The instr_trace will instead be used to
91 : track the order in which the instructions are executed. We leave
92 : space for an extra instruction to account for the case where the
93 : transaction has too many instructions leading to
94 : FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED.
95 : TODO: In reality, we should just be allocating instr_infos per
96 : instruction and not up front. The dependency on using instr_info
97 : for the sysvar instruction setup is not needed and should be
98 : removed. At this point, instr_info snad instr_trace should be
99 : combined. */
100 : fd_instr_info_t infos[FD_MAX_INSTRUCTION_TRACE_LENGTH * 2UL];
101 : ulong info_cnt;
102 : fd_exec_instr_trace_entry_t trace[FD_MAX_INSTRUCTION_TRACE_LENGTH]; /* Instruction trace */
103 : ulong trace_length; /* Number of instructions in the trace */
104 : /* The current instruction index being executed */
105 : int current_idx;
106 : } instr;
107 :
108 : struct {
109 : int is_bundle;
110 : fd_exec_txn_ctx_t * prev_txn_ctxs[ FD_PACK_MAX_TXN_PER_BUNDLE ];
111 : ulong prev_txn_ctxs_cnt;
112 : } bundle;
113 :
114 : struct {
115 : int is_committable;
116 : int is_fees_only;
117 : int txn_err;
118 : /* These are error fields produced by instruction execution
119 : when txn_err == FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR (-9). */
120 : int exec_err;
121 : int exec_err_kind;
122 : int exec_err_idx;
123 : uint custom_err;
124 : } err;
125 :
126 : struct {
127 : fd_compute_budget_details_t compute_budget; /* Compute budget details */
128 : ulong loaded_accounts_data_size; /* The actual transaction loaded data size */
129 : ulong loaded_accounts_data_size_cost; /* The cost of the loaded accounts data size in CUs */
130 : ulong accounts_resize_delta; /* Transaction level tracking for account resizing */
131 : fd_txn_return_data_t return_data; /* Data returned from `return_data` syscalls */
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 : /* When a program is deployed or upgraded, we must queue it to be
136 : updated in the program cache (if it exists already) so that
137 : the cache entry's ELF / sBPF information can be updated for
138 : future executions. We keep an array of pubkeys for the
139 : transaction to track which programs need to be reverified. The
140 : actual queueing for reverification is done in the transaction
141 : finalization step. */
142 : uchar programs_to_reverify_cnt;
143 : fd_pubkey_t programs_to_reverify[ MAX_TX_ACCOUNT_LOCKS ];
144 : } details;
145 :
146 : struct {
147 : int enable_exec_recording;
148 : fd_log_collector_t log_collector; /* Log collector instance */
149 : fd_capture_ctx_t * capture_ctx;
150 : /* Pointer to buffer used for dumping instructions and transactions
151 : into protobuf files. */
152 : uchar * dumping_mem;
153 : /* Pointer to buffer used for tracing instructions and transactions
154 : into protobuf files. */
155 : int enable_vm_tracing;
156 : uchar * tracing_mem;
157 : } log;
158 : };
159 :
160 0 : #define FD_EXEC_TXN_CTX_ALIGN (alignof(fd_exec_txn_ctx_t))
161 0 : #define FD_EXEC_TXN_CTX_FOOTPRINT ( sizeof(fd_exec_txn_ctx_t))
162 :
163 : FD_PROTOTYPES_BEGIN
164 :
165 : /* Error logging handholding assertions */
166 :
167 : #ifdef FD_RUNTIME_ERR_HANDHOLDING
168 :
169 : /* Asserts that the error and error kind are not populated (zero) */
170 : #define FD_TXN_TEST_ERR_OVERWRITE( txn_ctx ) \
171 : FD_TEST( !txn_ctx->err.exec_err ); \
172 : FD_TEST( !txn_ctx->err.exec_err_kind )
173 :
174 : /* Used prior to a FD_TXN_ERR_FOR_LOG_INSTR call to deliberately
175 : bypass overwrite handholding checks.
176 : Only use this if you know what you're doing. */
177 : #define FD_TXN_PREPARE_ERR_OVERWRITE( txn_ctx ) \
178 : txn_ctx->err.exec_err = 0; \
179 : txn_ctx->err.exec_err_kind = 0
180 :
181 : #else
182 :
183 0 : #define FD_TXN_TEST_ERR_OVERWRITE( txn_ctx ) ( ( void )0 )
184 0 : #define FD_TXN_PREPARE_ERR_OVERWRITE( txn_ctx ) ( ( void )0 )
185 :
186 : #endif
187 :
188 0 : #define FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, err_, idx ) (__extension__({ \
189 0 : FD_TXN_TEST_ERR_OVERWRITE( txn_ctx ); \
190 0 : txn_ctx->err.exec_err = err_; \
191 0 : txn_ctx->err.exec_err_kind = FD_EXECUTOR_ERR_KIND_INSTR; \
192 0 : txn_ctx->err.exec_err_idx = idx; \
193 0 : }))
194 :
195 : void *
196 : fd_exec_txn_ctx_new( void * mem );
197 :
198 : fd_exec_txn_ctx_t *
199 : fd_exec_txn_ctx_join( void * mem );
200 :
201 : void *
202 : fd_exec_txn_ctx_leave( fd_exec_txn_ctx_t * ctx );
203 :
204 : void *
205 : fd_exec_txn_ctx_delete( void * mem );
206 :
207 : /* Mirrors Agave function solana_sdk::transaction_context::find_index_of_account
208 :
209 : Backward scan over transaction accounts.
210 : Returns -1 if not found.
211 :
212 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L233-L238 */
213 :
214 : static inline int
215 : fd_exec_txn_ctx_find_index_of_account( fd_exec_txn_ctx_t const * ctx,
216 0 : fd_pubkey_t const * pubkey ) {
217 0 : for( ulong i=ctx->accounts.accounts_cnt; i>0UL; i-- ) {
218 0 : if( 0==memcmp( pubkey, &ctx->accounts.account_keys[ i-1UL ], sizeof(fd_pubkey_t) ) ) {
219 0 : return (int)(i-1UL);
220 0 : }
221 0 : }
222 0 : return -1;
223 0 : }
224 :
225 : typedef int fd_txn_account_condition_fn_t ( fd_txn_account_t * acc,
226 : fd_exec_txn_ctx_t const * ctx,
227 : ushort idx );
228 :
229 : /* Mirrors Agave function solana_sdk::transaction_context::get_account_at_index
230 :
231 : Takes a function pointer to a condition function to check pre-conditions on the
232 : obtained account. If the condition function is NULL, the account is returned without
233 : any pre-condition checks.
234 :
235 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L223-L230 */
236 :
237 : int
238 : fd_exec_txn_ctx_get_account_at_index( fd_exec_txn_ctx_t * ctx,
239 : ushort idx,
240 : fd_txn_account_t * * account,
241 : fd_txn_account_condition_fn_t * condition );
242 :
243 : /* A wrapper around fd_exec_txn_ctx_get_account_at_index that obtains an
244 : account from the transaction context by its pubkey. */
245 :
246 : int
247 : fd_exec_txn_ctx_get_account_with_key( fd_exec_txn_ctx_t * ctx,
248 : fd_pubkey_t const * pubkey,
249 : fd_txn_account_t * * account,
250 : fd_txn_account_condition_fn_t * condition );
251 :
252 : /* Gets an executable (program data) account via its pubkey. */
253 :
254 : int
255 : fd_exec_txn_ctx_get_executable_account( fd_exec_txn_ctx_t * ctx,
256 : fd_pubkey_t const * pubkey,
257 : fd_txn_account_t * * account,
258 : fd_txn_account_condition_fn_t * condition );
259 :
260 : /* Mirrors Agave function solana_sdk::transaction_context::get_key_of_account_at_index
261 :
262 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L212 */
263 :
264 : int
265 : fd_exec_txn_ctx_get_key_of_account_at_index( fd_exec_txn_ctx_t * ctx,
266 : ushort idx,
267 : fd_pubkey_t const * * key );
268 :
269 : /* In agave, the writable accounts cache is populated by this below function.
270 : This cache is then referenced to determine if a transaction account is
271 : writable or not.
272 :
273 : The overall logic is as follows: an account can be passed
274 : in as writable based on the signature and readonly signature as they are
275 : passed in by the transaction message. However, the account's writable
276 : status will be demoted if either of the two conditions are met:
277 : 1. If the account is in the set of reserved pubkeys
278 : 2. If the account is the program id AND the upgradeable loader account is in
279 : the set of transaction accounts. */
280 : /* https://github.com/anza-xyz/agave/blob/v2.1.1/sdk/program/src/message/versions/v0/loaded.rs#L137-L150 */
281 :
282 : int
283 : fd_exec_txn_ctx_account_is_writable_idx( fd_exec_txn_ctx_t const * txn_ctx, ushort idx );
284 :
285 : /* This flat function does the same as the function above, but uses the
286 : exact arguments needed instead of the full fd_exec_txn_ctx_t */
287 :
288 : int
289 : fd_exec_txn_account_is_writable_idx_flat( const ulong slot,
290 : const ushort idx,
291 : const fd_pubkey_t * addr_at_idx,
292 : const fd_txn_t * txn_descriptor,
293 : const fd_features_t * features,
294 : const uint bpf_upgradeable_in_txn );
295 :
296 : /* The bpf_upgradeable_in_txn argument of the above function can be
297 : obtained by the function below */
298 : uint
299 : fd_txn_account_has_bpf_loader_upgradeable( const fd_pubkey_t * account_keys,
300 : const ulong accounts_cnt );
301 :
302 :
303 : /* Account pre-condition filtering functions
304 :
305 : Used to filter accounts based on pre-conditions such as existence, is_writable, etc.
306 : when obtaining accounts from the transaction context. Passed as a function pointer. */
307 :
308 : int
309 : fd_txn_account_check_exists( fd_txn_account_t * acc,
310 : fd_exec_txn_ctx_t const * ctx,
311 : ushort idx );
312 :
313 : int
314 : fd_txn_account_check_is_writable( fd_txn_account_t * acc,
315 : fd_exec_txn_ctx_t const * ctx,
316 : ushort idx );
317 :
318 : /* The fee payer is a valid modifiable account if it is passed in as writable
319 : in the message via a valid signature. We ignore if the account has been
320 : demoted or not (see fd_exec_txn_ctx_account_is_writable_idx) for more details.
321 : Agave and Firedancer will reject the fee payer if the transaction message
322 : doesn't have a writable signature. */
323 :
324 : int
325 : fd_txn_account_check_fee_payer_writable( fd_txn_account_t * acc,
326 : fd_exec_txn_ctx_t const * ctx,
327 : ushort idx );
328 :
329 : /* Checks if the account is mutable and borrows the account mutably.
330 :
331 : The borrow is an acquired write on the account object.
332 : The caller is responsible for releasing the write via
333 : fd_txn_account_release_write.
334 :
335 : TODO: Agave doesn't need to check if the account is mutable
336 : because it uses Writable/Readable traits for accounts. We
337 : should have a similar concept to abstract away fd_txn_account_t's
338 : const_meta and meta fields. */
339 :
340 : int
341 : fd_txn_account_check_borrow_mut( fd_txn_account_t * acc,
342 : fd_exec_txn_ctx_t const * ctx,
343 : ushort idx );
344 :
345 :
346 : FD_PROTOTYPES_END
347 :
348 : #endif /* HEADER_fd_src_flamenco_runtime_context_fd_exec_txn_ctx_h */
|