Line data Source code
1 : #include "fd_executor.h"
2 : #include "fd_acc_mgr.h"
3 : #include "fd_hashes.h"
4 : #include "fd_runtime_err.h"
5 : #include "context/fd_exec_slot_ctx.h"
6 : #include "context/fd_exec_txn_ctx.h"
7 : #include "context/fd_exec_instr_ctx.h"
8 :
9 : #include "../nanopb/pb_encode.h"
10 : #include "../../util/rng/fd_rng.h"
11 : #include "fd_system_ids.h"
12 : #include "fd_account.h"
13 : #include "program/fd_address_lookup_table_program.h"
14 : #include "program/fd_bpf_loader_program.h"
15 : #include "program/fd_loader_v4_program.h"
16 : #include "program/fd_compute_budget_program.h"
17 : #include "program/fd_config_program.h"
18 : #include "program/fd_precompiles.h"
19 : #include "program/fd_stake_program.h"
20 : #include "program/fd_system_program.h"
21 : #include "program/fd_vote_program.h"
22 : #include "program/fd_zk_elgamal_proof_program.h"
23 : #include "program/fd_bpf_program_util.h"
24 : #include "sysvar/fd_sysvar_cache.h"
25 : #include "sysvar/fd_sysvar_slot_history.h"
26 : #include "sysvar/fd_sysvar_epoch_schedule.h"
27 : #include "sysvar/fd_sysvar_instructions.h"
28 :
29 : #include "../../ballet/base58/fd_base58.h"
30 : #include "../../ballet/pack/fd_pack.h"
31 : #include "../../ballet/pack/fd_pack_cost.h"
32 : #include "../../ballet/sbpf/fd_sbpf_loader.h"
33 : #include "../../ballet/pack/fd_pack.h"
34 :
35 : #include "../../util/bits/fd_uwide.h"
36 :
37 : #define SORT_NAME sort_uint64_t
38 0 : #define SORT_KEY_T uint64_t
39 0 : #define SORT_BEFORE(a,b) (a)<(b)
40 : #include "../../util/tmpl/fd_sort.c"
41 :
42 : #include <assert.h>
43 : #include <errno.h>
44 : #include <stdio.h> /* snprintf(3) */
45 : #include <fcntl.h> /* openat(2) */
46 : #include <unistd.h> /* write(3) */
47 : #include <time.h>
48 :
49 : #define MAX_COMPUTE_UNITS_PER_BLOCK (48000000UL)
50 : #define MAX_COMPUTE_UNITS_PER_WRITE_LOCKED_ACCOUNT (12000000UL)
51 : /* We should strive for these max limits matching between pack and the
52 : runtime. If there's a reason for these limits to mismatch, and there
53 : could be, then someone should explicitly document that when relaxing
54 : these assertions. */
55 : FD_STATIC_ASSERT( FD_PACK_MAX_COST_PER_BLOCK==MAX_COMPUTE_UNITS_PER_BLOCK, executor_pack_max_mismatch );
56 : FD_STATIC_ASSERT( FD_PACK_MAX_WRITE_COST_PER_ACCT==MAX_COMPUTE_UNITS_PER_WRITE_LOCKED_ACCOUNT, executor_pack_max_mismatch );
57 :
58 : /* TODO: precompiles currently enter this noop function. Once the move_precompile_verification_to_svm
59 : feature gets activated, this will need to be replaced with precompile verification functions. */
60 : static int
61 915 : fd_noop_instr_execute( fd_exec_instr_ctx_t * ctx FD_PARAM_UNUSED ) {
62 915 : return FD_EXECUTOR_INSTR_SUCCESS;
63 915 : }
64 :
65 : struct fd_native_prog_info {
66 : fd_pubkey_t key;
67 : fd_exec_instr_fn_t fn;
68 : };
69 : typedef struct fd_native_prog_info fd_native_prog_info_t;
70 :
71 : #define MAP_PERFECT_NAME fd_native_program_fn_lookup_tbl
72 : #define MAP_PERFECT_LG_TBL_SZ 4
73 : #define MAP_PERFECT_T fd_native_prog_info_t
74 68793 : #define MAP_PERFECT_HASH_C 478U
75 : #define MAP_PERFECT_KEY key.uc
76 : #define MAP_PERFECT_KEY_T fd_pubkey_t const *
77 : #define MAP_PERFECT_ZERO_KEY (0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0)
78 : #define MAP_PERFECT_COMPLEX_KEY 1
79 68793 : #define MAP_PERFECT_KEYS_EQUAL(k1,k2) (!memcmp( (k1), (k2), 32UL ))
80 :
81 68793 : #define PERFECT_HASH( u ) (((MAP_PERFECT_HASH_C*(u))>>28)&0xFU)
82 :
83 : #define MAP_PERFECT_HASH_PP( a00,a01,a02,a03,a04,a05,a06,a07,a08,a09,a10,a11,a12,a13,a14,a15, \
84 : a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31) \
85 : PERFECT_HASH( (a08 | (a09<<8) | (a10<<16) | (a11<<24)) )
86 68793 : #define MAP_PERFECT_HASH_R( ptr ) PERFECT_HASH( fd_uint_load_4( (uchar const *)ptr + 8UL ) )
87 :
88 : #define MAP_PERFECT_0 ( VOTE_PROG_ID ), .fn = fd_vote_program_execute
89 : #define MAP_PERFECT_1 ( SYS_PROG_ID ), .fn = fd_system_program_execute
90 : #define MAP_PERFECT_2 ( CONFIG_PROG_ID ), .fn = fd_config_program_execute
91 : #define MAP_PERFECT_3 ( STAKE_PROG_ID ), .fn = fd_stake_program_execute
92 : #define MAP_PERFECT_4 ( COMPUTE_BUDGET_PROG_ID ), .fn = fd_compute_budget_program_execute
93 : #define MAP_PERFECT_5 ( ADDR_LUT_PROG_ID ), .fn = fd_address_lookup_table_program_execute
94 : #define MAP_PERFECT_6 ( ZK_EL_GAMAL_PROG_ID ), .fn = fd_executor_zk_elgamal_proof_program_execute
95 : #define MAP_PERFECT_7 ( BPF_LOADER_1_PROG_ID ), .fn = fd_bpf_loader_program_execute
96 : #define MAP_PERFECT_8 ( BPF_LOADER_2_PROG_ID ), .fn = fd_bpf_loader_program_execute
97 : #define MAP_PERFECT_9 ( BPF_UPGRADEABLE_PROG_ID ), .fn = fd_bpf_loader_program_execute
98 : #define MAP_PERFECT_10 ( LOADER_V4_PROG_ID ), .fn = fd_loader_v4_program_execute
99 : #define MAP_PERFECT_11 ( ED25519_SV_PROG_ID ), .fn = fd_noop_instr_execute
100 : #define MAP_PERFECT_12 ( KECCAK_SECP_PROG_ID ), .fn = fd_noop_instr_execute
101 : #define MAP_PERFECT_13 ( SECP256R1_PROG_ID ), .fn = fd_noop_instr_execute
102 :
103 : #include "../../util/tmpl/fd_map_perfect.c"
104 : #undef PERFECT_HASH
105 :
106 : /* https://github.com/anza-xyz/agave/blob/9efdd74b1b65ecfd85b0db8ad341c6bd4faddfef/program-runtime/src/invoke_context.rs#L461-L488 */
107 : fd_exec_instr_fn_t
108 68793 : fd_executor_lookup_native_program( fd_borrowed_account_t const * prog_acc ) {
109 68793 : fd_pubkey_t const * pubkey = prog_acc->pubkey;
110 68793 : fd_pubkey_t const * owner = (fd_pubkey_t const *)prog_acc->const_meta->info.owner;
111 :
112 : /* Native programs should be owned by the native loader...
113 : This will not be the case though once core programs are migrated to BPF. */
114 68793 : int is_native_program = !memcmp( owner, fd_solana_native_loader_id.key, sizeof(fd_pubkey_t) );
115 68793 : if( FD_UNLIKELY( !memcmp( pubkey, fd_solana_ed25519_sig_verify_program_id.key, sizeof(fd_pubkey_t) ) &&
116 68793 : !memcmp( owner, fd_solana_system_program_id.key, sizeof(fd_pubkey_t) ) ) ) {
117 : /* ... except for the special case for testnet ed25519, which is
118 : bizarrely owned by the system program. */
119 0 : is_native_program = 1;
120 0 : }
121 68793 : fd_pubkey_t const * lookup_pubkey = is_native_program ? pubkey : owner;
122 68793 : const fd_native_prog_info_t null_function = (const fd_native_prog_info_t) {0};
123 68793 : return fd_native_program_fn_lookup_tbl_query( lookup_pubkey, &null_function )->fn;
124 68793 : }
125 :
126 : /* Returns 1 if the sysvar instruction is used, 0 otherwise */
127 : uint
128 5190 : fd_executor_txn_uses_sysvar_instructions( fd_exec_txn_ctx_t const * txn_ctx ) {
129 34032 : for( ulong i = 0; i < txn_ctx->accounts_cnt; i++ ) {
130 28845 : if( FD_UNLIKELY( memcmp( txn_ctx->accounts[i].key, fd_sysvar_instructions_id.key, sizeof(fd_pubkey_t) ) == 0 ) ) {
131 3 : return 1;
132 3 : }
133 28845 : }
134 :
135 5187 : return 0;
136 5190 : }
137 :
138 : int
139 6792 : fd_executor_is_system_nonce_account( fd_borrowed_account_t * account ) {
140 6792 : FD_SCRATCH_SCOPE_BEGIN {
141 6792 : if ( memcmp( account->const_meta->info.owner, fd_solana_system_program_id.uc, sizeof(fd_pubkey_t) ) == 0 ) {
142 6786 : if ( account->const_meta->dlen == 0 ) {
143 6711 : return 0;
144 6711 : } else if ( account->const_meta->dlen == 80 ) { // TODO: nonce size macro
145 72 : fd_bincode_decode_ctx_t decode = { .data = account->const_data,
146 72 : .dataend = account->const_data + account->const_meta->dlen,
147 72 : .valloc = fd_scratch_virtual() };
148 72 : fd_nonce_state_versions_t nonce_versions;
149 72 : if (fd_nonce_state_versions_decode( &nonce_versions, &decode ) != 0 ) {
150 6 : return -1;
151 6 : }
152 66 : fd_nonce_state_t * state;;
153 66 : if ( fd_nonce_state_versions_is_current( &nonce_versions ) ) {
154 60 : state = &nonce_versions.inner.current;
155 60 : } else {
156 6 : state = &nonce_versions.inner.legacy;
157 6 : }
158 :
159 66 : if ( fd_nonce_state_is_initialized( state ) ) {
160 60 : return 1;
161 60 : }
162 66 : }
163 6786 : }
164 :
165 15 : return -1;
166 6792 : } FD_SCRATCH_SCOPE_END;
167 6792 : }
168 :
169 : static int
170 6726 : check_rent_transition( fd_borrowed_account_t * account, fd_rent_t const * rent, ulong fee ) {
171 6726 : ulong min_balance = fd_rent_exempt_minimum_balance( rent, account->const_meta->dlen );
172 6726 : ulong pre_lamports = account->const_meta->info.lamports;
173 6726 : uchar pre_is_exempt = pre_lamports >= min_balance;
174 :
175 6726 : ulong post_lamports = pre_lamports - fee;
176 6726 : uchar post_is_exempt = post_lamports >= min_balance;
177 :
178 6726 : if ( post_lamports == 0 || post_is_exempt ) {
179 6648 : return 1;
180 6648 : }
181 :
182 78 : if ( pre_lamports == 0 || pre_is_exempt ) {
183 0 : return 0;
184 0 : }
185 :
186 78 : return post_lamports <= pre_lamports;
187 78 : }
188 :
189 : /* https://github.com/anza-xyz/agave/blob/v2.0.2/svm/src/account_loader.rs#L103 */
190 : int
191 6831 : fd_validate_fee_payer( fd_borrowed_account_t * account, fd_rent_t const * rent, ulong fee ) {
192 6831 : if( FD_UNLIKELY( account->const_meta->info.lamports==0UL ) ) {
193 39 : return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
194 39 : }
195 :
196 6792 : ulong min_balance = 0UL;
197 :
198 6792 : int is_nonce = fd_executor_is_system_nonce_account( account );
199 6792 : if ( FD_UNLIKELY( is_nonce<0 ) ) {
200 21 : return FD_RUNTIME_TXN_ERR_INVALID_ACCOUNT_FOR_FEE;
201 21 : }
202 :
203 6771 : if( is_nonce ) {
204 60 : min_balance = fd_rent_exempt_minimum_balance( rent, 80 );
205 60 : }
206 :
207 6771 : ulong out = ULONG_MAX;
208 6771 : int cf = fd_ulong_checked_sub( account->const_meta->info.lamports, min_balance, &out);
209 6771 : if( FD_UNLIKELY( cf!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
210 6 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_FEE;
211 6 : }
212 :
213 6765 : cf = fd_ulong_checked_sub( out, fee, &out );
214 6765 : if( FD_UNLIKELY( cf!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
215 39 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_FEE;
216 39 : }
217 :
218 6726 : if( FD_UNLIKELY( account->const_meta->info.lamports<fee ) ) {
219 0 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_FEE;
220 6726 : } else if( FD_UNLIKELY( memcmp( account->pubkey->key, fd_sysvar_incinerator_id.key, sizeof(fd_pubkey_t) ) != 0 &&
221 6726 : !check_rent_transition( account, rent, fee ) ) ) {
222 0 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
223 0 : }
224 :
225 6726 : return FD_RUNTIME_EXECUTE_SUCCESS;
226 6726 : }
227 :
228 : static int
229 0 : status_check_tower( ulong slot, void * _ctx ) {
230 0 : fd_exec_slot_ctx_t * ctx = (fd_exec_slot_ctx_t *)_ctx;
231 0 : if( slot==ctx->slot_bank.slot ) {
232 0 : return 1;
233 0 : }
234 :
235 0 : if( fd_txncache_is_rooted_slot( ctx->status_cache, slot ) ) {
236 0 : return 1;
237 0 : }
238 :
239 0 : if( fd_sysvar_slot_history_find_slot( ctx->slot_history, slot ) == FD_SLOT_HISTORY_SLOT_FOUND ) {
240 0 : return 1;
241 0 : }
242 :
243 0 : return 0;
244 0 : }
245 :
246 : static int
247 6930 : fd_executor_check_status_cache( fd_exec_txn_ctx_t * txn_ctx ) {
248 :
249 6930 : if( FD_UNLIKELY( !txn_ctx->slot_ctx->status_cache ) ) {
250 6930 : return FD_RUNTIME_EXECUTE_SUCCESS;
251 6930 : }
252 :
253 0 : fd_hash_t * blockhash = (fd_hash_t *)((uchar *)txn_ctx->_txn_raw->raw + txn_ctx->txn_descriptor->recent_blockhash_off);
254 :
255 0 : fd_txncache_query_t curr_query;
256 0 : curr_query.blockhash = blockhash->uc;
257 0 : fd_blake3_t b3[1];
258 :
259 : /* Compute the blake3 hash of the transaction message
260 : https://github.com/anza-xyz/agave/blob/v2.1.7/sdk/program/src/message/versions/mod.rs#L159-L167 */
261 0 : fd_blake3_init( b3 );
262 0 : fd_blake3_append( b3, "solana-tx-message-v1", 20UL );
263 0 : fd_blake3_append( b3, ((uchar *)txn_ctx->_txn_raw->raw + txn_ctx->txn_descriptor->message_off),(ulong)( txn_ctx->_txn_raw->txn_sz - txn_ctx->txn_descriptor->message_off ) );
264 0 : fd_blake3_fini( b3, &txn_ctx->blake_txn_msg_hash );
265 0 : curr_query.txnhash = txn_ctx->blake_txn_msg_hash.uc;
266 :
267 : // TODO: figure out if it is faster to batch query properly and loop all txns again
268 0 : int err;
269 0 : fd_txncache_query_batch( txn_ctx->slot_ctx->status_cache, &curr_query, 1UL, (void *)txn_ctx->slot_ctx, status_check_tower, &err );
270 0 : return err;
271 6930 : }
272 :
273 : /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/runtime/src/bank.rs#L3596-L3605 */
274 : int
275 7971 : fd_executor_check_transactions( fd_exec_txn_ctx_t * txn_ctx ) {
276 : /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/runtime/src/bank.rs#L3603 */
277 7971 : int err = fd_check_transaction_age( txn_ctx );
278 7971 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
279 1041 : return err;
280 1041 : }
281 :
282 : /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/runtime/src/bank.rs#L3604 */
283 6930 : err = fd_executor_check_status_cache( txn_ctx );
284 6930 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
285 0 : return err;
286 0 : }
287 :
288 6930 : return FD_RUNTIME_EXECUTE_SUCCESS;
289 6930 : }
290 :
291 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/verify_precompiles.rs#L11-L34 */
292 : int
293 12477 : fd_executor_verify_precompiles( fd_exec_txn_ctx_t * txn_ctx ) {
294 12477 : ushort instr_cnt = txn_ctx->txn_descriptor->instr_cnt;
295 12477 : fd_acct_addr_t const * tx_accs = fd_txn_get_acct_addrs( txn_ctx->txn_descriptor, txn_ctx->_txn_raw->raw );
296 12477 : int err = 0;
297 26049 : for( ushort i=0; i<instr_cnt; i++ ) {
298 17685 : fd_txn_instr_t const * instr = &txn_ctx->txn_descriptor->instr[i];
299 17685 : fd_acct_addr_t const * program_id = tx_accs + instr->program_id;
300 17685 : if( !memcmp( program_id, &fd_solana_ed25519_sig_verify_program_id, sizeof(fd_pubkey_t) ) ) {
301 3687 : err = fd_precompile_ed25519_verify( txn_ctx, instr );
302 3687 : if( FD_UNLIKELY( err ) ) {
303 3144 : FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, err, i );
304 3144 : return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR;
305 3144 : }
306 13998 : } else if( !memcmp( program_id, &fd_solana_keccak_secp_256k_program_id, sizeof(fd_pubkey_t) )) {
307 393 : err = fd_precompile_secp256k1_verify( txn_ctx, instr );
308 393 : if( FD_UNLIKELY( err ) ) {
309 315 : FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, err, i );
310 315 : return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR;
311 315 : }
312 13605 : } else if( !memcmp( program_id, &fd_solana_secp256r1_program_id, sizeof(fd_pubkey_t)) && FD_FEATURE_ACTIVE( txn_ctx->slot_ctx, enable_secp256r1_precompile ) ) {
313 1071 : err = fd_precompile_secp256r1_verify( txn_ctx, instr );
314 1071 : if( FD_UNLIKELY( err ) ) {
315 654 : FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, err, i );
316 654 : return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR;
317 654 : }
318 1071 : }
319 17685 : }
320 8364 : return FD_RUNTIME_EXECUTE_SUCCESS;
321 12477 : }
322 :
323 :
324 : /* Only accounts in the transaction account keys that are owned by one of the four
325 : loaders (bpf v1, v2, v3, v4) are iterated over in Agave's replenish_program_cache()
326 : function to be loaded into the program cache. An account may be in the program cache
327 : iff the owners match one of the four loaders since `filter_executable_program_accounts()`
328 : filters out all other accounts here:
329 : https://github.com/anza-xyz/agave/blob/v2.1/svm/src/transaction_processor.rs#L530-L560
330 :
331 : If this check holds true, the account is promoted to an executable account within
332 : `fd_execute_load_transaction_accounts()`, which sadly involves modifying its read-only metadata
333 : to set the `executable` flag to true.
334 :
335 : Note that although the v4 loader is not yet activated, Agave still checks that the
336 : owner matches one of the four bpf loaders provided in the hyperlink below
337 : within `filter_executable_program_accounts()`:
338 : https://github.com/anza-xyz/agave/blob/v2.1/sdk/account/src/lib.rs#L800-L806 */
339 : FD_FN_PURE static inline int
340 450 : is_maybe_in_loaded_program_cache( fd_borrowed_account_t const * acct ) {
341 450 : return !memcmp( acct->const_meta->info.owner, fd_solana_bpf_loader_deprecated_program_id.key, sizeof(fd_pubkey_t) ) ||
342 450 : !memcmp( acct->const_meta->info.owner, fd_solana_bpf_loader_program_id.key, sizeof(fd_pubkey_t) ) ||
343 450 : !memcmp( acct->const_meta->info.owner, fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) ||
344 450 : !memcmp( acct->const_meta->info.owner, fd_solana_bpf_loader_v4_program_id.key, sizeof(fd_pubkey_t) );
345 450 : }
346 :
347 : /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L410-427 */
348 : static int
349 : accumulate_and_check_loaded_account_data_size( ulong acc_size,
350 : ulong requested_loaded_accounts_data_size,
351 35682 : ulong * accumulated_account_size ) {
352 35682 : *accumulated_account_size = fd_ulong_sat_add( *accumulated_account_size, acc_size );
353 35682 : if( FD_UNLIKELY( *accumulated_account_size>requested_loaded_accounts_data_size ) ) {
354 0 : return FD_RUNTIME_TXN_ERR_MAX_LOADED_ACCOUNTS_DATA_SIZE_EXCEEDED;
355 0 : }
356 35682 : return FD_RUNTIME_EXECUTE_SUCCESS;
357 35682 : }
358 :
359 : /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L191-L372 */
360 : int
361 6726 : fd_executor_load_transaction_accounts( fd_exec_txn_ctx_t * txn_ctx ) {
362 :
363 6726 : ulong requested_loaded_accounts_data_size = txn_ctx->loaded_accounts_data_size_limit;
364 6726 : ulong accumulated_account_size = 0UL;
365 6726 : fd_rawtxn_b_t const * txn_raw = txn_ctx->_txn_raw;
366 6726 : ushort instr_cnt = txn_ctx->txn_descriptor->instr_cnt;
367 :
368 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L323-L337
369 :
370 : In the agave client, this big chunk of code is responsible for loading in all of the
371 : accounts in the transaction, mimicking each call to `load_transaction_account()`
372 : (https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L406-L497)
373 :
374 : This contains a LOT of special casing as their accounts database and program cache
375 : is handled very differently than the FD client.
376 :
377 : The logic is as follows:
378 : 1. If the account is the instructions sysvar, then load in the compiled
379 : instructions from the transactions into the sysvar's data.
380 : 2. If the account is a fee payer, then it is already loaded.
381 : 3. If the account is an account override, then handle seperately. Account
382 : overrides are used for simulating transactions.
383 : - This is only used for testing.
384 : 4. If the account is not writable and not an instruction account and it is
385 : in the loaded program cache, then load in a dummy account with the
386 : correct owner and the executable flag set to true.
387 : - See comments below
388 : 5. Otherwise load in the account from the accounts DB. If the account is
389 : writable try to collect rent from the account.
390 :
391 : After the account is loaded, accumulate the data size to make sure the
392 : transaction doesn't violate the transaction loading limit.
393 :
394 : In the firedancer client only some of these steps are necessary because
395 : all of the accounts are loaded in from the accounts db into borrowed
396 : accounts already.
397 : 1. If the account is writable, try to collect fees on the account. Unlike
398 : the agave client, this is also done on the fee payer account. The agave
399 : client tries to collect rent on the fee payer while the fee is being
400 : collected in validate_fees().
401 : 2. If the account is not writable and it is not an instruction account
402 : and would be in the loaded program cache, then it should be replaced
403 : with a dummy value.
404 : */
405 :
406 6726 : fd_epoch_schedule_t const * schedule = fd_sysvar_cache_epoch_schedule( txn_ctx->slot_ctx->sysvar_cache );
407 6726 : ulong epoch = fd_slot_to_epoch( schedule, txn_ctx->slot_ctx->slot_bank.slot, NULL );
408 :
409 : /* In `load_transaction_account()`, there are special checks based on the priviledges of each account key.
410 : We precompute the results here before proceeding with the special casing. Note the start range of 1 since
411 : `load_transaction_account()` doesn't handle the fee payer. */
412 6726 : uchar txn_account_is_instruction_account[MAX_TX_ACCOUNT_LOCKS] = {0};
413 18066 : for( ushort i=0UL; i<instr_cnt; i++ ) {
414 : /* Set up the instr infos for the transaction */
415 11340 : fd_txn_instr_t const * instr = &txn_ctx->txn_descriptor->instr[i];
416 11340 : fd_convert_txn_instr_to_instr( txn_ctx, instr, txn_ctx->borrowed_accounts, &txn_ctx->instr_infos[i] );
417 :
418 11340 : uchar const * instr_acc_idxs = fd_txn_get_instr_accts( instr, txn_raw->raw );
419 49719 : for( ushort j=0; j<instr->acct_cnt; j++ ) {
420 38379 : uchar txn_acc_idx = instr_acc_idxs[j];
421 38379 : txn_account_is_instruction_account[txn_acc_idx] = 1;
422 38379 : }
423 11340 : }
424 6726 : txn_ctx->instr_info_cnt = txn_ctx->txn_descriptor->instr_cnt;
425 :
426 40116 : for( ulong i=1UL; i<txn_ctx->accounts_cnt; i++ ) {
427 33390 : fd_borrowed_account_t * acct = &txn_ctx->borrowed_accounts[i];
428 :
429 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L418-L421 */
430 33390 : uchar is_instruction_account = txn_account_is_instruction_account[i];
431 33390 : uchar is_writable = !!( fd_txn_account_is_writable_idx( txn_ctx, (int)i ) );
432 :
433 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L417 */
434 :
435 33390 : int err = fd_txn_borrowed_account_view_idx( txn_ctx, (uchar)i, &acct );
436 33390 : ulong acc_size = err==FD_ACC_MGR_SUCCESS ? acct->const_meta->dlen : 0UL;
437 :
438 : /* `load_transaction_account()`
439 : https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L406-L497 */
440 :
441 : /* First case: checking if the account is the instructions sysvar
442 : https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L422-L429 */
443 33390 : if( FD_UNLIKELY( !memcmp( acct->pubkey->key, fd_sysvar_instructions_id.key, sizeof(fd_pubkey_t) ) ) ) {
444 93 : acct->account_found = 1;
445 93 : fd_sysvar_instructions_serialize_account( txn_ctx, (fd_instr_info_t const *)txn_ctx->instr_infos, txn_ctx->txn_descriptor->instr_cnt );
446 : /* Continue because this should not be counted towards the total loaded account size.
447 : https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L426 */
448 93 : continue;
449 93 : }
450 :
451 : /* Second case: loading a program account that is not writable, not an instruction account,
452 : not already executable, and may be in the loaded program cache. We bypass this special casing
453 : if `disable_account_loader_special_case` is active.
454 : https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L438-L451 */
455 33297 : if( FD_UNLIKELY( !fd_account_is_executable( acct->const_meta ) &&
456 33297 : !is_instruction_account && !is_writable &&
457 33297 : !FD_FEATURE_ACTIVE( txn_ctx->slot_ctx, disable_account_loader_special_case ) &&
458 33297 : is_maybe_in_loaded_program_cache( acct ) ) ) {
459 : /* In the corresponding branch in the agave client, a dummy account is loaded in that has the
460 : executable flag set to true. This is a hack to mirror those semantics.
461 : https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L499-L507 */
462 135 : void * borrowed_account_data = fd_spad_alloc( txn_ctx->spad, FD_ACCOUNT_REC_ALIGN, FD_ACC_TOT_SZ_MAX );
463 135 : fd_borrowed_account_make_readonly_copy( acct, borrowed_account_data );
464 135 : fd_account_meta_t * meta = (fd_account_meta_t *)acct->const_meta;
465 135 : meta->info.executable = 1;
466 135 : acct->account_found = 1;
467 135 : }
468 : /* Third case: Default case
469 : https://github.com/anza-xyz/agave/blob/v2.1.0/svm/src/account_loader.rs#L452-L494 */
470 33162 : else {
471 : /* If the account exists and is writable, collect rent from it. */
472 33162 : if( FD_LIKELY( acct->account_found ) ) {
473 30984 : if( is_writable ) {
474 11280 : txn_ctx->collected_rent += fd_runtime_collect_rent_from_account( txn_ctx->slot_ctx, acct->meta, acct->pubkey, epoch );
475 11280 : acct->starting_lamports = acct->meta->info.lamports;
476 11280 : }
477 30984 : }
478 33162 : }
479 :
480 33297 : err = accumulate_and_check_loaded_account_data_size( acc_size,
481 33297 : requested_loaded_accounts_data_size,
482 33297 : &accumulated_account_size );
483 :
484 33297 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
485 0 : return err;
486 0 : }
487 33297 : }
488 :
489 6726 : fd_pubkey_t program_owners[instr_cnt];
490 6726 : ushort program_owners_cnt = 0;
491 :
492 : /* The logic below handles special casing with loading instruction accounts.
493 : https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L297-L358 */
494 : /* At this point, we can set has_program_id to be 0 as the program_indices vector is of length 0 */
495 6726 : txn_ctx->has_program_id = 0;
496 15621 : for( ushort i=0; i<instr_cnt; i++ ) {
497 10431 : fd_txn_instr_t const * instr = &txn_ctx->txn_descriptor->instr[i];
498 : /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L304-306 */
499 10431 : fd_borrowed_account_t * program_account = NULL;
500 10431 : int err = fd_txn_borrowed_account_view_idx_allow_dead( txn_ctx, instr->program_id, &program_account );
501 10431 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
502 0 : return FD_RUNTIME_TXN_ERR_PROGRAM_ACCOUNT_NOT_FOUND;
503 0 : }
504 :
505 : /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L307-309 */
506 10431 : if( FD_UNLIKELY( !memcmp( program_account->pubkey->key, fd_solana_native_loader_id.key, sizeof(fd_pubkey_t) ) ) ) {
507 60 : continue;
508 60 : }
509 :
510 : /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L311-L314 */
511 10371 : if( FD_UNLIKELY( !program_account->account_found ) ) {
512 963 : return FD_RUNTIME_TXN_ERR_PROGRAM_ACCOUNT_NOT_FOUND;
513 963 : }
514 :
515 : /* The above checks from the mirrored `load_transaction_account()` function would promote
516 : this account to executable if necessary, so this check is sufficient.
517 : https://github.com/anza-xyz/agave/blob/89872fdb074e6658646b2b57a299984f0059cc84/svm/src/account_loader.rs#L493-L500 */
518 9408 : if( FD_UNLIKELY( !FD_FEATURE_ACTIVE( txn_ctx->slot_ctx, remove_accounts_executable_flag_checks ) &&
519 9408 : !fd_account_is_executable( program_account->const_meta ) ) ) {
520 120 : return FD_RUNTIME_TXN_ERR_INVALID_PROGRAM_FOR_EXECUTION;
521 120 : }
522 :
523 : /* At this point, program_indices will no longer have 0 length, so we are set this flag to 1 */
524 : /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L321 */
525 9288 : txn_ctx->has_program_id = 1;
526 :
527 : /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L322-325 */
528 9288 : if( !memcmp( program_account->const_meta->info.owner, fd_solana_native_loader_id.key, sizeof(fd_pubkey_t) ) ) {
529 6450 : continue;
530 6450 : }
531 :
532 : /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L326-330
533 : The agave client does checks on the program account's owners as well.
534 : However, it is important to not do these checks multiple times as the
535 : total size of accounts and their owners are accumulated: duplicate owners
536 : should be avoided. */
537 :
538 : /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L334-353 */
539 2838 : FD_BORROWED_ACCOUNT_DECL( owner_account );
540 2838 : err = fd_acc_mgr_view( txn_ctx->slot_ctx->acc_mgr, txn_ctx->slot_ctx->funk_txn, (fd_pubkey_t *) program_account->const_meta->info.owner, owner_account );
541 2838 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
542 450 : return FD_RUNTIME_TXN_ERR_PROGRAM_ACCOUNT_NOT_FOUND;
543 450 : }
544 :
545 2646 : for( ushort i=0; i<program_owners_cnt; i++ ) {
546 258 : if( !memcmp( program_owners[i].key, owner_account->pubkey, sizeof(fd_pubkey_t) ) ) {
547 : /* If the owner account has already been seen, skip the owner checks
548 : and do not acccumulate the account size. */
549 255 : continue;
550 255 : }
551 258 : }
552 :
553 : /* https://github.com/anza-xyz/agave/blob/89872fdb074e6658646b2b57a299984f0059cc84/svm/src/account_loader.rs#L537-L545 */
554 2388 : if( FD_UNLIKELY( memcmp( owner_account->const_meta->info.owner, fd_solana_native_loader_id.key, sizeof(fd_pubkey_t) ) ||
555 2388 : ( !FD_FEATURE_ACTIVE( txn_ctx->slot_ctx, remove_accounts_executable_flag_checks ) &&
556 2388 : !fd_account_is_executable( owner_account->const_meta ) ) ) ) {
557 3 : return FD_RUNTIME_TXN_ERR_INVALID_PROGRAM_FOR_EXECUTION;
558 3 : }
559 :
560 : /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L342-347 */
561 : /* Count the owner's data in the loaded account size for program accounts.
562 : However, it is important to not double count repeated owners. */
563 2385 : err = accumulate_and_check_loaded_account_data_size( owner_account->const_meta->dlen,
564 2385 : requested_loaded_accounts_data_size,
565 2385 : &accumulated_account_size );
566 2385 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
567 0 : return err;
568 0 : }
569 :
570 2385 : fd_memcpy( &program_owners[ program_owners_cnt++ ], owner_account->pubkey, sizeof(fd_pubkey_t) );
571 2385 : }
572 :
573 5190 : return FD_RUNTIME_EXECUTE_SUCCESS;
574 6726 : }
575 :
576 : /* https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L118 */
577 : int
578 8364 : fd_executor_validate_account_locks( fd_exec_txn_ctx_t const * txn_ctx ) {
579 : /* Ensure the number of account keys does not exceed the transaction lock limit
580 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L123 */
581 8364 : ulong tx_account_lock_limit = get_transaction_account_lock_limit( txn_ctx->slot_ctx );
582 8364 : if( FD_UNLIKELY( txn_ctx->accounts_cnt>tx_account_lock_limit ) ) {
583 45 : return FD_RUNTIME_TXN_ERR_TOO_MANY_ACCOUNT_LOCKS;
584 45 : }
585 :
586 : /* Duplicate account check
587 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L125 */
588 54504 : for( ushort i=0; i<txn_ctx->accounts_cnt; i++ ) {
589 218430 : for( ushort j=(ushort)(i+1U); j<txn_ctx->accounts_cnt; j++ ) {
590 172245 : if( FD_UNLIKELY( !memcmp( &txn_ctx->accounts[i], &txn_ctx->accounts[j], sizeof(fd_pubkey_t) ) ) ) {
591 348 : return FD_RUNTIME_TXN_ERR_ACCOUNT_LOADED_TWICE;
592 348 : }
593 172245 : }
594 46533 : }
595 :
596 : /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/sdk/src/transaction/sanitized.rs#L286-L288 */
597 7971 : return FD_RUNTIME_EXECUTE_SUCCESS;
598 8319 : }
599 :
600 : /* https://github.com/anza-xyz/agave/blob/89050f3cb7e76d9e273f10bea5e8207f2452f79f/svm/src/account_loader.rs#L101-L126 */
601 : static int
602 : fd_should_set_exempt_rent_epoch_max( fd_exec_slot_ctx_t const * slot_ctx,
603 22212 : fd_borrowed_account_t * rec ) {
604 : /* https://github.com/anza-xyz/agave/blob/89050f3cb7e76d9e273f10bea5e8207f2452f79f/svm/src/account_loader.rs#L109-L125 */
605 22212 : if( FD_FEATURE_ACTIVE( slot_ctx, disable_rent_fees_collection ) ) {
606 4596 : if( FD_LIKELY( rec->const_meta->info.rent_epoch!=ULONG_MAX
607 4596 : && rec->const_meta->info.lamports>=fd_rent_exempt_minimum_balance( &slot_ctx->epoch_ctx->epoch_bank.rent, rec->const_meta->dlen ) ) ) {
608 3093 : return 1;
609 3093 : }
610 1503 : return 0;
611 4596 : }
612 :
613 17616 : ulong epoch = fd_slot_to_epoch( &slot_ctx->epoch_ctx->epoch_bank.epoch_schedule, slot_ctx->slot_bank.slot, NULL );
614 :
615 : /* https://github.com/anza-xyz/agave/blob/89050f3cb7e76d9e273f10bea5e8207f2452f79f/sdk/src/rent_collector.rs#L158-L162 */
616 17616 : if( rec->const_meta->info.rent_epoch==ULONG_MAX || rec->const_meta->info.rent_epoch>epoch ) {
617 17175 : return 0;
618 17175 : }
619 :
620 : /* https://github.com/anza-xyz/agave/blob/89050f3cb7e76d9e273f10bea5e8207f2452f79f/sdk/src/rent_collector.rs#L163-L166 */
621 441 : if( rec->const_meta->info.executable || !memcmp( rec->pubkey->key, fd_sysvar_incinerator_id.key, sizeof(fd_pubkey_t) ) ) {
622 156 : return 1;
623 156 : }
624 :
625 : /* https://github.com/anza-xyz/agave/blob/89050f3cb7e76d9e273f10bea5e8207f2452f79f/sdk/src/rent_collector.rs#L167-L183 */
626 285 : if( rec->const_meta->info.lamports && rec->const_meta->info.lamports<fd_rent_exempt_minimum_balance( &slot_ctx->epoch_ctx->epoch_bank.rent, rec->const_meta->dlen ) ) {
627 132 : return 0;
628 132 : }
629 :
630 153 : return 1;
631 285 : }
632 :
633 : static void
634 : compute_priority_fee( fd_exec_txn_ctx_t const * txn_ctx,
635 : ulong * fee,
636 6831 : ulong * priority ) {
637 6831 : switch( txn_ctx->prioritization_fee_type ) {
638 6771 : case FD_COMPUTE_BUDGET_PRIORITIZATION_FEE_TYPE_DEPRECATED: {
639 6771 : if( !txn_ctx->compute_unit_limit ) {
640 27 : *priority = 0UL;
641 27 : }
642 6744 : else {
643 6744 : uint128 micro_lamport_fee = (uint128)txn_ctx->compute_unit_price * (uint128)MICRO_LAMPORTS_PER_LAMPORT;
644 6744 : uint128 _priority = micro_lamport_fee / (uint128)txn_ctx->compute_unit_limit;
645 6744 : *priority = _priority > (uint128)ULONG_MAX ? ULONG_MAX : (ulong)_priority;
646 6744 : }
647 :
648 6771 : *fee = txn_ctx->compute_unit_price;
649 6771 : return;
650 :
651 60 : } case FD_COMPUTE_BUDGET_PRIORITIZATION_FEE_TYPE_COMPUTE_UNIT_PRICE: {
652 60 : uint128 micro_lamport_fee = (uint128)txn_ctx->compute_unit_price * (uint128)txn_ctx->compute_unit_limit;
653 60 : *priority = txn_ctx->compute_unit_price;
654 60 : uint128 _fee = (micro_lamport_fee + (uint128)(MICRO_LAMPORTS_PER_LAMPORT - 1)) / (uint128)(MICRO_LAMPORTS_PER_LAMPORT);
655 60 : *fee = _fee > (uint128)ULONG_MAX ? ULONG_MAX : (ulong)_fee;
656 60 : return;
657 :
658 0 : }
659 0 : default:
660 0 : __builtin_unreachable();
661 6831 : }
662 6831 : }
663 :
664 : static ulong
665 6831 : fd_executor_lamports_per_signature( fd_slot_bank_t const *slot_bank ) {
666 : // https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/sdk/program/src/fee_calculator.rs#L110
667 6831 : return slot_bank->fee_rate_governor.target_lamports_per_signature / 2;
668 6831 : }
669 :
670 : static void
671 : fd_executor_calculate_fee( fd_exec_txn_ctx_t *txn_ctx,
672 : fd_txn_t const *txn_descriptor,
673 : fd_rawtxn_b_t const *txn_raw,
674 : ulong *ret_execution_fee,
675 6831 : ulong *ret_priority_fee) {
676 :
677 : // https://github.com/anza-xyz/agave/blob/2e6ca8c1f62db62c1db7f19c9962d4db43d0d550/sdk/src/fee.rs#L82
678 6831 : #define ACCOUNT_DATA_COST_PAGE_SIZE fd_ulong_sat_mul(32, 1024)
679 :
680 : // https://github.com/firedancer-io/solana/blob/08a1ef5d785fe58af442b791df6c4e83fe2e7c74/runtime/src/bank.rs#L4443
681 6831 : ulong priority = 0UL;
682 6831 : ulong priority_fee = 0UL;
683 6831 : compute_priority_fee( txn_ctx, &priority_fee, &priority );
684 :
685 : // let signature_fee = Self::get_num_signatures_in_message(message) .saturating_mul(fee_structure.lamports_per_signature);
686 6831 : ulong num_signatures = txn_descriptor->signature_cnt;
687 18375 : for (ushort i=0; i<txn_descriptor->instr_cnt; ++i ) {
688 11544 : fd_txn_instr_t const * txn_instr = &txn_descriptor->instr[i];
689 11544 : fd_pubkey_t * program_id = &txn_ctx->accounts[txn_instr->program_id];
690 11544 : if( !memcmp(program_id->uc, fd_solana_keccak_secp_256k_program_id.key, sizeof(fd_pubkey_t)) ||
691 11544 : !memcmp(program_id->uc, fd_solana_ed25519_sig_verify_program_id.key, sizeof(fd_pubkey_t)) ||
692 11544 : (!memcmp(program_id->uc, fd_solana_secp256r1_program_id.key, sizeof(fd_pubkey_t)) && FD_FEATURE_ACTIVE( txn_ctx->slot_ctx, enable_secp256r1_precompile )) ) {
693 927 : if( !txn_instr->data_sz ) {
694 0 : continue;
695 0 : }
696 927 : uchar * data = (uchar *)txn_raw->raw + txn_instr->data_off;
697 927 : num_signatures = fd_ulong_sat_add(num_signatures, (ulong)(data[0]));
698 927 : }
699 11544 : }
700 :
701 6831 : ulong signature_fee = fd_executor_lamports_per_signature(&txn_ctx->slot_ctx->slot_bank) * num_signatures;
702 :
703 : // TODO: as far as I can tell, this is always 0
704 : //
705 : // let write_lock_fee = Self::get_num_write_locks_in_message(message)
706 : // .saturating_mul(fee_structure.lamports_per_write_lock);
707 6831 : ulong lamports_per_write_lock = 0UL;
708 6831 : ulong write_lock_fee = fd_ulong_sat_mul(fd_txn_account_cnt(txn_descriptor, FD_TXN_ACCT_CAT_WRITABLE), lamports_per_write_lock);
709 :
710 : // TODO: the fee_structure bin is static and default..
711 : // let loaded_accounts_data_size_cost = if include_loaded_account_data_size_in_fee {
712 : // FeeStructure::calculate_memory_usage_cost(
713 : // budget_limits.loaded_accounts_data_size_limit,
714 : // budget_limits.heap_cost,
715 : // )
716 : // } else {
717 : // 0_u64
718 : // };
719 : // let total_compute_units =
720 : // loaded_accounts_data_size_cost.saturating_add(budget_limits.compute_unit_limit);
721 : // let compute_fee = self
722 : // .compute_fee_bins
723 : // .iter()
724 : // .find(|bin| total_compute_units <= bin.limit)
725 : // .map(|bin| bin.fee)
726 : // .unwrap_or_else(|| {
727 : // self.compute_fee_bins
728 : // .last()
729 : // .map(|bin| bin.fee)
730 : // .unwrap_or_default()
731 : // });
732 :
733 : // https://github.com/anza-xyz/agave/blob/2e6ca8c1f62db62c1db7f19c9962d4db43d0d550/sdk/src/fee.rs#L203-L206
734 6831 : ulong execution_fee = fd_ulong_sat_add( signature_fee, write_lock_fee );
735 :
736 6831 : if( execution_fee >= ULONG_MAX ) {
737 0 : *ret_execution_fee = ULONG_MAX;
738 6831 : } else {
739 6831 : *ret_execution_fee = execution_fee;
740 6831 : }
741 :
742 6831 : if( priority_fee >= ULONG_MAX ) {
743 3 : *ret_priority_fee = ULONG_MAX;
744 6828 : } else {
745 6828 : *ret_priority_fee = priority_fee;
746 6828 : }
747 :
748 6831 : #undef ACCOUNT_DATA_COST_PAGE_SIZE
749 6831 : }
750 :
751 : static int
752 6831 : fd_executor_collect_fees( fd_exec_txn_ctx_t * txn_ctx, fd_borrowed_account_t * fee_payer_rec ) {
753 :
754 6831 : ulong execution_fee = 0UL;
755 6831 : ulong priority_fee = 0UL;
756 :
757 6831 : fd_executor_calculate_fee( txn_ctx, txn_ctx->txn_descriptor, txn_ctx->_txn_raw, &execution_fee, &priority_fee );
758 :
759 6831 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( txn_ctx->slot_ctx->epoch_ctx );
760 6831 : ulong total_fee = fd_ulong_sat_add( execution_fee, priority_fee );
761 :
762 : // https://github.com/anza-xyz/agave/blob/2e6ca8c1f62db62c1db7f19c9962d4db43d0d550/sdk/src/fee.rs#L54
763 6831 : if ( !FD_FEATURE_ACTIVE( txn_ctx->slot_ctx, remove_rounding_in_fee_calculation ) ) {
764 5505 : total_fee = fd_rust_cast_double_to_ulong( round( (double)total_fee ) );
765 5505 : }
766 :
767 6831 : int err = fd_validate_fee_payer( fee_payer_rec, &epoch_bank->rent, total_fee );
768 6831 : if( FD_UNLIKELY( err ) ) {
769 105 : return err;
770 105 : }
771 :
772 : /* At this point, the fee payer has been validated and the fee has been
773 : calculated. This means that the fee can be safely subtracted from the
774 : fee payer's borrowed account. However, the starting lamports of the
775 : account must be updated as well. Each instruction must have the net
776 : same (balanced) amount of lamports. This is done by comparing the
777 : borrowed accounts starting lamports and comparing it to the sum of
778 : the ending lamports. Therefore, we need to update the starting lamports
779 : specifically for the fee payer.
780 :
781 : This is especially important in the case where the transaction fails. This
782 : is because we need to roll back the account to the balance AFTER the fee
783 : is paid. It is also possible for the accounts data and owner to change.
784 : This means that the entire state of the borrowed account must be rolled
785 : back to this point. */
786 :
787 6726 : fee_payer_rec->meta->info.lamports -= total_fee;
788 6726 : fee_payer_rec->starting_lamports = fee_payer_rec->meta->info.lamports;
789 :
790 : /* Update the fee payer's rent epoch to ULONG_MAX if it is rent exempt. */
791 6726 : if( fd_should_set_exempt_rent_epoch_max( txn_ctx->slot_ctx, fee_payer_rec ) ) {
792 0 : fee_payer_rec->meta->info.rent_epoch = ULONG_MAX;
793 0 : }
794 :
795 6726 : txn_ctx->execution_fee = execution_fee;
796 6726 : txn_ctx->priority_fee = priority_fee;
797 :
798 6726 : return FD_RUNTIME_EXECUTE_SUCCESS;
799 6831 : }
800 :
801 : /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/svm/src/transaction_processor.rs#L413-L497 */
802 : int
803 6930 : fd_executor_validate_transaction_fee_payer( fd_exec_txn_ctx_t * txn_ctx ) {
804 : /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/svm/src/transaction_processor.rs#L423-L430 */
805 6930 : int err = fd_executor_compute_budget_program_execute_instructions( txn_ctx, txn_ctx->_txn_raw );
806 6930 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
807 99 : return err;
808 99 : }
809 :
810 : /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/svm/src/transaction_processor.rs#L431-L436 */
811 6831 : fd_borrowed_account_t * fee_payer_rec = NULL;
812 6831 : err = fd_txn_borrowed_account_modify_fee_payer( txn_ctx, &fee_payer_rec );
813 6831 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
814 0 : return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
815 0 : }
816 :
817 : /* Collect rent from the fee payer and set the starting lamports (to avoid unbalanced lamports issues in instruction execution)
818 : https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/svm/src/transaction_processor.rs#L438-L445 */
819 6831 : fd_epoch_schedule_t const * schedule = fd_sysvar_cache_epoch_schedule( txn_ctx->slot_ctx->sysvar_cache );
820 6831 : ulong epoch = fd_slot_to_epoch( schedule, txn_ctx->slot_ctx->slot_bank.slot, NULL );
821 6831 : txn_ctx->collected_rent += fd_runtime_collect_rent_from_account( txn_ctx->slot_ctx, fee_payer_rec->meta, fee_payer_rec->pubkey, epoch );
822 6831 : fee_payer_rec->starting_lamports = fee_payer_rec->meta->info.lamports;
823 :
824 : /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/svm/src/transaction_processor.rs#L431-L488 */
825 6831 : err = fd_executor_collect_fees( txn_ctx, fee_payer_rec );
826 6831 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
827 105 : return err;
828 105 : }
829 :
830 6726 : return FD_RUNTIME_EXECUTE_SUCCESS;
831 6831 : }
832 :
833 : int
834 12741 : fd_executor_setup_accessed_accounts_for_txn( fd_exec_txn_ctx_t * txn_ctx ) {
835 :
836 12741 : fd_pubkey_t * tx_accs = (fd_pubkey_t *)((uchar *)txn_ctx->_txn_raw->raw + txn_ctx->txn_descriptor->acct_addr_off);
837 :
838 : // Set up accounts in the transaction body and perform checks
839 59679 : for( ulong i = 0; i < txn_ctx->txn_descriptor->acct_addr_cnt; i++ ) {
840 46938 : txn_ctx->accounts[i] = tx_accs[i];
841 46938 : }
842 :
843 12741 : txn_ctx->accounts_cnt += (uchar) txn_ctx->txn_descriptor->acct_addr_cnt;
844 :
845 12741 : if( txn_ctx->txn_descriptor->transaction_version == FD_TXN_V0 ) {
846 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/runtime/src/bank/address_lookup_table.rs#L44-L48 */
847 12228 : fd_slot_hashes_t const * slot_hashes = fd_sysvar_cache_slot_hashes( txn_ctx->slot_ctx->sysvar_cache );
848 12228 : if( FD_UNLIKELY( !slot_hashes ) ) {
849 0 : return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
850 0 : }
851 :
852 12228 : fd_pubkey_t readonly_lut_accs[128];
853 12228 : ulong readonly_lut_accs_cnt = 0;
854 12228 : FD_SCRATCH_SCOPE_BEGIN {
855 : // Set up accounts in the account look up tables.
856 12228 : fd_txn_acct_addr_lut_t const * addr_luts = fd_txn_get_address_tables_const( txn_ctx->txn_descriptor );
857 19749 : for( ulong i = 0; i < txn_ctx->txn_descriptor->addr_table_lookup_cnt; i++ ) {
858 7785 : fd_txn_acct_addr_lut_t const * addr_lut = &addr_luts[i];
859 7785 : fd_pubkey_t const * addr_lut_acc = (fd_pubkey_t *)((uchar *)txn_ctx->_txn_raw->raw + addr_lut->addr_off);
860 :
861 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/accounts-db/src/accounts.rs#L90-L94 */
862 7785 : FD_BORROWED_ACCOUNT_DECL(addr_lut_rec);
863 7785 : int err = fd_acc_mgr_view(txn_ctx->slot_ctx->acc_mgr, txn_ctx->slot_ctx->funk_txn, (fd_pubkey_t *) addr_lut_acc, addr_lut_rec);
864 7785 : if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) {
865 57 : return FD_RUNTIME_TXN_ERR_ADDRESS_LOOKUP_TABLE_NOT_FOUND;
866 57 : }
867 :
868 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/accounts-db/src/accounts.rs#L96-L114 */
869 7728 : if( FD_UNLIKELY( memcmp( addr_lut_rec->const_meta->info.owner, fd_solana_address_lookup_table_program_id.key, sizeof(fd_pubkey_t) ) ) ) {
870 36 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_OWNER;
871 36 : }
872 :
873 : /* Realistically impossible case, but need to make sure we don't cause an OOB data access
874 : https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L205-L209 */
875 7692 : if( FD_UNLIKELY( addr_lut_rec->const_meta->dlen < FD_LOOKUP_TABLE_META_SIZE ) ) {
876 117 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
877 117 : }
878 :
879 : /* https://github.com/anza-xyz/agave/blob/574bae8fefc0ed256b55340b9d87b7689bcdf222/accounts-db/src/accounts.rs#L141-L142 */
880 7575 : fd_address_lookup_table_state_t addr_lookup_table_state;
881 7575 : fd_bincode_decode_ctx_t decode_ctx = {
882 7575 : .data = addr_lut_rec->const_data,
883 7575 : .dataend = &addr_lut_rec->const_data[FD_LOOKUP_TABLE_META_SIZE],
884 7575 : .valloc = fd_scratch_virtual(),
885 7575 : };
886 :
887 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L197-L214 */
888 7575 : if( FD_UNLIKELY( fd_address_lookup_table_state_decode( &addr_lookup_table_state, &decode_ctx ) ) ) {
889 9 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
890 9 : }
891 :
892 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L200-L203 */
893 7566 : if( FD_UNLIKELY( addr_lookup_table_state.discriminant != fd_address_lookup_table_state_enum_lookup_table ) ) {
894 3 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
895 3 : }
896 :
897 : /* Again probably an impossible case, but the ALUT data needs to be 32-byte aligned
898 : https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L210-L214 */
899 7563 : if( FD_UNLIKELY( ( addr_lut_rec->const_meta->dlen - FD_LOOKUP_TABLE_META_SIZE ) & 0x1fUL ) ) {
900 3 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
901 3 : }
902 :
903 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/accounts-db/src/accounts.rs#L101-L112 */
904 7560 : fd_pubkey_t * lookup_addrs = (fd_pubkey_t *)&addr_lut_rec->const_data[FD_LOOKUP_TABLE_META_SIZE];
905 7560 : ulong lookup_addrs_cnt = ( addr_lut_rec->const_meta->dlen - FD_LOOKUP_TABLE_META_SIZE ) >> 5UL; // = (dlen - 56) / 32
906 :
907 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L175-L176 */
908 7560 : ulong active_addresses_len;
909 7560 : err = fd_get_active_addresses_len( &addr_lookup_table_state.inner.lookup_table,
910 7560 : txn_ctx->slot_ctx->slot_bank.slot,
911 7560 : slot_hashes->hashes,
912 7560 : lookup_addrs_cnt,
913 7560 : &active_addresses_len );
914 7560 : if( FD_UNLIKELY( err ) ) {
915 9 : return err;
916 9 : }
917 :
918 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L169-L182 */
919 7551 : uchar * writable_lut_idxs = (uchar *)txn_ctx->_txn_raw->raw + addr_lut->writable_off;
920 19014 : for( ulong j = 0; j < addr_lut->writable_cnt; j++ ) {
921 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L177-L181 */
922 11475 : if( writable_lut_idxs[j] >= active_addresses_len ) {
923 12 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_INDEX;
924 12 : }
925 11463 : txn_ctx->accounts[txn_ctx->accounts_cnt++] = lookup_addrs[writable_lut_idxs[j]];
926 11463 : }
927 :
928 7539 : uchar * readonly_lut_idxs = (uchar *)txn_ctx->_txn_raw->raw + addr_lut->readonly_off;
929 15291 : for( ulong j = 0; j < addr_lut->readonly_cnt; j++ ) {
930 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L177-L181 */
931 7770 : if( readonly_lut_idxs[j] >= active_addresses_len ) {
932 18 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_INDEX;
933 18 : }
934 7752 : readonly_lut_accs[readonly_lut_accs_cnt++] = lookup_addrs[readonly_lut_idxs[j]];
935 7752 : }
936 7539 : }
937 12228 : } FD_SCRATCH_SCOPE_END;
938 :
939 11964 : fd_memcpy( &txn_ctx->accounts[txn_ctx->accounts_cnt], readonly_lut_accs, readonly_lut_accs_cnt * sizeof(fd_pubkey_t) );
940 11964 : txn_ctx->accounts_cnt += readonly_lut_accs_cnt;
941 11964 : }
942 12477 : return FD_RUNTIME_EXECUTE_SUCCESS;
943 12741 : }
944 :
945 : static void
946 0 : dump_sorted_features( const fd_features_t * features, fd_exec_test_feature_set_t * output_feature_set ) {
947 : /* NOTE: Caller must have a scratch frame prepared */
948 0 : uint64_t * unsorted_features = fd_scratch_alloc( alignof(uint64_t), FD_FEATURE_ID_CNT * sizeof(uint64_t) );
949 0 : ulong num_features = 0;
950 0 : for( const fd_feature_id_t * current_feature = fd_feature_iter_init(); !fd_feature_iter_done( current_feature ); current_feature = fd_feature_iter_next( current_feature ) ) {
951 0 : if (features->f[current_feature->index] != FD_FEATURE_DISABLED) {
952 0 : unsorted_features[num_features++] = (uint64_t) current_feature->id.ul[0];
953 0 : }
954 0 : }
955 : // Sort the features
956 0 : void * scratch = fd_scratch_alloc( sort_uint64_t_stable_scratch_align(), sort_uint64_t_stable_scratch_footprint(num_features) );
957 0 : uint64_t * sorted_features = sort_uint64_t_stable_fast( unsorted_features, num_features, scratch );
958 :
959 : // Set feature set in message
960 0 : output_feature_set->features_count = (pb_size_t) num_features;
961 0 : output_feature_set->features = sorted_features;
962 0 : }
963 :
964 : static void
965 : dump_account_state( fd_borrowed_account_t const * borrowed_account,
966 0 : fd_exec_test_acct_state_t * output_account ) {
967 : // Address
968 0 : fd_memcpy(output_account->address, borrowed_account->pubkey, sizeof(fd_pubkey_t));
969 :
970 : // Lamports
971 0 : output_account->lamports = (uint64_t) borrowed_account->const_meta->info.lamports;
972 :
973 : // Data
974 0 : output_account->data = fd_scratch_alloc(alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE(borrowed_account->const_meta->dlen));
975 0 : output_account->data->size = (pb_size_t) borrowed_account->const_meta->dlen;
976 0 : fd_memcpy(output_account->data->bytes, borrowed_account->const_data, borrowed_account->const_meta->dlen);
977 :
978 : // Executable
979 0 : output_account->executable = (bool) borrowed_account->const_meta->info.executable;
980 :
981 : // Rent epoch
982 0 : output_account->rent_epoch = (uint64_t) borrowed_account->const_meta->info.rent_epoch;
983 :
984 : // Owner
985 0 : fd_memcpy(output_account->owner, borrowed_account->const_meta->info.owner, sizeof(fd_pubkey_t));
986 :
987 : // Seed address (not present)
988 0 : output_account->has_seed_addr = false;
989 0 : }
990 :
991 : void
992 : fd_create_instr_context_protobuf_from_instructions( fd_exec_test_instr_context_t * instr_context,
993 : fd_exec_txn_ctx_t const *txn_ctx,
994 0 : fd_instr_info_t const *instr ) {
995 : /*
996 : NOTE: Calling this function requires the caller to have a scratch frame ready (see dump_instr_to_protobuf)
997 : */
998 :
999 : /* Prepare sysvar cache accounts */
1000 0 : fd_pubkey_t const fd_relevant_sysvar_ids[] = {
1001 0 : fd_sysvar_clock_id,
1002 0 : fd_sysvar_epoch_schedule_id,
1003 0 : fd_sysvar_epoch_rewards_id,
1004 0 : fd_sysvar_fees_id,
1005 0 : fd_sysvar_rent_id,
1006 0 : fd_sysvar_slot_hashes_id,
1007 0 : fd_sysvar_recent_block_hashes_id,
1008 0 : fd_sysvar_stake_history_id,
1009 0 : fd_sysvar_last_restart_slot_id,
1010 0 : fd_sysvar_instructions_id,
1011 0 : };
1012 0 : const ulong num_sysvar_entries = (sizeof(fd_relevant_sysvar_ids) / sizeof(fd_pubkey_t));
1013 :
1014 : /* Program ID */
1015 0 : fd_memcpy( instr_context->program_id, instr->program_id_pubkey.uc, sizeof(fd_pubkey_t) );
1016 :
1017 : /* Accounts */
1018 0 : instr_context->accounts_count = (pb_size_t) txn_ctx->accounts_cnt;
1019 0 : instr_context->accounts = fd_scratch_alloc(alignof(fd_exec_test_acct_state_t), (instr_context->accounts_count + num_sysvar_entries + txn_ctx->executable_cnt) * sizeof(fd_exec_test_acct_state_t));
1020 0 : for( ulong i = 0; i < txn_ctx->accounts_cnt; i++ ) {
1021 : // Copy account information over
1022 0 : fd_borrowed_account_t const * borrowed_account = &txn_ctx->borrowed_accounts[i];
1023 0 : fd_exec_test_acct_state_t * output_account = &instr_context->accounts[i];
1024 0 : dump_account_state( borrowed_account, output_account );
1025 0 : }
1026 :
1027 : /* Add sysvar cache variables */
1028 0 : for( ulong i = 0; i < num_sysvar_entries; i++ ) {
1029 0 : FD_BORROWED_ACCOUNT_DECL(borrowed_account);
1030 0 : int ret = fd_acc_mgr_view( txn_ctx->acc_mgr, txn_ctx->funk_txn, &fd_relevant_sysvar_ids[i], borrowed_account );
1031 0 : if( ret != FD_ACC_MGR_SUCCESS ) {
1032 0 : continue;
1033 0 : }
1034 : // Make sure the account doesn't exist in the output accounts yet
1035 0 : int account_exists = 0;
1036 0 : for( ulong j = 0; j < txn_ctx->accounts_cnt; j++ ) {
1037 0 : if ( 0 == memcmp( txn_ctx->accounts[j].key, fd_relevant_sysvar_ids[i].uc, sizeof(fd_pubkey_t) ) ) {
1038 0 : account_exists = true;
1039 0 : break;
1040 0 : }
1041 0 : }
1042 :
1043 : // Copy it into output
1044 0 : if (!account_exists) {
1045 0 : fd_exec_test_acct_state_t * output_account = &instr_context->accounts[instr_context->accounts_count++];
1046 0 : dump_account_state( borrowed_account, output_account );
1047 0 : }
1048 0 : }
1049 :
1050 : /* Add executable accounts */
1051 0 : for( ulong i = 0; i < txn_ctx->executable_cnt; i++ ) {
1052 0 : FD_BORROWED_ACCOUNT_DECL(borrowed_account);
1053 0 : int ret = fd_acc_mgr_view( txn_ctx->acc_mgr, txn_ctx->funk_txn, txn_ctx->executable_accounts[i].pubkey, borrowed_account );
1054 0 : if( ret != FD_ACC_MGR_SUCCESS ) {
1055 0 : continue;
1056 0 : }
1057 : // Make sure the account doesn't exist in the output accounts yet
1058 0 : bool account_exists = false;
1059 0 : for( ulong j = 0; j < instr_context->accounts_count; j++ ) {
1060 0 : if( 0 == memcmp( instr_context->accounts[j].address, txn_ctx->executable_accounts[i].pubkey->uc, sizeof(fd_pubkey_t) ) ) {
1061 0 : account_exists = true;
1062 0 : break;
1063 0 : }
1064 0 : }
1065 : // Copy it into output
1066 0 : if( !account_exists ) {
1067 0 : fd_exec_test_acct_state_t * output_account = &instr_context->accounts[instr_context->accounts_count++];
1068 0 : dump_account_state( borrowed_account, output_account );
1069 0 : }
1070 0 : }
1071 :
1072 : /* Instruction Accounts */
1073 0 : instr_context->instr_accounts_count = (pb_size_t) instr->acct_cnt;
1074 0 : instr_context->instr_accounts = fd_scratch_alloc( alignof(fd_exec_test_instr_acct_t), instr_context->instr_accounts_count * sizeof(fd_exec_test_instr_acct_t) );
1075 0 : for( ushort i = 0; i < instr->acct_cnt; i++ ) {
1076 0 : fd_exec_test_instr_acct_t * output_instr_account = &instr_context->instr_accounts[i];
1077 :
1078 0 : uchar account_flag = instr->acct_flags[i];
1079 0 : bool is_writable = account_flag & FD_INSTR_ACCT_FLAGS_IS_WRITABLE;
1080 0 : bool is_signer = account_flag & FD_INSTR_ACCT_FLAGS_IS_SIGNER;
1081 :
1082 0 : output_instr_account->index = instr->acct_txn_idxs[i];
1083 0 : output_instr_account->is_writable = is_writable;
1084 0 : output_instr_account->is_signer = is_signer;
1085 0 : }
1086 :
1087 : /* Data */
1088 0 : instr_context->data = fd_scratch_alloc( alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE(instr->data_sz) );
1089 0 : instr_context->data->size = (pb_size_t) instr->data_sz;
1090 0 : fd_memcpy( instr_context->data->bytes, instr->data, instr->data_sz );
1091 :
1092 : /* Compute Units */
1093 0 : instr_context->cu_avail = txn_ctx->compute_meter;
1094 :
1095 : /* Slot Context */
1096 0 : instr_context->has_slot_context = true;
1097 :
1098 : /* Epoch Context */
1099 0 : instr_context->has_epoch_context = true;
1100 0 : instr_context->epoch_context.has_features = true;
1101 0 : dump_sorted_features( &txn_ctx->epoch_ctx->features, &instr_context->epoch_context.features );
1102 0 : }
1103 :
1104 : /* This function dumps individual instructions from a ledger replay.
1105 :
1106 : The following arguments can be added when replaying ledger transactions:
1107 : --dump-insn-to-pb <0/1>
1108 : * If enabled, instructions will be dumped to the specified output directory
1109 : --dump-proto-sig-filter <base_58_enc_sig>
1110 : * If enabled, only instructions with the specified transaction signature will be dumped
1111 : * Provided signature must be base58-encoded
1112 : * Default behavior if signature filter is not provided is to dump EVERY instruction
1113 : --dump-proto-output-dir <output_dir>
1114 : * Each file represents a single instruction as a serialized InstrContext Protobuf message
1115 : * File name format is "instr-<base58_enc_sig>-<instruction_idx>.bin", where instruction_idx is 1-indexed
1116 :
1117 : solana-conformance (https://github.com/firedancer-io/solana-conformance)
1118 : * Allows decoding / debugging of instructions in an isolated environment
1119 : * Allows execution result(s) comparison with Solana / Agave
1120 : * See solana-conformance/README.md for functionality and use cases
1121 : */
1122 : static void
1123 : dump_instr_to_protobuf( fd_exec_txn_ctx_t *txn_ctx,
1124 : fd_instr_info_t *instr,
1125 0 : ushort instruction_idx ) {
1126 :
1127 :
1128 0 : FD_SCRATCH_SCOPE_BEGIN {
1129 : // Get base58-encoded tx signature
1130 0 : const fd_ed25519_sig_t * signatures = fd_txn_get_signatures( txn_ctx->txn_descriptor, txn_ctx->_txn_raw->raw );
1131 0 : fd_ed25519_sig_t signature; fd_memcpy( signature, signatures[0], sizeof(fd_ed25519_sig_t) );
1132 0 : char encoded_signature[FD_BASE58_ENCODED_64_SZ];
1133 0 : ulong out_size;
1134 0 : fd_base58_encode_64( signature, &out_size, encoded_signature );
1135 :
1136 0 : if (txn_ctx->capture_ctx->dump_proto_sig_filter) {
1137 0 : ulong filter_strlen = (ulong) strlen(txn_ctx->capture_ctx->dump_proto_sig_filter);
1138 :
1139 : // Terminate early if the signature does not match
1140 0 : if( memcmp( txn_ctx->capture_ctx->dump_proto_sig_filter, encoded_signature, filter_strlen < out_size ? filter_strlen : out_size ) ) {
1141 0 : return;
1142 0 : }
1143 0 : }
1144 :
1145 0 : fd_exec_test_instr_context_t instr_context = FD_EXEC_TEST_INSTR_CONTEXT_INIT_DEFAULT;
1146 0 : fd_create_instr_context_protobuf_from_instructions( &instr_context, txn_ctx, instr );
1147 :
1148 : /* Output to file */
1149 0 : ulong out_buf_size = 100 * 1024 * 1024;
1150 0 : uint8_t * out = fd_scratch_alloc( alignof(uchar) , out_buf_size );
1151 0 : pb_ostream_t stream = pb_ostream_from_buffer( out, out_buf_size );
1152 0 : if (pb_encode(&stream, FD_EXEC_TEST_INSTR_CONTEXT_FIELDS, &instr_context)) {
1153 0 : char output_filepath[256]; fd_memset(output_filepath, 0, sizeof(output_filepath));
1154 0 : char * position = fd_cstr_init(output_filepath);
1155 0 : position = fd_cstr_append_cstr(position, txn_ctx->capture_ctx->dump_proto_output_dir);
1156 0 : position = fd_cstr_append_cstr(position, "/instr-");
1157 0 : position = fd_cstr_append_cstr(position, encoded_signature);
1158 0 : position = fd_cstr_append_cstr(position, "-");
1159 0 : position = fd_cstr_append_ushort_as_text(position, '0', 0, instruction_idx, 3); // Assume max 3 digits
1160 0 : position = fd_cstr_append_cstr(position, ".bin");
1161 0 : fd_cstr_fini(position);
1162 :
1163 0 : FILE * file = fopen(output_filepath, "wb");
1164 0 : if( file ) {
1165 0 : fwrite( out, 1, stream.bytes_written, file );
1166 0 : fclose( file );
1167 0 : }
1168 0 : }
1169 0 : } FD_SCRATCH_SCOPE_END;
1170 0 : }
1171 :
1172 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L319-L357 */
1173 : static inline int
1174 : fd_txn_ctx_push( fd_exec_txn_ctx_t * txn_ctx,
1175 81504 : fd_instr_info_t * instr ) {
1176 : /* Earlier checks in the permalink are redundant since Agave maintains instr stack and trace accounts separately
1177 : https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L327-L328 */
1178 81504 : ulong starting_lamports_h = 0UL;
1179 81504 : ulong starting_lamports_l = 0UL;
1180 81504 : int err = fd_instr_info_sum_account_lamports( instr, &starting_lamports_h, &starting_lamports_l );
1181 81504 : if( FD_UNLIKELY( err ) ) {
1182 0 : return err;
1183 0 : }
1184 81504 : instr->starting_lamports_h = starting_lamports_h;
1185 81504 : instr->starting_lamports_l = starting_lamports_l;
1186 :
1187 : /* Check that the caller's lamport sum has not changed.
1188 : https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L329-L340 */
1189 81504 : if( txn_ctx->instr_stack_sz>0 ) {
1190 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L330 */
1191 519 : fd_exec_instr_ctx_t const * caller_instruction_context = &txn_ctx->instr_stack[ txn_ctx->instr_stack_sz-1 ];
1192 :
1193 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L331-L332 */
1194 519 : ulong original_caller_lamport_sum_h = caller_instruction_context->instr->starting_lamports_h;
1195 519 : ulong original_caller_lamport_sum_l = caller_instruction_context->instr->starting_lamports_l;
1196 :
1197 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L333-L334 */
1198 519 : ulong current_caller_lamport_sum_h = 0UL;
1199 519 : ulong current_caller_lamport_sum_l = 0UL;
1200 519 : int err = fd_instr_info_sum_account_lamports( caller_instruction_context->instr, ¤t_caller_lamport_sum_h, ¤t_caller_lamport_sum_l );
1201 519 : if( FD_UNLIKELY( err ) ) {
1202 0 : return err;
1203 0 : }
1204 :
1205 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L335-L339 */
1206 519 : if( FD_UNLIKELY( current_caller_lamport_sum_h!=original_caller_lamport_sum_h ||
1207 519 : current_caller_lamport_sum_l!=original_caller_lamport_sum_l ) ) {
1208 0 : return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
1209 0 : }
1210 519 : }
1211 :
1212 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L347-L351 */
1213 81504 : if( FD_UNLIKELY( txn_ctx->instr_trace_length>=FD_MAX_INSTRUCTION_TRACE_LENGTH ) ) {
1214 0 : return FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED;
1215 0 : }
1216 81504 : txn_ctx->instr_trace_length++;
1217 :
1218 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L352-L356 */
1219 81504 : if( FD_UNLIKELY( txn_ctx->instr_stack_sz>=FD_MAX_INSTRUCTION_STACK_DEPTH ) ) {
1220 0 : return FD_EXECUTOR_INSTR_ERR_CALL_DEPTH;
1221 0 : }
1222 81504 : txn_ctx->instr_stack_sz++;
1223 :
1224 81504 : return FD_EXECUTOR_INSTR_SUCCESS;
1225 81504 : }
1226 :
1227 : /* Pushes a new instruction onto the instruction stack and trace. This check loops through all instructions in the current call stack
1228 : and checks for reentrancy violations. If successful, simply increments the instruction stack and trace size and returns. It is
1229 : the responsibility of the caller to populate the newly pushed instruction fields, which are undefined otherwise.
1230 :
1231 : https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L246-L290 */
1232 : int
1233 : fd_instr_stack_push( fd_exec_txn_ctx_t * txn_ctx,
1234 81564 : fd_instr_info_t * instr ) {
1235 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L253-L255 */
1236 : /* Agave looks into the program_indices that was generated in load_transaction_accounts, if the vector has nothing, it throw a UnsupportedProgramId */
1237 81564 : if( FD_UNLIKELY( !txn_ctx->has_program_id ) ) {
1238 57 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
1239 57 : }
1240 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L256-L286 */
1241 81507 : if( txn_ctx->instr_stack_sz ) {
1242 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L261-L285 */
1243 522 : uchar contains = 0;
1244 522 : uchar is_last = 0;
1245 :
1246 : // Checks all previous instructions in the stack for reentrancy
1247 1197 : for( uchar level=0; level<txn_ctx->instr_stack_sz; level++ ) {
1248 675 : fd_exec_instr_ctx_t * instr_ctx = &txn_ctx->instr_stack[level];
1249 : // Optimization: compare program id index instead of pubkey since account keys are unique
1250 675 : if( instr->program_id == instr_ctx->instr->program_id ) {
1251 : // Reentrancy not allowed unless caller is calling itself
1252 42 : if( level == txn_ctx->instr_stack_sz-1 ) {
1253 39 : is_last = 1;
1254 39 : }
1255 42 : contains = 1;
1256 42 : }
1257 675 : }
1258 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L282-L285 */
1259 522 : if( FD_UNLIKELY( contains && !is_last ) ) {
1260 3 : return FD_EXECUTOR_INSTR_ERR_REENTRANCY_NOT_ALLOWED;
1261 3 : }
1262 522 : }
1263 : /* "Push" a new instruction onto the stack by simply incrementing the stack and trace size counters
1264 : https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L289 */
1265 81504 : return fd_txn_ctx_push( txn_ctx, instr );
1266 81507 : }
1267 :
1268 : /* Pops an instruction from the instruction stack. Agave's implementation performs instruction balancing checks every time pop is called,
1269 : but error codes returned from `pop` are only used if the program's execution was successful. Therefore, we can optimize our code by only
1270 : checking for unbalanced instructions if the program execution was successful within fd_execute_instr.
1271 :
1272 : https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L293-L298 */
1273 : int
1274 : fd_instr_stack_pop( fd_exec_txn_ctx_t * txn_ctx,
1275 81504 : fd_instr_info_t const * instr ) {
1276 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L362-L364 */
1277 81504 : if( FD_UNLIKELY( txn_ctx->instr_stack_sz==0 ) ) {
1278 0 : return FD_EXECUTOR_INSTR_ERR_CALL_DEPTH;
1279 0 : }
1280 81504 : txn_ctx->instr_stack_sz--;
1281 :
1282 : /* Verify all executable accounts have no outstanding refs
1283 : https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L369-L374 */
1284 525726 : for( ushort i=0; i<instr->acct_cnt; i++ ) {
1285 444690 : if( FD_UNLIKELY( instr->borrowed_accounts[i]->const_meta->info.executable &&
1286 444690 : instr->borrowed_accounts[i]->refcnt_excl ) ) {
1287 468 : return FD_EXECUTOR_INSTR_ERR_ACC_BORROW_OUTSTANDING;
1288 468 : }
1289 444690 : }
1290 :
1291 : /* Verify lamports are balanced before and after instruction
1292 : https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L366-L380 */
1293 81036 : ulong ending_lamports_h = 0UL;
1294 81036 : ulong ending_lamports_l = 0UL;
1295 81036 : int err = fd_instr_info_sum_account_lamports( instr, &ending_lamports_h, &ending_lamports_l );
1296 81036 : if( FD_UNLIKELY( err ) ) {
1297 0 : return err;
1298 0 : }
1299 81036 : if( FD_UNLIKELY( ending_lamports_l != instr->starting_lamports_l || ending_lamports_h != instr->starting_lamports_h ) ) {
1300 186 : return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
1301 186 : }
1302 :
1303 80850 : return FD_EXECUTOR_INSTR_SUCCESS;;
1304 0 : }
1305 :
1306 : int
1307 : fd_execute_instr( fd_exec_txn_ctx_t * txn_ctx,
1308 68853 : fd_instr_info_t * instr ) {
1309 68853 : FD_RUNTIME_TXN_SPAD_FRAME_BEGIN( txn_ctx->spad, txn_ctx ) {
1310 68853 : FD_SCRATCH_SCOPE_BEGIN {
1311 68853 : fd_exec_instr_ctx_t * parent = NULL;
1312 68853 : if( txn_ctx->instr_stack_sz ) {
1313 522 : parent = &txn_ctx->instr_stack[ txn_ctx->instr_stack_sz - 1 ];
1314 522 : }
1315 :
1316 68853 : int err = fd_instr_stack_push( txn_ctx, instr );
1317 68853 : if( FD_UNLIKELY( err ) ) {
1318 60 : FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, err, txn_ctx->instr_err_idx );
1319 60 : return err;
1320 60 : }
1321 :
1322 68793 : fd_exec_instr_ctx_t * ctx = &txn_ctx->instr_stack[ txn_ctx->instr_stack_sz - 1 ];
1323 68793 : *ctx = (fd_exec_instr_ctx_t) {
1324 68793 : .instr = instr,
1325 68793 : .txn_ctx = txn_ctx,
1326 68793 : .epoch_ctx = txn_ctx->epoch_ctx,
1327 68793 : .slot_ctx = txn_ctx->slot_ctx,
1328 68793 : .acc_mgr = txn_ctx->acc_mgr,
1329 68793 : .funk_txn = txn_ctx->funk_txn,
1330 68793 : .parent = parent,
1331 68793 : .index = parent ? (parent->child_cnt++) : 0,
1332 68793 : .depth = parent ? (parent->depth+1 ) : 0,
1333 68793 : .child_cnt = 0U,
1334 68793 : };
1335 :
1336 68793 : txn_ctx->instr_trace[ txn_ctx->instr_trace_length - 1 ] = (fd_exec_instr_trace_entry_t) {
1337 68793 : .instr_info = instr,
1338 68793 : .stack_height = txn_ctx->instr_stack_sz,
1339 68793 : };
1340 :
1341 68793 : fd_exec_instr_fn_t native_prog_fn = fd_executor_lookup_native_program( &txn_ctx->borrowed_accounts[ instr->program_id ] );
1342 68793 : fd_exec_txn_ctx_reset_return_data( txn_ctx );
1343 68793 : int exec_result = FD_EXECUTOR_INSTR_SUCCESS;
1344 68793 : if( native_prog_fn != NULL ) {
1345 : /* Log program invokation (internally caches program_id base58) */
1346 68712 : fd_log_collector_program_invoke( ctx );
1347 68712 : exec_result = native_prog_fn( ctx );
1348 68712 : } else {
1349 81 : exec_result = FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
1350 81 : }
1351 :
1352 68793 : int stack_pop_err = fd_instr_stack_pop( txn_ctx, instr );
1353 68793 : if( FD_LIKELY( exec_result == FD_EXECUTOR_INSTR_SUCCESS ) ) {
1354 : /* Log success */
1355 12969 : fd_log_collector_program_success( ctx );
1356 :
1357 : /* Only report the stack pop error on success */
1358 12969 : if( FD_UNLIKELY( stack_pop_err ) ) {
1359 0 : FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, stack_pop_err, txn_ctx->instr_err_idx );
1360 0 : return stack_pop_err;
1361 0 : }
1362 55824 : } else {
1363 : /* if txn_ctx->exec_err is not set, it indicates an instruction error */
1364 55824 : if( !txn_ctx->exec_err ) {
1365 55713 : FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, exec_result, txn_ctx->instr_err_idx );
1366 55713 : }
1367 :
1368 55824 : if( !txn_ctx->failed_instr ) {
1369 55815 : txn_ctx->failed_instr = ctx;
1370 55815 : ctx->instr_err = (uint)( -exec_result - 1 );
1371 55815 : }
1372 :
1373 : /* Log failure cases.
1374 : We assume that the correct type of error is stored in ctx.
1375 : Syscalls are expected to log when the error is generated, while
1376 : native programs will be logged here.
1377 : (This is because syscall errors often carry data with them.) */
1378 55824 : fd_log_collector_program_failure( ctx );
1379 55824 : }
1380 :
1381 : #ifdef VLOG
1382 : if ( FD_UNLIKELY( exec_result != FD_EXECUTOR_INSTR_SUCCESS ) ) {
1383 : FD_LOG_WARNING(( "instruction executed unsuccessfully: error code %d, custom err: %d, program id: %s", exec_result, txn_ctx->custom_err, FD_BASE58_ENC_32_ALLOCA( instr->program_id_pubkey.uc ));
1384 : } else {
1385 : FD_LOG_WARNING(( "instruction executed successfully: error code %d, custom err: %d, program id: %s", exec_result, txn_ctx->custom_err, FD_BASE58_ENC_32_ALLOCA( instr->program_id_pubkey.uc ));
1386 : }
1387 : #endif
1388 :
1389 68793 : return exec_result;
1390 68853 : } FD_SCRATCH_SCOPE_END;
1391 68853 : } FD_RUNTIME_TXN_SPAD_FRAME_END;
1392 68853 : }
1393 :
1394 : void
1395 0 : fd_txn_reclaim_accounts( fd_exec_txn_ctx_t * txn_ctx ) {
1396 0 : for( ulong i = 0; i < txn_ctx->accounts_cnt; i++ ) {
1397 0 : fd_borrowed_account_t * acc_rec = &txn_ctx->borrowed_accounts[i];
1398 :
1399 : /* An account writable iff it is writable AND it is not being demoted.
1400 : If this criteria is not met, the account should not be marked as touched
1401 : via updating its most recent slot. */
1402 0 : if( !fd_txn_account_is_writable_idx( txn_ctx, (int)i ) ) {
1403 0 : continue;
1404 0 : }
1405 :
1406 0 : acc_rec->meta->slot = txn_ctx->slot_ctx->slot_bank.slot;
1407 :
1408 0 : if( acc_rec->meta->info.lamports == 0 ) {
1409 0 : acc_rec->meta->dlen = 0;
1410 0 : memset( acc_rec->meta->info.owner, 0, sizeof(fd_pubkey_t) );
1411 0 : }
1412 0 : }
1413 0 : }
1414 :
1415 : int
1416 : fd_executor_is_blockhash_valid_for_age( fd_block_hash_queue_t const * block_hash_queue,
1417 : fd_hash_t const * blockhash,
1418 7971 : ulong max_age ) {
1419 7971 : fd_hash_hash_age_pair_t_mapnode_t key;
1420 7971 : fd_memcpy( key.elem.key.uc, blockhash, sizeof(fd_hash_t) );
1421 :
1422 7971 : fd_hash_hash_age_pair_t_mapnode_t * hash_age = fd_hash_hash_age_pair_t_map_find( block_hash_queue->ages_pool, block_hash_queue->ages_root, &key );
1423 7971 : if( hash_age==NULL ) {
1424 : #ifdef VLOG
1425 : FD_LOG_WARNING(( "txn with missing recent blockhash - blockhash: %s", FD_BASE58_ENC_32_ALLOCA( blockhash->uc ) ));
1426 : #endif
1427 2535 : return 0;
1428 2535 : }
1429 5436 : ulong age = block_hash_queue->last_hash_index-hash_age->elem.val.hash_index;
1430 : #ifdef VLOG
1431 : if( age>max_age ) {
1432 : FD_LOG_WARNING(( "txn with old blockhash - age: %lu, blockhash: %s", age, FD_BASE58_ENC_32_ALLOCA( hash_age->elem.key.uc ) ));
1433 : }
1434 : #endif
1435 5436 : return ( age<=max_age );
1436 7971 : }
1437 :
1438 : void
1439 12477 : fd_executor_setup_borrowed_accounts_for_txn( fd_exec_txn_ctx_t * txn_ctx ) {
1440 12477 : ulong j = 0;
1441 77139 : for( ulong i = 0; i < txn_ctx->accounts_cnt; i++ ) {
1442 64662 : FD_SCRATCH_SCOPE_BEGIN {
1443 :
1444 64662 : fd_pubkey_t * acc = &txn_ctx->accounts[i];
1445 64662 : txn_ctx->nonce_accounts[i] = 0;
1446 :
1447 64662 : fd_borrowed_account_t * borrowed_account = fd_borrowed_account_init( &txn_ctx->borrowed_accounts[i] );
1448 64662 : int err = fd_acc_mgr_view( txn_ctx->acc_mgr, txn_ctx->funk_txn, acc, borrowed_account );
1449 64662 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS && err!=FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) {
1450 0 : FD_LOG_ERR(( "fd_acc_mgr_view err=%d", err ));
1451 0 : }
1452 64662 : uchar is_unknown_account = err==FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT;
1453 64662 : memcpy( borrowed_account->pubkey->key, acc, sizeof(fd_pubkey_t) );
1454 64662 : if ( FD_UNLIKELY( is_unknown_account ) ) {
1455 10239 : borrowed_account->account_found = 0;
1456 10239 : }
1457 :
1458 : /* Create a borrowed account for all writable accounts and the fee payer
1459 : account which is almost always writable, but doesn't have to be.
1460 :
1461 : TODO: The borrowed account semantics should better match Agave's. */
1462 64662 : if( fd_txn_account_is_writable_idx( txn_ctx, (int)i ) || i==FD_FEE_PAYER_TXN_IDX ) {
1463 33207 : void * borrowed_account_data = fd_spad_alloc( txn_ctx->spad, FD_ACCOUNT_REC_ALIGN, FD_ACC_TOT_SZ_MAX );
1464 33207 : fd_borrowed_account_make_modifiable( borrowed_account, borrowed_account_data );
1465 :
1466 : /* All new accounts should have their rent epoch set to ULONG_MAX.
1467 : https://github.com/anza-xyz/agave/blob/89050f3cb7e76d9e273f10bea5e8207f2452f79f/svm/src/account_loader.rs#L485-L497 */
1468 33207 : if( is_unknown_account
1469 33207 : || ( i>0UL && fd_should_set_exempt_rent_epoch_max( txn_ctx->slot_ctx, borrowed_account ) ) ) {
1470 9345 : borrowed_account->meta->info.rent_epoch = ULONG_MAX;
1471 9345 : }
1472 33207 : }
1473 :
1474 64662 : fd_account_meta_t const * meta = borrowed_account->const_meta ? borrowed_account->const_meta : borrowed_account->meta;
1475 64662 : if( meta==NULL ) {
1476 4296 : static const fd_account_meta_t sentinel = { .magic = FD_ACCOUNT_META_MAGIC, .info = { .rent_epoch = ULONG_MAX } };
1477 4296 : borrowed_account->const_meta = &sentinel;
1478 4296 : borrowed_account->starting_lamports = 0UL;
1479 4296 : borrowed_account->starting_dlen = 0UL;
1480 4296 : continue;
1481 4296 : }
1482 :
1483 60366 : if( meta->info.executable ) {
1484 26475 : FD_BORROWED_ACCOUNT_DECL(owner_borrowed_account);
1485 26475 : int err = fd_acc_mgr_view( txn_ctx->acc_mgr, txn_ctx->funk_txn, (fd_pubkey_t *)meta->info.owner, owner_borrowed_account );
1486 26475 : if( FD_UNLIKELY( err ) ) {
1487 17583 : borrowed_account->starting_owner_dlen = 0UL;
1488 17583 : } else {
1489 8892 : borrowed_account->starting_owner_dlen = owner_borrowed_account->const_meta->dlen;
1490 8892 : }
1491 26475 : }
1492 :
1493 60366 : if( FD_UNLIKELY( memcmp( meta->info.owner, fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) == 0 ) ) {
1494 4641 : fd_bpf_upgradeable_loader_state_t program_loader_state = {0};
1495 4641 : int err = 0;
1496 4641 : if( FD_UNLIKELY( !read_bpf_upgradeable_loader_state_for_program( txn_ctx, (uchar) i, &program_loader_state, &err ) ) ) {
1497 1503 : continue;
1498 1503 : }
1499 :
1500 3138 : if( !fd_bpf_upgradeable_loader_state_is_program( &program_loader_state ) ) {
1501 1467 : continue;
1502 1467 : }
1503 :
1504 1671 : fd_pubkey_t * programdata_acc = &program_loader_state.inner.program.programdata_address;
1505 1671 : fd_borrowed_account_t * executable_account = fd_borrowed_account_init( &txn_ctx->executable_accounts[j] );
1506 1671 : fd_acc_mgr_view( txn_ctx->acc_mgr, txn_ctx->funk_txn, programdata_acc, executable_account);
1507 1671 : j++;
1508 1671 : }
1509 :
1510 64662 : } FD_SCRATCH_SCOPE_END;
1511 64662 : }
1512 12477 : txn_ctx->executable_cnt = j;
1513 12477 : }
1514 :
1515 : /* Stuff to be done before multithreading can begin */
1516 : int
1517 : fd_execute_txn_prepare_start( fd_exec_slot_ctx_t * slot_ctx,
1518 : fd_exec_txn_ctx_t * txn_ctx,
1519 : fd_txn_t const * txn_descriptor,
1520 12741 : fd_rawtxn_b_t const * txn_raw ) {
1521 : /* Init txn ctx */
1522 12741 : fd_exec_txn_ctx_new( txn_ctx );
1523 12741 : fd_exec_txn_ctx_from_exec_slot_ctx( slot_ctx, txn_ctx );
1524 12741 : fd_exec_txn_ctx_setup( txn_ctx, txn_descriptor, txn_raw );
1525 :
1526 : /* Unroll accounts from aluts and place into correct spots */
1527 12741 : int res = fd_executor_setup_accessed_accounts_for_txn( txn_ctx );
1528 :
1529 12741 : return res;
1530 : /* TODO:FIXME: MOVE THIS PELASE */
1531 12741 : }
1532 :
1533 : int
1534 0 : fd_executor_txn_verify( fd_exec_txn_ctx_t * txn_ctx ) {
1535 0 : FD_SCRATCH_SCOPE_BEGIN {
1536 0 : fd_sha512_t * shas[ FD_TXN_ACTUAL_SIG_MAX ];
1537 0 : for ( ulong i=0; i<FD_TXN_ACTUAL_SIG_MAX; i++ ) {
1538 0 : fd_sha512_t * sha = fd_sha512_join( fd_sha512_new( fd_scratch_alloc( alignof( fd_sha512_t ), sizeof( fd_sha512_t ) ) ) );
1539 0 : if( FD_UNLIKELY( !sha ) ) FD_LOG_ERR(( "fd_sha512_join failed" ));
1540 0 : shas[i] = sha;
1541 0 : }
1542 :
1543 0 : uchar signature_cnt = txn_ctx->txn_descriptor->signature_cnt;
1544 0 : ushort signature_off = txn_ctx->txn_descriptor->signature_off;
1545 0 : ushort acct_addr_off = txn_ctx->txn_descriptor->acct_addr_off;
1546 0 : ushort message_off = txn_ctx->txn_descriptor->message_off;
1547 :
1548 0 : uchar const * signatures = (uchar *)txn_ctx->_txn_raw->raw + signature_off;
1549 0 : uchar const * pubkeys = (uchar *)txn_ctx->_txn_raw->raw + acct_addr_off;
1550 0 : uchar const * msg = (uchar *)txn_ctx->_txn_raw->raw + message_off;
1551 0 : ulong msg_sz = (ulong)txn_ctx->_txn_raw->txn_sz - message_off;
1552 :
1553 : /* Verify signatures */
1554 0 : int res = fd_ed25519_verify_batch_single_msg( msg, msg_sz, signatures, pubkeys, shas, signature_cnt );
1555 0 : if( FD_UNLIKELY( res != FD_ED25519_SUCCESS ) ) {
1556 0 : return -1;
1557 0 : }
1558 :
1559 0 : return 0;
1560 0 : } FD_SCRATCH_SCOPE_END;
1561 0 : }
1562 :
1563 : /* Creates a TxnContext Protobuf message from a provided txn_ctx.
1564 : - The transaction is assumed to have just finished phase 1 of preparation
1565 : - Caller of this function should have a scratch frame ready
1566 : */
1567 : static void
1568 : create_txn_context_protobuf_from_txn( fd_exec_test_txn_context_t * txn_context_msg,
1569 : fd_exec_txn_ctx_t * txn_ctx,
1570 0 : fd_spad_t * spad ) {
1571 0 : fd_txn_t const * txn_descriptor = txn_ctx->txn_descriptor;
1572 0 : uchar const * txn_payload = (uchar const *) txn_ctx->_txn_raw->raw;
1573 0 : fd_exec_slot_ctx_t const * slot_ctx = txn_ctx->slot_ctx;
1574 :
1575 : /* We don't want to store builtins in account shared data */
1576 0 : fd_pubkey_t const loaded_builtins[] = {
1577 0 : fd_solana_system_program_id,
1578 0 : fd_solana_vote_program_id,
1579 0 : fd_solana_stake_program_id,
1580 0 : fd_solana_config_program_id,
1581 : // fd_solana_zk_token_proof_program_id,
1582 0 : fd_solana_bpf_loader_v4_program_id,
1583 0 : fd_solana_address_lookup_table_program_id,
1584 0 : fd_solana_bpf_loader_deprecated_program_id,
1585 0 : fd_solana_bpf_loader_program_id,
1586 0 : fd_solana_bpf_loader_upgradeable_program_id,
1587 0 : fd_solana_compute_budget_program_id,
1588 0 : fd_solana_keccak_secp_256k_program_id,
1589 0 : fd_solana_secp256r1_program_id,
1590 0 : fd_solana_zk_elgamal_proof_program_id,
1591 0 : fd_solana_ed25519_sig_verify_program_id,
1592 0 : fd_solana_spl_native_mint_id,
1593 0 : };
1594 0 : const ulong num_loaded_builtins = (sizeof(loaded_builtins) / sizeof(fd_pubkey_t));
1595 :
1596 : /* Prepare sysvar cache accounts */
1597 0 : fd_pubkey_t const fd_relevant_sysvar_ids[] = {
1598 0 : fd_sysvar_clock_id,
1599 0 : fd_sysvar_epoch_schedule_id,
1600 0 : fd_sysvar_epoch_rewards_id,
1601 0 : fd_sysvar_fees_id,
1602 0 : fd_sysvar_rent_id,
1603 0 : fd_sysvar_slot_hashes_id,
1604 0 : fd_sysvar_recent_block_hashes_id,
1605 0 : fd_sysvar_stake_history_id,
1606 0 : fd_sysvar_last_restart_slot_id,
1607 0 : fd_sysvar_instructions_id,
1608 0 : };
1609 0 : const ulong num_sysvar_entries = (sizeof(fd_relevant_sysvar_ids) / sizeof(fd_pubkey_t));
1610 :
1611 : /* Transaction Context -> tx */
1612 0 : txn_context_msg->has_tx = true;
1613 0 : fd_exec_test_sanitized_transaction_t * sanitized_transaction = &txn_context_msg->tx;
1614 :
1615 : /* Transaction Context -> tx -> message */
1616 0 : sanitized_transaction->has_message = true;
1617 0 : fd_exec_test_transaction_message_t * message = &sanitized_transaction->message;
1618 :
1619 : /* Transaction Context -> tx -> message -> is_legacy */
1620 0 : message->is_legacy = txn_descriptor->transaction_version == FD_TXN_VLEGACY;
1621 :
1622 : /* Transaction Context -> tx -> message -> header */
1623 0 : message->has_header = true;
1624 0 : fd_exec_test_message_header_t * header = &message->header;
1625 :
1626 : /* Transaction Context -> tx -> message -> header -> num_required_signatures */
1627 0 : header->num_required_signatures = txn_descriptor->signature_cnt;
1628 :
1629 : /* Transaction Context -> tx -> message -> header -> num_readonly_signed_accounts */
1630 0 : header->num_readonly_signed_accounts = txn_descriptor->readonly_signed_cnt;
1631 :
1632 : /* Transaction Context -> tx -> message -> header -> num_readonly_unsigned_accounts */
1633 0 : header->num_readonly_unsigned_accounts = txn_descriptor->readonly_unsigned_cnt;
1634 :
1635 : /* Transaction Context -> tx -> message -> account_keys */
1636 0 : message->account_keys_count = txn_descriptor->acct_addr_cnt;
1637 0 : message->account_keys = fd_scratch_alloc( alignof(pb_bytes_array_t *), PB_BYTES_ARRAY_T_ALLOCSIZE(txn_descriptor->acct_addr_cnt * sizeof(pb_bytes_array_t *)) );
1638 0 : fd_acct_addr_t const * account_keys = fd_txn_get_acct_addrs( txn_descriptor, txn_payload );
1639 0 : for( ulong i = 0; i < txn_descriptor->acct_addr_cnt; i++ ) {
1640 0 : pb_bytes_array_t * account_key = fd_scratch_alloc( alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE(sizeof(fd_pubkey_t)) );
1641 0 : account_key->size = sizeof(fd_pubkey_t);
1642 0 : memcpy( account_key->bytes, &account_keys[i], sizeof(fd_pubkey_t) );
1643 0 : message->account_keys[i] = account_key;
1644 0 : }
1645 :
1646 : /* Transaction Context -> tx -> message -> account_shared_data
1647 : Contains:
1648 : - Account data for regular accounts
1649 : - Account data for LUT accounts
1650 : - Account data for executable accounts
1651 : - Account data for (almost) all sysvars
1652 : */
1653 : // Dump regular accounts first
1654 0 : message->account_shared_data_count = 0;
1655 0 : message->account_shared_data = fd_scratch_alloc( alignof(fd_exec_test_acct_state_t),
1656 0 : (txn_ctx->accounts_cnt * 2 + txn_descriptor->addr_table_lookup_cnt + num_sysvar_entries) * sizeof(fd_exec_test_acct_state_t) );
1657 0 : for( ulong i = 0; i < txn_ctx->accounts_cnt; ++i ) {
1658 0 : FD_BORROWED_ACCOUNT_DECL(borrowed_account);
1659 0 : int ret = fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, &txn_ctx->accounts[i], borrowed_account );
1660 0 : if( FD_UNLIKELY(ret != FD_ACC_MGR_SUCCESS) ) {
1661 0 : continue;
1662 0 : }
1663 :
1664 : // Make sure account is not a builtin
1665 0 : bool is_builtin = false;
1666 0 : for( ulong j = 0; j < num_loaded_builtins; ++j ) {
1667 0 : if( 0 == memcmp( &txn_ctx->accounts[i], &loaded_builtins[j], sizeof(fd_pubkey_t) ) ) {
1668 0 : is_builtin = true;
1669 0 : break;
1670 0 : }
1671 0 : }
1672 0 : if( !is_builtin ) {
1673 0 : dump_account_state( borrowed_account, &message->account_shared_data[message->account_shared_data_count++] );
1674 0 : }
1675 0 : }
1676 :
1677 : // For executable accounts, we need to set up dummy borrowed accounts by cluttering txn ctx state and resetting it after
1678 : // TODO: Revisit this hacky approach
1679 0 : txn_ctx->spad = spad;
1680 0 : fd_spad_push( txn_ctx->spad );
1681 0 : txn_ctx->funk_txn = slot_ctx->funk_txn;
1682 0 : fd_executor_setup_borrowed_accounts_for_txn( txn_ctx );
1683 :
1684 : // Dump executable accounts
1685 0 : for( ulong i = 0; i < txn_ctx->executable_cnt; ++i ) {
1686 0 : if( !txn_ctx->executable_accounts[i].const_meta ) {
1687 0 : continue;
1688 0 : }
1689 0 : dump_account_state( &txn_ctx->executable_accounts[i], &message->account_shared_data[message->account_shared_data_count++] );
1690 0 : }
1691 :
1692 : // Reset state
1693 0 : txn_ctx->funk_txn = NULL;
1694 0 : txn_ctx->executable_cnt = 0;
1695 0 : fd_spad_pop( txn_ctx->spad );
1696 :
1697 : // Dump LUT accounts
1698 0 : fd_txn_acct_addr_lut_t const * address_lookup_tables = fd_txn_get_address_tables_const( txn_descriptor );
1699 0 : for( ulong i = 0; i < txn_descriptor->addr_table_lookup_cnt; ++i ) {
1700 0 : FD_BORROWED_ACCOUNT_DECL(borrowed_account);
1701 0 : fd_pubkey_t * alut_key = (fd_pubkey_t *) (txn_payload + address_lookup_tables[i].addr_off);
1702 0 : int ret = fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, alut_key, borrowed_account );
1703 0 : if( FD_UNLIKELY(ret != FD_ACC_MGR_SUCCESS) ) {
1704 0 : continue;
1705 0 : }
1706 0 : dump_account_state( borrowed_account, &message->account_shared_data[message->account_shared_data_count++] );
1707 0 : }
1708 :
1709 : // Dump sysvars
1710 0 : for( ulong i = 0; i < num_sysvar_entries; i++ ) {
1711 0 : FD_BORROWED_ACCOUNT_DECL(borrowed_account);
1712 0 : int ret = fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, &fd_relevant_sysvar_ids[i], borrowed_account );
1713 0 : if( ret != FD_ACC_MGR_SUCCESS ) {
1714 0 : continue;
1715 0 : }
1716 :
1717 : // Make sure the account doesn't exist in the output accounts yet
1718 0 : int account_exists = 0;
1719 0 : for( ulong j = 0; j < txn_ctx->accounts_cnt; j++ ) {
1720 0 : if ( 0 == memcmp( txn_ctx->accounts[j].key, fd_relevant_sysvar_ids[i].uc, sizeof(fd_pubkey_t) ) ) {
1721 0 : account_exists = true;
1722 0 : break;
1723 0 : }
1724 0 : }
1725 : // Copy it into output
1726 0 : if (!account_exists) {
1727 0 : dump_account_state( borrowed_account, &message->account_shared_data[message->account_shared_data_count++] );
1728 0 : }
1729 0 : }
1730 :
1731 : /* Transaction Context -> tx -> message -> recent_blockhash */
1732 0 : uchar const * recent_blockhash = fd_txn_get_recent_blockhash( txn_descriptor, txn_payload );
1733 0 : message->recent_blockhash = fd_scratch_alloc( alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE(sizeof(fd_hash_t)) );
1734 0 : message->recent_blockhash->size = sizeof(fd_hash_t);
1735 0 : memcpy( message->recent_blockhash->bytes, recent_blockhash, sizeof(fd_hash_t) );
1736 :
1737 : /* Transaction Context -> tx -> message -> instructions */
1738 0 : message->instructions_count = txn_descriptor->instr_cnt;
1739 0 : message->instructions = fd_scratch_alloc( alignof(fd_exec_test_compiled_instruction_t), txn_descriptor->instr_cnt * sizeof(fd_exec_test_compiled_instruction_t) );
1740 0 : for( ulong i = 0; i < txn_descriptor->instr_cnt; ++i ) {
1741 0 : fd_txn_instr_t instr = txn_descriptor->instr[i];
1742 0 : fd_exec_test_compiled_instruction_t * compiled_instruction = &message->instructions[i];
1743 :
1744 : // compiled instruction -> program_id_index
1745 0 : compiled_instruction->program_id_index = instr.program_id;
1746 :
1747 : // compiled instruction -> accounts
1748 0 : compiled_instruction->accounts_count = instr.acct_cnt;
1749 0 : compiled_instruction->accounts = fd_scratch_alloc( alignof(uint32_t), instr.acct_cnt * sizeof(uint32_t) );
1750 0 : uchar const * instr_accounts = fd_txn_get_instr_accts( &instr, txn_payload );
1751 0 : for( ulong j = 0; j < instr.acct_cnt; ++j ) {
1752 0 : uchar instr_acct_index = instr_accounts[j];
1753 0 : compiled_instruction->accounts[j] = instr_acct_index;
1754 0 : }
1755 :
1756 : // compiled instruction -> data
1757 0 : uchar const * instr_data = fd_txn_get_instr_data( &instr, txn_payload );
1758 0 : compiled_instruction->data = fd_scratch_alloc( alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE(instr.data_sz) );
1759 0 : compiled_instruction->data->size = instr.data_sz;
1760 0 : memcpy( compiled_instruction->data->bytes, instr_data, instr.data_sz );
1761 0 : }
1762 :
1763 : /* ALUT stuff (non-legacy) */
1764 0 : message->address_table_lookups_count = 0;
1765 0 : if( !message->is_legacy ) {
1766 : /* Transaction Context -> tx -> message -> address_table_lookups */
1767 0 : message->address_table_lookups_count = txn_descriptor->addr_table_lookup_cnt;
1768 0 : message->address_table_lookups = fd_scratch_alloc( alignof(fd_exec_test_message_address_table_lookup_t),
1769 0 : txn_descriptor->addr_table_lookup_cnt * sizeof(fd_exec_test_message_address_table_lookup_t) );
1770 0 : for( ulong i = 0; i < txn_descriptor->addr_table_lookup_cnt; ++i ) {
1771 : // alut -> account_key
1772 0 : fd_pubkey_t * alut_key = (fd_pubkey_t *) (txn_payload + address_lookup_tables[i].addr_off);
1773 0 : memcpy( message->address_table_lookups[i].account_key, alut_key, sizeof(fd_pubkey_t) );
1774 :
1775 : // Access ALUT account data to access its keys
1776 0 : FD_BORROWED_ACCOUNT_DECL(addr_lut_rec);
1777 0 : int err = fd_acc_mgr_view(slot_ctx->acc_mgr, slot_ctx->funk_txn, alut_key, addr_lut_rec);
1778 0 : if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) {
1779 0 : FD_LOG_ERR(( "addr lut not found" ));
1780 0 : }
1781 :
1782 : // alut -> writable_indexes
1783 0 : message->address_table_lookups[i].writable_indexes_count = address_lookup_tables[i].writable_cnt;
1784 0 : message->address_table_lookups[i].writable_indexes = fd_scratch_alloc( alignof(uint32_t), address_lookup_tables[i].writable_cnt * sizeof(uint32_t) );
1785 0 : uchar * writable_indexes = (uchar *) (txn_payload + address_lookup_tables[i].writable_off);
1786 0 : for( ulong j = 0; j < address_lookup_tables[i].writable_cnt; ++j ) {
1787 0 : message->address_table_lookups[i].writable_indexes[j] = writable_indexes[j];
1788 0 : }
1789 :
1790 : // alut -> readonly_indexes
1791 0 : message->address_table_lookups[i].readonly_indexes_count = address_lookup_tables[i].readonly_cnt;
1792 0 : message->address_table_lookups[i].readonly_indexes = fd_scratch_alloc( alignof(uint32_t), address_lookup_tables[i].readonly_cnt * sizeof(uint32_t) );
1793 0 : uchar * readonly_indexes = (uchar *) (txn_payload + address_lookup_tables[i].readonly_off);
1794 0 : for( ulong j = 0; j < address_lookup_tables[i].readonly_cnt; ++j ) {
1795 0 : message->address_table_lookups[i].readonly_indexes[j] = readonly_indexes[j];
1796 0 : }
1797 0 : }
1798 0 : }
1799 :
1800 : /* Transaction Context -> tx -> message_hash */
1801 : // Skip because it does not matter what's in here
1802 :
1803 : /* Transaction Context -> tx -> is_simple_vote_tx */
1804 : // Doesn't matter for FD, but does for Agave
1805 : // https://github.com/anza-xyz/agave/blob/9a7bf72940f4b3cd7fc94f54e005868ce707d53d/sdk/src/simple_vote_transaction_checker.rs#L5-L9
1806 0 : sanitized_transaction->is_simple_vote_tx = ( txn_descriptor->signature_cnt < 3 )
1807 0 : && ( message->is_legacy )
1808 0 : && ( txn_descriptor->instr_cnt == 1 )
1809 0 : && ( 0 == memcmp( &txn_ctx->accounts[txn_descriptor->instr[0].program_id], fd_solana_vote_program_id.key, sizeof(fd_pubkey_t) ) );
1810 :
1811 : /* Transaction Context -> tx -> signatures */
1812 0 : sanitized_transaction->signatures_count = txn_descriptor->signature_cnt;
1813 0 : sanitized_transaction->signatures = fd_scratch_alloc( alignof(pb_bytes_array_t *), PB_BYTES_ARRAY_T_ALLOCSIZE(txn_descriptor->signature_cnt * sizeof(pb_bytes_array_t *)) );
1814 0 : fd_ed25519_sig_t const * signatures = fd_txn_get_signatures( txn_descriptor, txn_payload );
1815 0 : for( uchar i = 0; i < txn_descriptor->signature_cnt; ++i ) {
1816 0 : pb_bytes_array_t * signature = fd_scratch_alloc( alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE(sizeof(fd_ed25519_sig_t)) );
1817 0 : signature->size = sizeof(fd_ed25519_sig_t);
1818 0 : memcpy( signature->bytes, &signatures[i], sizeof(fd_ed25519_sig_t) );
1819 0 : sanitized_transaction->signatures[i] = signature;
1820 0 : }
1821 :
1822 : /* Transaction Context -> blockhash_queue
1823 : NOTE: Agave's implementation of register_hash incorrectly allows the blockhash queue to hold max_age + 1 (max 301)
1824 : entries. We have this incorrect logic implemented in fd_sysvar_recent_hashes:register_blockhash and it's not a
1825 : huge issue, but something to keep in mind. */
1826 0 : pb_bytes_array_t ** output_blockhash_queue = fd_scratch_alloc(
1827 0 : alignof(pb_bytes_array_t *),
1828 0 : PB_BYTES_ARRAY_T_ALLOCSIZE((FD_BLOCKHASH_QUEUE_MAX_ENTRIES + 1) * sizeof(pb_bytes_array_t *)) );
1829 0 : txn_context_msg->blockhash_queue_count = 0;
1830 0 : txn_context_msg->blockhash_queue = output_blockhash_queue;
1831 :
1832 : // Iterate over all block hashes in the queue and save them
1833 0 : fd_block_hash_queue_t const * queue = &slot_ctx->slot_bank.block_hash_queue;
1834 0 : fd_hash_hash_age_pair_t_mapnode_t * nn;
1835 0 : for ( fd_hash_hash_age_pair_t_mapnode_t * n = fd_hash_hash_age_pair_t_map_minimum( queue->ages_pool, queue->ages_root ); n; n = nn ) {
1836 0 : nn = fd_hash_hash_age_pair_t_map_successor( queue->ages_pool, n );
1837 :
1838 : /* Get the index in the blockhash queue
1839 : - Lower index = newer
1840 : - 0 will be the most recent blockhash
1841 : - Index range is [0, max_age] (not a typo) */
1842 0 : ulong queue_index = queue->last_hash_index - n->elem.val.hash_index;
1843 0 : fd_hash_t blockhash = n->elem.key;
1844 :
1845 : // Write the blockhash to the correct index (note we write in reverse order since in the Protobuf message, the oldest blockhash goes first)
1846 0 : pb_bytes_array_t * output_blockhash = fd_scratch_alloc( alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE(sizeof(fd_hash_t)) );
1847 0 : output_blockhash->size = sizeof(fd_hash_t);
1848 0 : memcpy( output_blockhash->bytes, &blockhash, sizeof(fd_hash_t) );
1849 0 : output_blockhash_queue[FD_BLOCKHASH_QUEUE_MAX_ENTRIES - queue_index] = output_blockhash;
1850 0 : txn_context_msg->blockhash_queue_count++;
1851 0 : }
1852 :
1853 : // Shift blockhash queue elements if num elements < 301
1854 0 : if( txn_context_msg->blockhash_queue_count < FD_BLOCKHASH_QUEUE_MAX_ENTRIES + 1 ) {
1855 0 : ulong index_offset = FD_BLOCKHASH_QUEUE_MAX_ENTRIES + 1 - txn_context_msg->blockhash_queue_count;
1856 0 : for( ulong i = 0; i < txn_context_msg->blockhash_queue_count; i++ ) {
1857 0 : output_blockhash_queue[i] = output_blockhash_queue[i + index_offset];
1858 0 : }
1859 0 : }
1860 :
1861 : /* Transaction Context -> epoch_ctx */
1862 0 : txn_context_msg->has_epoch_ctx = true;
1863 0 : txn_context_msg->epoch_ctx.has_features = true;
1864 0 : dump_sorted_features( &txn_ctx->epoch_ctx->features, &txn_context_msg->epoch_ctx.features );
1865 :
1866 : /* Transaction Context -> slot_ctx */
1867 0 : txn_context_msg->has_slot_ctx = true;
1868 0 : txn_context_msg->slot_ctx.slot = slot_ctx->slot_bank.slot;
1869 0 : }
1870 :
1871 : /* Similar to dump_instr_to_protobuf, but dumps individual transactions from a ledger replay.
1872 :
1873 : This is more reliable for BPF program invocations since solfuzz-agave's transaction replay harness
1874 : is more robust.
1875 :
1876 : The following arguments can be added when replaying ledger transactions:
1877 : --dump-txn-to-pb <0/1>
1878 : * If enabled, transactions will be dumped to the specified output directory
1879 : --dump-proto-sig-filter <base_58_enc_sig>
1880 : * If enabled, only transactions with the specified signature will be dumped
1881 : * Provided signature must be base58-encoded
1882 : * Default behavior if signature filter is not provided is to dump EVERY transaction
1883 : --dump-proto-output-dir <output_dir>
1884 : * Each file represents a single transaction as a serialized TxnContext Protobuf message
1885 : * File name format is "txn-<base58_enc_sig>.bin"
1886 : */
1887 : void
1888 0 : fd_dump_txn_to_protobuf( fd_exec_txn_ctx_t *txn_ctx, fd_spad_t * spad ) {
1889 0 : FD_SCRATCH_SCOPE_BEGIN {
1890 : // Get base58-encoded tx signature
1891 0 : const fd_ed25519_sig_t * signatures = fd_txn_get_signatures( txn_ctx->txn_descriptor, txn_ctx->_txn_raw->raw );
1892 0 : fd_ed25519_sig_t signature; fd_memcpy( signature, signatures[0], sizeof(fd_ed25519_sig_t) );
1893 0 : char encoded_signature[FD_BASE58_ENCODED_64_SZ];
1894 0 : ulong out_size;
1895 0 : fd_base58_encode_64( signature, &out_size, encoded_signature );
1896 :
1897 0 : if( txn_ctx->capture_ctx->dump_proto_sig_filter ) {
1898 : // Terminate early if the signature does not match
1899 0 : if( strcmp( txn_ctx->capture_ctx->dump_proto_sig_filter, encoded_signature ) ) {
1900 0 : return;
1901 0 : }
1902 0 : }
1903 :
1904 0 : fd_exec_test_txn_context_t txn_context_msg = FD_EXEC_TEST_TXN_CONTEXT_INIT_DEFAULT;
1905 0 : create_txn_context_protobuf_from_txn( &txn_context_msg, txn_ctx, spad );
1906 :
1907 : /* Output to file */
1908 0 : ulong out_buf_size = 100 * 1024 * 1024;
1909 0 : uint8_t * out = fd_scratch_alloc( alignof(uint8_t), out_buf_size );
1910 0 : pb_ostream_t stream = pb_ostream_from_buffer( out, out_buf_size );
1911 0 : if( pb_encode( &stream, FD_EXEC_TEST_TXN_CONTEXT_FIELDS, &txn_context_msg ) ) {
1912 0 : char output_filepath[256]; fd_memset( output_filepath, 0, sizeof(output_filepath) );
1913 0 : char * position = fd_cstr_init( output_filepath );
1914 0 : position = fd_cstr_append_cstr( position, txn_ctx->capture_ctx->dump_proto_output_dir );
1915 0 : position = fd_cstr_append_cstr( position, "/txn-" );
1916 0 : position = fd_cstr_append_cstr( position, encoded_signature );
1917 0 : position = fd_cstr_append_cstr(position, ".bin");
1918 0 : fd_cstr_fini(position);
1919 :
1920 0 : FILE * file = fopen(output_filepath, "wb");
1921 0 : if( file ) {
1922 0 : fwrite( out, 1, stream.bytes_written, file );
1923 0 : fclose( file );
1924 0 : }
1925 0 : }
1926 0 : } FD_SCRATCH_SCOPE_END;
1927 0 : }
1928 :
1929 : int
1930 5190 : fd_execute_txn( fd_exec_txn_ctx_t * txn_ctx ) {
1931 5190 : FD_SCRATCH_SCOPE_BEGIN {
1932 5190 : uint use_sysvar_instructions = fd_executor_txn_uses_sysvar_instructions( txn_ctx );
1933 5190 : int ret = 0;
1934 :
1935 : #ifdef VLOG
1936 : fd_txn_t const *txn = txn_ctx->txn_descriptor;
1937 : fd_rawtxn_b_t const *raw_txn = txn_ctx->_txn_raw;
1938 : uchar * sig = (uchar *)raw_txn->raw + txn->signature_off;
1939 : #endif
1940 :
1941 5190 : bool dump_insn = txn_ctx->capture_ctx && txn_ctx->slot_ctx->slot_bank.slot >= txn_ctx->capture_ctx->dump_proto_start_slot && txn_ctx->capture_ctx->dump_insn_to_pb;
1942 :
1943 : /* Initialize log collection */
1944 5190 : fd_log_collector_init( &txn_ctx->log_collector, txn_ctx->slot_ctx->enable_exec_recording );
1945 :
1946 8049 : for ( ushort i = 0; i < txn_ctx->txn_descriptor->instr_cnt; i++ ) {
1947 : #ifdef VLOG
1948 : FD_LOG_WARNING(( "Start of transaction for %d for %s", i, FD_BASE58_ENC_64_ALLOCA( sig ) ));
1949 : #endif
1950 :
1951 6339 : if ( FD_UNLIKELY( use_sysvar_instructions ) ) {
1952 0 : ret = fd_sysvar_instructions_update_current_instr_idx( txn_ctx, i );
1953 0 : if( ret != FD_ACC_MGR_SUCCESS ) {
1954 0 : FD_LOG_WARNING(( "sysvar instructions failed to update instruction index" ));
1955 0 : return ret;
1956 0 : }
1957 0 : }
1958 :
1959 6339 : if( dump_insn ) {
1960 : // Capture the input and convert it into a Protobuf message
1961 0 : dump_instr_to_protobuf(txn_ctx, &txn_ctx->instr_infos[i], i);
1962 0 : }
1963 :
1964 :
1965 6339 : int exec_result = fd_execute_instr( txn_ctx, &txn_ctx->instr_infos[i] );
1966 : #ifdef VLOG
1967 : FD_LOG_WARNING(( "fd_execute_instr result (%d) for %s", exec_result, FD_BASE58_ENC_64_ALLOCA( sig ) ));
1968 : #endif
1969 6339 : if( exec_result != FD_EXECUTOR_INSTR_SUCCESS ) {
1970 3480 : if ( txn_ctx->instr_err_idx == INT_MAX )
1971 3480 : {
1972 3480 : txn_ctx->instr_err_idx = i;
1973 3480 : }
1974 : #ifdef VLOG
1975 : if ( 257037453 == txn_ctx->slot_ctx->slot_bank.slot ) {
1976 : #endif
1977 3480 : if (exec_result == FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR ) {
1978 : #ifdef VLOG
1979 : FD_LOG_WARNING(( "fd_execute_instr failed (%d:%d) for %s",
1980 : exec_result,
1981 : txn_ctx->custom_err,
1982 : FD_BASE58_ENC_64_ALLOCA( sig ) ));
1983 : #endif
1984 2805 : } else {
1985 : #ifdef VLOG
1986 : FD_LOG_WARNING(( "fd_execute_instr failed (%d) index %u for %s",
1987 : exec_result,
1988 : i,
1989 : FD_BASE58_ENC_64_ALLOCA( sig ) ));
1990 : #endif
1991 2805 : }
1992 : #ifdef VLOG
1993 : }
1994 : #endif
1995 3480 : return exec_result;
1996 3480 : }
1997 6339 : }
1998 1710 : int err = fd_executor_txn_check( txn_ctx->slot_ctx, txn_ctx );
1999 1710 : if ( err != FD_EXECUTOR_INSTR_SUCCESS) {
2000 6 : FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, err, txn_ctx->instr_err_idx );
2001 6 : FD_LOG_DEBUG(( "fd_executor_txn_check failed (%d)", err ));
2002 6 : return err;
2003 6 : }
2004 :
2005 1704 : return 0;
2006 5190 : } FD_SCRATCH_SCOPE_END;
2007 5190 : }
2008 :
2009 : int
2010 : fd_executor_txn_check( fd_exec_slot_ctx_t const * slot_ctx,
2011 1710 : fd_exec_txn_ctx_t * txn ) {
2012 1710 : fd_rent_t const * rent = fd_sysvar_cache_rent( slot_ctx->sysvar_cache );
2013 :
2014 1710 : ulong starting_lamports_l = 0;
2015 1710 : ulong starting_lamports_h = 0;
2016 :
2017 1710 : ulong ending_lamports_l = 0;
2018 1710 : ulong ending_lamports_h = 0;
2019 :
2020 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L63 */
2021 8043 : for( ulong idx = 0; idx < txn->accounts_cnt; idx++ ) {
2022 6339 : fd_borrowed_account_t * b = &txn->borrowed_accounts[idx];
2023 :
2024 : // Was this account written to?
2025 6339 : if( NULL != b->meta ) {
2026 3480 : fd_uwide_inc( &ending_lamports_h, &ending_lamports_l, ending_lamports_h, ending_lamports_l, b->meta->info.lamports );
2027 :
2028 : /* Rent states are defined as followed:
2029 : - lamports == 0 -> Uninitialized
2030 : - 0 < lamports < rent_exempt_minimum -> RentPaying
2031 : - lamports >= rent_exempt_minimum -> RentExempt
2032 : In Agave, 'self' refers to our 'after' state. */
2033 3480 : uchar after_uninitialized = b->meta->info.lamports == 0;
2034 3480 : uchar after_rent_exempt = b->meta->info.lamports >= fd_rent_exempt_minimum_balance( rent, b->meta->dlen );
2035 :
2036 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L96 */
2037 3480 : if( FD_LIKELY( memcmp( b->pubkey->key, fd_sysvar_incinerator_id.key, sizeof(fd_pubkey_t) ) != 0 ) ) {
2038 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L44 */
2039 3480 : if( after_uninitialized || after_rent_exempt ) {
2040 : // no-op
2041 3369 : } else {
2042 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L45-L59 */
2043 111 : uchar before_uninitialized = b->starting_dlen == ULONG_MAX || b->starting_lamports == 0;
2044 111 : uchar before_rent_exempt = b->starting_dlen != ULONG_MAX && b->starting_lamports >= fd_rent_exempt_minimum_balance( rent, b->starting_dlen );
2045 :
2046 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L50 */
2047 111 : if( before_uninitialized || before_rent_exempt ) {
2048 3 : FD_LOG_DEBUG(( "Rent exempt error for %s Curr len %lu Starting len %lu Curr lamports %lu Starting lamports %lu Curr exempt %lu Starting exempt %lu",
2049 3 : FD_BASE58_ENC_32_ALLOCA( b->pubkey->uc ),
2050 3 : b->meta->dlen,
2051 3 : b->starting_dlen,
2052 3 : b->meta->info.lamports,
2053 3 : b->starting_lamports,
2054 3 : fd_rent_exempt_minimum_balance( rent, b->meta->dlen ),
2055 3 : fd_rent_exempt_minimum_balance( rent, b->starting_dlen ) ));
2056 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L104 */
2057 3 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
2058 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L56 */
2059 108 : } else if( (b->meta->dlen == b->starting_dlen) && b->meta->info.lamports <= b->starting_lamports ) {
2060 : // no-op
2061 105 : } else {
2062 3 : FD_LOG_DEBUG(( "Rent exempt error for %s Curr len %lu Starting len %lu Curr lamports %lu Starting lamports %lu Curr exempt %lu Starting exempt %lu",
2063 3 : FD_BASE58_ENC_32_ALLOCA( b->pubkey->uc ),
2064 3 : b->meta->dlen,
2065 3 : b->starting_dlen,
2066 3 : b->meta->info.lamports,
2067 3 : b->starting_lamports,
2068 3 : fd_rent_exempt_minimum_balance( rent, b->meta->dlen ),
2069 3 : fd_rent_exempt_minimum_balance( rent, b->starting_dlen ) ));
2070 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L104 */
2071 3 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
2072 3 : }
2073 111 : }
2074 3480 : }
2075 :
2076 3474 : if( b->starting_lamports != ULONG_MAX ) {
2077 3384 : fd_uwide_inc( &starting_lamports_h, &starting_lamports_l, starting_lamports_h, starting_lamports_l, b->starting_lamports );
2078 3384 : }
2079 3474 : } else if( NULL != b->const_meta ) {
2080 : // Should these just kill the client? They are impossible...
2081 2859 : if( b->starting_lamports != b->const_meta->info.lamports ) {
2082 0 : FD_LOG_DEBUG(("Const rec mismatch %s starting %lu %lu ending %lu %lu", FD_BASE58_ENC_32_ALLOCA( b->pubkey->uc ), b->starting_dlen, b->starting_lamports, b->const_meta->dlen, b->const_meta->info.lamports));
2083 0 : return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
2084 0 : }
2085 2859 : if( b->starting_dlen != b->const_meta->dlen ) {
2086 0 : FD_LOG_DEBUG(("Const rec mismatch %s starting %lu %lu ending %lu %lu", FD_BASE58_ENC_32_ALLOCA( b->pubkey->uc ), b->starting_dlen, b->starting_lamports, b->const_meta->dlen, b->const_meta->info.lamports));
2087 0 : return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
2088 0 : }
2089 2859 : }
2090 6339 : }
2091 :
2092 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/transaction_processor.rs#L839-L845 */
2093 1704 : if( FD_UNLIKELY( ending_lamports_l != starting_lamports_l || ending_lamports_h != starting_lamports_h ) ) {
2094 0 : FD_LOG_DEBUG(( "Lamport sum mismatch: starting %lx%lx ending %lx%lx", starting_lamports_h, starting_lamports_l, ending_lamports_h, ending_lamports_l ));
2095 0 : return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
2096 0 : }
2097 :
2098 1704 : return FD_EXECUTOR_INSTR_SUCCESS;
2099 1704 : }
2100 : #undef VLOG
2101 :
2102 : /* fd_executor_instr_strerror() returns the error message corresponding to err,
2103 : intended to be logged by log_collector, or an empty string if the error code
2104 : should be omitted in logs for whatever reason. Omitted examples are success,
2105 : fatal (placeholder just in firedancer), custom error.
2106 : See also fd_log_collector_program_failure(). */
2107 : FD_FN_CONST char const *
2108 47391 : fd_executor_instr_strerror( int err ) {
2109 :
2110 47391 : switch( err ) {
2111 0 : case FD_EXECUTOR_INSTR_SUCCESS : return ""; // not used
2112 0 : case FD_EXECUTOR_INSTR_ERR_FATAL : return ""; // not used
2113 0 : case FD_EXECUTOR_INSTR_ERR_GENERIC_ERR : return "generic instruction error";
2114 2790 : case FD_EXECUTOR_INSTR_ERR_INVALID_ARG : return "invalid program argument";
2115 9567 : case FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA : return "invalid instruction data";
2116 7293 : case FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA : return "invalid account data for instruction";
2117 21 : case FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL : return "account data too small for instruction";
2118 1236 : case FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS : return "insufficient funds for instruction";
2119 228 : case FD_EXECUTOR_INSTR_ERR_INCORRECT_PROGRAM_ID : return "incorrect program id for instruction";
2120 6741 : case FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE : return "missing required signature for instruction";
2121 6 : case FD_EXECUTOR_INSTR_ERR_ACC_ALREADY_INITIALIZED : return "instruction requires an uninitialized account";
2122 75 : case FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT : return "instruction requires an initialized account";
2123 0 : case FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR : return "sum of account balances before and after instruction do not match";
2124 318 : case FD_EXECUTOR_INSTR_ERR_MODIFIED_PROGRAM_ID : return "instruction illegally modified the program id of an account";
2125 45 : case FD_EXECUTOR_INSTR_ERR_EXTERNAL_ACCOUNT_LAMPORT_SPEND : return "instruction spent from the balance of an account it does not own";
2126 51 : case FD_EXECUTOR_INSTR_ERR_EXTERNAL_DATA_MODIFIED : return "instruction modified data of an account it does not own";
2127 192 : case FD_EXECUTOR_INSTR_ERR_READONLY_LAMPORT_CHANGE : return "instruction changed the balance of a read-only account";
2128 303 : case FD_EXECUTOR_INSTR_ERR_READONLY_DATA_MODIFIED : return "instruction modified data of a read-only account";
2129 0 : case FD_EXECUTOR_INSTR_ERR_DUPLICATE_ACCOUNT_IDX : return "instruction contains duplicate accounts";
2130 0 : case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_MODIFIED : return "instruction changed executable bit of an account";
2131 0 : case FD_EXECUTOR_INSTR_ERR_RENT_EPOCH_MODIFIED : return "instruction modified rent epoch of an account";
2132 993 : case FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS : return "insufficient account keys for instruction";
2133 0 : case FD_EXECUTOR_INSTR_ERR_ACC_DATA_SIZE_CHANGED : return "program other than the account's owner changed the size of the account data";
2134 0 : case FD_EXECUTOR_INSTR_ERR_ACC_NOT_EXECUTABLE : return "instruction expected an executable account";
2135 237 : case FD_EXECUTOR_INSTR_ERR_ACC_BORROW_FAILED : return "instruction tries to borrow reference for an account which is already borrowed";
2136 0 : case FD_EXECUTOR_INSTR_ERR_ACC_BORROW_OUTSTANDING : return "instruction left account with an outstanding borrowed reference";
2137 0 : case FD_EXECUTOR_INSTR_ERR_DUPLICATE_ACCOUNT_OUT_OF_SYNC : return "instruction modifications of multiply-passed account differ";
2138 0 : case FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR : return ""; // custom handling via txn_ctx->custom_err
2139 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_ERR : return "program returned invalid error code";
2140 303 : case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_DATA_MODIFIED : return "instruction changed executable accounts data";
2141 87 : case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_LAMPORT_CHANGE : return "instruction changed the balance of an executable account";
2142 0 : case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_ACCOUNT_NOT_RENT_EXEMPT : return "executable accounts must be rent exempt";
2143 165 : case FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID : return "Unsupported program id";
2144 0 : case FD_EXECUTOR_INSTR_ERR_CALL_DEPTH : return "Cross-program invocation call depth too deep";
2145 0 : case FD_EXECUTOR_INSTR_ERR_MISSING_ACC : return "An account required by the instruction is missing";
2146 12 : case FD_EXECUTOR_INSTR_ERR_REENTRANCY_NOT_ALLOWED : return "Cross-program invocation reentrancy not allowed for this instruction";
2147 0 : case FD_EXECUTOR_INSTR_ERR_MAX_SEED_LENGTH_EXCEEDED : return "Length of the seed is too long for address generation";
2148 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_SEEDS : return "Provided seeds do not result in a valid address";
2149 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC : return "Failed to reallocate account data";
2150 10299 : case FD_EXECUTOR_INSTR_ERR_COMPUTE_BUDGET_EXCEEDED : return "Computational budget exceeded";
2151 3 : case FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION : return "Cross-program invocation with unauthorized signer or writable account";
2152 0 : case FD_EXECUTOR_INSTR_ERR_PROGRAM_ENVIRONMENT_SETUP_FAILURE : return "Failed to create program execution environment";
2153 18 : case FD_EXECUTOR_INSTR_ERR_PROGRAM_FAILED_TO_COMPLETE : return "Program failed to complete";
2154 0 : case FD_EXECUTOR_INSTR_ERR_PROGRAM_FAILED_TO_COMPILE : return "Program failed to compile";
2155 3 : case FD_EXECUTOR_INSTR_ERR_ACC_IMMUTABLE : return "Account is immutable";
2156 6 : case FD_EXECUTOR_INSTR_ERR_INCORRECT_AUTHORITY : return "Incorrect authority provided";
2157 0 : case FD_EXECUTOR_INSTR_ERR_BORSH_IO_ERROR : return "Failed to serialize or deserialize account data"; // truncated
2158 0 : case FD_EXECUTOR_INSTR_ERR_ACC_NOT_RENT_EXEMPT : return "An account does not have enough lamports to be rent-exempt";
2159 6150 : case FD_EXECUTOR_INSTR_ERR_INVALID_ACC_OWNER : return "Invalid account owner";
2160 84 : case FD_EXECUTOR_INSTR_ERR_ARITHMETIC_OVERFLOW : return "Program arithmetic overflowed";
2161 165 : case FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR : return "Unsupported sysvar";
2162 0 : case FD_EXECUTOR_INSTR_ERR_ILLEGAL_OWNER : return "Provided owner is not allowed";
2163 0 : case FD_EXECUTOR_INSTR_ERR_MAX_ACCS_DATA_ALLOCS_EXCEEDED : return "Accounts data allocations exceeded the maximum allowed per transaction";
2164 0 : case FD_EXECUTOR_INSTR_ERR_MAX_ACCS_EXCEEDED : return "Max accounts exceeded";
2165 0 : case FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED : return "Max instruction trace length exceeded";
2166 0 : case FD_EXECUTOR_INSTR_ERR_BUILTINS_MUST_CONSUME_CUS : return "Builtin programs must consume compute units";
2167 0 : default: break;
2168 47391 : }
2169 :
2170 0 : return "";
2171 47391 : }
2172 :
2173 : // This is purely linker magic to force the inclusion of the yaml type walker so that it is
2174 : // available for debuggers
2175 : void
2176 0 : fd_debug_symbology(void) {
2177 0 : (void)fd_get_types_yaml();
2178 0 : }
|