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