Line data Source code
1 : #include "fd_executor.h"
2 : #include "fd_acc_mgr.h"
3 : #include "fd_bank.h"
4 : #include "fd_hashes.h"
5 : #include "fd_runtime.h"
6 : #include "fd_runtime_err.h"
7 :
8 : #include "context/fd_exec_txn_ctx.h"
9 :
10 : #include "fd_system_ids.h"
11 : #include "program/fd_address_lookup_table_program.h"
12 : #include "program/fd_bpf_loader_program.h"
13 : #include "program/fd_loader_v4_program.h"
14 : #include "program/fd_compute_budget_program.h"
15 : #include "program/fd_config_program.h"
16 : #include "program/fd_precompiles.h"
17 : #include "program/fd_stake_program.h"
18 : #include "program/fd_system_program.h"
19 : #include "program/fd_builtin_programs.h"
20 : #include "program/fd_vote_program.h"
21 : #include "program/fd_zk_elgamal_proof_program.h"
22 : #include "sysvar/fd_sysvar_cache.h"
23 : #include "program/fd_program_cache.h"
24 : #include "sysvar/fd_sysvar_epoch_schedule.h"
25 : #include "sysvar/fd_sysvar_instructions.h"
26 : #include "sysvar/fd_sysvar_rent.h"
27 : #include "sysvar/fd_sysvar_slot_history.h"
28 :
29 : #include "tests/fd_dump_pb.h"
30 :
31 : #include "../../ballet/base58/fd_base58.h"
32 : #include "../../disco/pack/fd_pack.h"
33 : #include "../../disco/pack/fd_pack_cost.h"
34 :
35 : #include "../../util/bits/fd_uwide.h"
36 :
37 : #include <assert.h>
38 : #include <math.h>
39 : #include <stdio.h> /* snprintf(3) */
40 : #include <fcntl.h> /* openat(2) */
41 : #include <unistd.h> /* write(3) */
42 : #include <time.h>
43 :
44 : struct fd_native_prog_info {
45 : fd_pubkey_t key;
46 : fd_exec_instr_fn_t fn;
47 : uchar is_bpf_loader;
48 : };
49 : typedef struct fd_native_prog_info fd_native_prog_info_t;
50 :
51 : #define MAP_PERFECT_NAME fd_native_program_fn_lookup_tbl
52 : #define MAP_PERFECT_LG_TBL_SZ 4
53 : #define MAP_PERFECT_T fd_native_prog_info_t
54 45 : #define MAP_PERFECT_HASH_C 478U
55 : #define MAP_PERFECT_KEY key.uc
56 : #define MAP_PERFECT_KEY_T fd_pubkey_t const *
57 : #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)
58 : #define MAP_PERFECT_COMPLEX_KEY 1
59 45 : #define MAP_PERFECT_KEYS_EQUAL(k1,k2) (!memcmp( (k1), (k2), 32UL ))
60 :
61 45 : #define PERFECT_HASH( u ) (((MAP_PERFECT_HASH_C*(u))>>28)&0xFU)
62 :
63 : #define MAP_PERFECT_HASH_PP( a00,a01,a02,a03,a04,a05,a06,a07,a08,a09,a10,a11,a12,a13,a14,a15, \
64 : a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31) \
65 : PERFECT_HASH( (a08 | (a09<<8) | (a10<<16) | (a11<<24)) )
66 45 : #define MAP_PERFECT_HASH_R( ptr ) PERFECT_HASH( fd_uint_load_4( (uchar const *)ptr + 8UL ) )
67 :
68 : #define MAP_PERFECT_0 ( VOTE_PROG_ID ), .fn = fd_vote_program_execute, .is_bpf_loader = 0
69 : #define MAP_PERFECT_1 ( SYS_PROG_ID ), .fn = fd_system_program_execute, .is_bpf_loader = 0
70 : #define MAP_PERFECT_2 ( CONFIG_PROG_ID ), .fn = fd_config_program_execute, .is_bpf_loader = 0
71 : #define MAP_PERFECT_3 ( STAKE_PROG_ID ), .fn = fd_stake_program_execute, .is_bpf_loader = 0
72 : #define MAP_PERFECT_4 ( COMPUTE_BUDGET_PROG_ID ), .fn = fd_compute_budget_program_execute, .is_bpf_loader = 0
73 : #define MAP_PERFECT_5 ( ADDR_LUT_PROG_ID ), .fn = fd_address_lookup_table_program_execute, .is_bpf_loader = 0
74 : #define MAP_PERFECT_6 ( ZK_EL_GAMAL_PROG_ID ), .fn = fd_executor_zk_elgamal_proof_program_execute, .is_bpf_loader = 0
75 : #define MAP_PERFECT_7 ( BPF_LOADER_1_PROG_ID ), .fn = fd_bpf_loader_program_execute, .is_bpf_loader = 1
76 : #define MAP_PERFECT_8 ( BPF_LOADER_2_PROG_ID ), .fn = fd_bpf_loader_program_execute, .is_bpf_loader = 1
77 : #define MAP_PERFECT_9 ( BPF_UPGRADEABLE_PROG_ID ), .fn = fd_bpf_loader_program_execute, .is_bpf_loader = 1
78 : #define MAP_PERFECT_10 ( LOADER_V4_PROG_ID ), .fn = fd_loader_v4_program_execute, .is_bpf_loader = 1
79 :
80 : #include "../../util/tmpl/fd_map_perfect.c"
81 : #undef PERFECT_HASH
82 :
83 : #define MAP_PERFECT_NAME fd_native_precompile_program_fn_lookup_tbl
84 : #define MAP_PERFECT_LG_TBL_SZ 2
85 : #define MAP_PERFECT_T fd_native_prog_info_t
86 0 : #define MAP_PERFECT_HASH_C 63546U
87 : #define MAP_PERFECT_KEY key.uc
88 : #define MAP_PERFECT_KEY_T fd_pubkey_t const *
89 : #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)
90 : #define MAP_PERFECT_COMPLEX_KEY 1
91 0 : #define MAP_PERFECT_KEYS_EQUAL(k1,k2) (!memcmp( (k1), (k2), 32UL ))
92 :
93 0 : #define PERFECT_HASH( u ) (((MAP_PERFECT_HASH_C*(u))>>30)&0x3U)
94 :
95 : #define MAP_PERFECT_HASH_PP( a00,a01,a02,a03,a04,a05,a06,a07,a08,a09,a10,a11,a12,a13,a14,a15, \
96 : a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31) \
97 : PERFECT_HASH( (a00 | (a01<<8)) )
98 0 : #define MAP_PERFECT_HASH_R( ptr ) PERFECT_HASH( fd_uint_load_2( (uchar const *)ptr ) )
99 :
100 : #define MAP_PERFECT_0 ( ED25519_SV_PROG_ID ), .fn = fd_precompile_ed25519_verify
101 : #define MAP_PERFECT_1 ( KECCAK_SECP_PROG_ID ), .fn = fd_precompile_secp256k1_verify
102 : #define MAP_PERFECT_2 ( SECP256R1_PROG_ID ), .fn = fd_precompile_secp256r1_verify
103 :
104 : #include "../../util/tmpl/fd_map_perfect.c"
105 : #undef PERFECT_HASH
106 :
107 : fd_exec_instr_fn_t
108 0 : fd_executor_lookup_native_precompile_program( fd_txn_account_t const * prog_acc ) {
109 0 : fd_pubkey_t const * pubkey = prog_acc->pubkey;
110 0 : const fd_native_prog_info_t null_function = {0};
111 0 : return fd_native_precompile_program_fn_lookup_tbl_query( pubkey, &null_function )->fn;
112 0 : }
113 :
114 : uchar
115 45 : fd_executor_pubkey_is_bpf_loader( fd_pubkey_t const * pubkey ) {
116 45 : fd_native_prog_info_t const null_function = {0};
117 45 : return fd_native_program_fn_lookup_tbl_query( pubkey, &null_function )->is_bpf_loader;
118 45 : }
119 :
120 : /* fd_executor_lookup_native_program returns the appropriate instruction processor for the given
121 : native program ID. Returns NULL if given ID is not a recognized native program.
122 : https://github.com/anza-xyz/agave/blob/v2.2.6/program-runtime/src/invoke_context.rs#L520-L544 */
123 : static int
124 : fd_executor_lookup_native_program( fd_txn_account_t const * prog_acc,
125 : fd_exec_txn_ctx_t * txn_ctx,
126 : fd_exec_instr_fn_t * native_prog_fn,
127 0 : uchar * is_precompile ) {
128 : /* First lookup to see if the program key is a precompile */
129 0 : *is_precompile = 0;
130 0 : *native_prog_fn = fd_executor_lookup_native_precompile_program( prog_acc );
131 0 : if( FD_UNLIKELY( *native_prog_fn!=NULL ) ) {
132 0 : *is_precompile = 1;
133 0 : return 0;
134 0 : }
135 :
136 0 : fd_pubkey_t const * pubkey = prog_acc->pubkey;
137 0 : fd_pubkey_t const * owner = fd_txn_account_get_owner( prog_acc );
138 :
139 : /* Native programs should be owned by the native loader...
140 : This will not be the case though once core programs are migrated to BPF. */
141 0 : int is_native_program = !memcmp( owner, fd_solana_native_loader_id.key, sizeof(fd_pubkey_t) );
142 :
143 0 : if( !is_native_program && FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, remove_accounts_executable_flag_checks ) ) {
144 0 : if( FD_UNLIKELY( !fd_executor_pubkey_is_bpf_loader( owner ) ) ) {
145 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
146 0 : }
147 0 : }
148 :
149 0 : fd_pubkey_t const * lookup_pubkey = is_native_program ? pubkey : owner;
150 :
151 : /* Migrated programs must be executed via the corresponding BPF
152 : loader(s), not natively. This check is performed at the transaction
153 : level, but we re-check to please the instruction level (and below)
154 : fuzzers. */
155 0 : uchar has_migrated;
156 0 : if( FD_UNLIKELY( fd_is_migrating_builtin_program( txn_ctx, lookup_pubkey, &has_migrated ) && has_migrated ) ) {
157 0 : *native_prog_fn = NULL;
158 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
159 0 : }
160 :
161 0 : fd_native_prog_info_t const null_function = {0};
162 0 : *native_prog_fn = fd_native_program_fn_lookup_tbl_query( lookup_pubkey, &null_function )->fn;
163 0 : return 0;
164 0 : }
165 :
166 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L117-L136 */
167 : static uchar
168 : fd_executor_rent_transition_allowed( fd_rent_state_t const * pre_rent_state,
169 0 : fd_rent_state_t const * post_rent_state ) {
170 0 : switch( post_rent_state->discriminant ) {
171 0 : case fd_rent_state_enum_uninitialized:
172 0 : case fd_rent_state_enum_rent_exempt: {
173 0 : return 1;
174 0 : }
175 0 : case fd_rent_state_enum_rent_paying: {
176 0 : switch( pre_rent_state->discriminant ) {
177 0 : case fd_rent_state_enum_uninitialized:
178 0 : case fd_rent_state_enum_rent_exempt: {
179 0 : return 0;
180 0 : }
181 0 : case fd_rent_state_enum_rent_paying: {
182 0 : return post_rent_state->inner.rent_paying.data_size==pre_rent_state->inner.rent_paying.data_size &&
183 0 : post_rent_state->inner.rent_paying.lamports<=pre_rent_state->inner.rent_paying.lamports;
184 0 : }
185 0 : default: {
186 0 : __builtin_unreachable();
187 0 : }
188 0 : }
189 0 : }
190 0 : default: {
191 0 : __builtin_unreachable();
192 0 : }
193 0 : }
194 0 : }
195 :
196 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L61-L77 */
197 : static int
198 : fd_executor_check_rent_state_with_account( fd_txn_account_t const * account,
199 : fd_rent_state_t const * pre_rent_state,
200 0 : fd_rent_state_t const * post_rent_state ) {
201 0 : if( FD_UNLIKELY( memcmp( account->pubkey->key, fd_sysvar_incinerator_id.key, sizeof(fd_pubkey_t) ) &&
202 0 : !fd_executor_rent_transition_allowed( pre_rent_state, post_rent_state ) ) ) {
203 0 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
204 0 : }
205 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
206 0 : }
207 :
208 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L87-L101 */
209 : fd_rent_state_t
210 0 : fd_executor_get_account_rent_state( fd_txn_account_t const * account, fd_rent_t const * rent ) {
211 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L88-L89 */
212 0 : if( fd_txn_account_get_lamports( account )==0UL ) {
213 0 : return (fd_rent_state_t){
214 0 : .discriminant = fd_rent_state_enum_uninitialized
215 0 : };
216 0 : }
217 :
218 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L90-L94 */
219 0 : if( fd_txn_account_get_lamports( account )>=fd_rent_exempt_minimum_balance( rent, fd_txn_account_get_data_len( account ) ) ) {
220 0 : return (fd_rent_state_t){
221 0 : .discriminant = fd_rent_state_enum_rent_exempt
222 0 : };
223 0 : }
224 :
225 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L95-L99 */
226 0 : return (fd_rent_state_t){
227 0 : .discriminant = fd_rent_state_enum_rent_paying,
228 0 : .inner = {
229 0 : .rent_paying = {
230 0 : .lamports = fd_txn_account_get_lamports( account ),
231 0 : .data_size = fd_txn_account_get_data_len( account )
232 0 : }
233 0 : }
234 0 : };
235 0 : }
236 :
237 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L293-L342 */
238 : static int
239 : fd_validate_fee_payer( fd_txn_account_t * account,
240 : fd_rent_t const * rent,
241 : ulong fee,
242 0 : fd_spad_t * exec_spad ) {
243 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L301-L304 */
244 0 : if( FD_UNLIKELY( fd_txn_account_get_lamports( account )==0UL ) ) {
245 0 : return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
246 0 : }
247 :
248 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L305-L308 */
249 0 : int system_account_kind = fd_get_system_account_kind( account, exec_spad );
250 0 : if( FD_UNLIKELY( system_account_kind==FD_SYSTEM_PROGRAM_NONCE_ACCOUNT_KIND_UNKNOWN ) ) {
251 0 : return FD_RUNTIME_TXN_ERR_INVALID_ACCOUNT_FOR_FEE;
252 0 : }
253 :
254 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L309-L318 */
255 0 : ulong min_balance = 0UL;
256 0 : if( system_account_kind==FD_SYSTEM_PROGRAM_NONCE_ACCOUNT_KIND_NONCE ) {
257 0 : min_balance = fd_rent_exempt_minimum_balance( rent, FD_SYSTEM_PROGRAM_NONCE_DLEN );
258 0 : }
259 :
260 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L320-L327 */
261 0 : if( FD_UNLIKELY( min_balance>fd_txn_account_get_lamports( account ) ||
262 0 : fee>fd_txn_account_get_lamports( account )-min_balance ) ) {
263 0 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_FEE;
264 0 : }
265 :
266 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L329 */
267 0 : fd_rent_state_t payer_pre_rent_state = fd_executor_get_account_rent_state( account, rent );
268 :
269 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L330-L332 */
270 0 : int err = fd_txn_account_checked_sub_lamports( account, fee );
271 0 : if( FD_UNLIKELY( err!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
272 0 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_FEE;
273 0 : }
274 :
275 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L334 */
276 0 : fd_rent_state_t payer_post_rent_state = fd_executor_get_account_rent_state( account, rent );
277 :
278 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L335-L342 */
279 0 : return fd_executor_check_rent_state_with_account( account, &payer_pre_rent_state, &payer_post_rent_state );
280 0 : }
281 :
282 : static int
283 0 : fd_executor_check_status_cache( fd_exec_txn_ctx_t * txn_ctx ) {
284 :
285 0 : if( FD_UNLIKELY( !txn_ctx->status_cache ) ) {
286 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
287 0 : }
288 :
289 0 : fd_hash_t * blockhash = (fd_hash_t *)((uchar *)txn_ctx->txn.payload + TXN( &txn_ctx->txn )->recent_blockhash_off);
290 :
291 0 : fd_txncache_query_t curr_query;
292 0 : curr_query.blockhash = blockhash->uc;
293 0 : fd_blake3_t b3[1];
294 :
295 : /* Compute the blake3 hash of the transaction message
296 : https://github.com/anza-xyz/agave/blob/v2.1.7/sdk/program/src/message/versions/mod.rs#L159-L167 */
297 0 : fd_blake3_init( b3 );
298 0 : fd_blake3_append( b3, "solana-tx-message-v1", 20UL );
299 0 : fd_blake3_append( b3, ((uchar *)txn_ctx->txn.payload + TXN( &txn_ctx->txn )->message_off),(ulong)( txn_ctx->txn.payload_sz - TXN( &txn_ctx->txn )->message_off ) );
300 0 : fd_blake3_fini( b3, &txn_ctx->blake_txn_msg_hash );
301 0 : curr_query.txnhash = txn_ctx->blake_txn_msg_hash.uc;
302 :
303 : // FIXME: Commenting out until txncache is fixed
304 0 : (void)curr_query;
305 : // int err;
306 : // fd_txncache_query_batch( txn_ctx->status_cache,
307 : // &curr_query,
308 : // 1UL,
309 : // (void *)txn_ctx,
310 : // status_check_tower, &err );
311 0 : return 0;
312 0 : }
313 :
314 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/check_transactions.rs#L77-L141 */
315 : static int
316 0 : fd_executor_check_transaction_age_and_compute_budget_limits( fd_exec_txn_ctx_t * txn_ctx ) {
317 : /* Note that in Agave, although this function is called after the
318 : compute budget limits are sanitized, if the transaction age checks
319 : fail, then we return the transaction age error instead of the
320 : compute budget error.
321 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/check_transactions.rs#L128-L136 */
322 0 : int err = fd_check_transaction_age( txn_ctx );
323 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
324 0 : return err;
325 0 : }
326 :
327 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/check_transactions.rs#L103 */
328 0 : err = fd_sanitize_compute_unit_limits( txn_ctx );
329 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
330 0 : return err;
331 0 : }
332 :
333 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
334 0 : }
335 :
336 : /* https://github.com/anza-xyz/agave/blob/v2.0.9/runtime/src/bank.rs#L3239-L3251 */
337 : static inline ulong
338 0 : get_transaction_account_lock_limit( fd_exec_txn_ctx_t const * txn_ctx ) {
339 0 : return fd_ulong_if( FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, increase_tx_account_lock_limit ), MAX_TX_ACCOUNT_LOCKS, 64UL );
340 0 : }
341 :
342 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/check_transactions.rs#L61-L75 */
343 : int
344 0 : fd_executor_check_transactions( fd_exec_txn_ctx_t * txn_ctx ) {
345 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/check_transactions.rs#L68-L73 */
346 0 : int err = fd_executor_check_transaction_age_and_compute_budget_limits( txn_ctx );
347 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
348 0 : return err;
349 0 : }
350 :
351 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/check_transactions.rs#L74 */
352 0 : err = fd_executor_check_status_cache( txn_ctx );
353 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
354 0 : return err;
355 0 : }
356 :
357 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
358 0 : }
359 :
360 : /* `verify_transaction()` is the first function called in the
361 : transaction execution pipeline. It is responsible for deserializing
362 : the transaction, verifying the message hash (sigverify), verifying
363 : the precompiles, and processing compute budget instructions. We
364 : leave sigverify out for now to easily bypass this function's
365 : checks for fuzzing.
366 :
367 : TODO: Maybe support adding sigverify in here, and toggling it
368 : on/off with a flag.
369 :
370 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank.rs#L5725-L5753 */
371 : int
372 0 : fd_executor_verify_transaction( fd_exec_txn_ctx_t * txn_ctx ) {
373 0 : int err = FD_RUNTIME_EXECUTE_SUCCESS;
374 :
375 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L566-L569 */
376 0 : err = fd_executor_compute_budget_program_execute_instructions( txn_ctx );
377 0 : if( FD_UNLIKELY( err ) ) return err;
378 :
379 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
380 0 : }
381 :
382 : static void
383 0 : fd_executor_setup_instr_infos_from_txn_instrs( fd_exec_txn_ctx_t * txn_ctx ) {
384 0 : ushort instr_cnt = TXN( &txn_ctx->txn )->instr_cnt;
385 :
386 : /* Set up the instr infos for the transaction */
387 0 : for( ushort i=0; i<instr_cnt; i++ ) {
388 0 : fd_txn_instr_t const * instr = &TXN( &txn_ctx->txn )->instr[i];
389 0 : fd_instr_info_init_from_txn_instr( &txn_ctx->instr_infos[i], txn_ctx, instr );
390 0 : }
391 :
392 0 : txn_ctx->instr_info_cnt = instr_cnt;
393 0 : }
394 :
395 : /* https://github.com/anza-xyz/agave/blob/v2.0.9/svm/src/account_loader.rs#L410-427 */
396 : static int
397 : accumulate_and_check_loaded_account_data_size( ulong acc_size,
398 : ulong requested_loaded_accounts_data_size,
399 0 : ulong * accumulated_account_size ) {
400 0 : *accumulated_account_size = fd_ulong_sat_add( *accumulated_account_size, acc_size );
401 0 : if( FD_UNLIKELY( *accumulated_account_size>requested_loaded_accounts_data_size ) ) {
402 0 : return FD_RUNTIME_TXN_ERR_MAX_LOADED_ACCOUNTS_DATA_SIZE_EXCEEDED;
403 0 : }
404 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
405 0 : }
406 :
407 : /* This function contains special casing for loading and collecting rent from
408 : each transaction account. The logic is as follows:
409 : 1. If the account is the instructions sysvar, then load in the compiled
410 : instructions from the transactions into the sysvar's data.
411 : 2. If the account is a fee payer, then it is already loaded.
412 : 3. Otherwise load in the account from the accounts DB. If the account is
413 : writable and exists, try to collect rent from it.
414 :
415 : Returns the loaded transaction account size, which is the value that
416 : must be used when accumulating and checking against the
417 : transactions's loaded account data size limit.
418 :
419 : Agave relies on this function to actually load accounts from their
420 : accounts db. However, since our accounts model is slightly different,
421 : our account loading logic is handled earlier in the transaction
422 : execution pipeline within `fd_executor_setup_accounts_for_txn()`.
423 : Therefore, the name of this function is slightly misleading - we
424 : don't actually load accounts here, but we still need to collect
425 : rent from writable accounts and accumulate the transaction's
426 : total loaded account size.
427 :
428 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L199-L228 */
429 : static ulong
430 : load_transaction_account( fd_exec_txn_ctx_t * txn_ctx,
431 : fd_txn_account_t * acct,
432 : uchar is_writable,
433 : ulong epoch,
434 0 : uchar unknown_acc ) {
435 :
436 : /* Handling the sysvar instructions account explictly.
437 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L817-L824 */
438 0 : if( FD_UNLIKELY( !memcmp( acct->pubkey->key, fd_sysvar_instructions_id.key, sizeof(fd_pubkey_t) ) ) ) {
439 : /* The sysvar instructions account cannot be "loaded" since it's
440 : constructed by the SVM and modified within each transaction's
441 : instruction execution only, so it incurs a loaded size cost
442 : of 0. */
443 0 : fd_sysvar_instructions_serialize_account( txn_ctx, (fd_instr_info_t const *)txn_ctx->instr_infos, TXN( &txn_ctx->txn )->instr_cnt );
444 0 : return 0UL;
445 0 : }
446 :
447 : /* This next block calls `account_loader::load_transaction_account()`
448 : which loads the account from the accounts db. If the account exists
449 : and is writable, collect rent from it.
450 :
451 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L828-L835 */
452 0 : if( FD_LIKELY( !unknown_acc ) ) {
453 : /* SIMD-0186 introduces a base account size of 64 bytes for all
454 : transaction counts that exist prior to the transaction's
455 : execution.
456 :
457 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L204-L208 */
458 0 : ulong base_account_size = FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, formalize_loaded_transaction_data_size ) ? FD_TRANSACTION_ACCOUNT_BASE_SIZE : 0UL;
459 :
460 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L828-L835 */
461 0 : if( is_writable ) {
462 0 : fd_epoch_schedule_t const epoch_schedule = fd_bank_epoch_schedule_get( txn_ctx->bank );
463 0 : fd_rent_t const rent = fd_bank_rent_get( txn_ctx->bank );
464 0 : txn_ctx->collected_rent += fd_runtime_collect_rent_from_account(
465 0 : &epoch_schedule, &rent,
466 0 : fd_bank_slots_per_year_get( txn_ctx->bank ),
467 0 : acct, epoch );
468 0 : acct->starting_lamports = fd_txn_account_get_lamports( acct ); /* TODO: why do we do this everywhere? */
469 0 : }
470 0 : return fd_ulong_sat_add( base_account_size, fd_txn_account_get_data_len( acct ) );
471 0 : }
472 :
473 : /* The rest of this function is a no-op for us since we already set up
474 : the transaction accounts for unknown accounts within
475 : `fd_executor_setup_accounts_for_txn()`. We also do not need to
476 : add a base cost to the loaded account size because the SIMD
477 : states that accounts that do not exist prior to the transaction's
478 : execution should not incur a loaded size cost.
479 : https://github.com/anza-xyz/agave/blob/v2.2.0/svm/src/account_loader.rs#L566-L577 */
480 0 : return 0UL;
481 0 : }
482 :
483 : /* This big function contains a lot of logic and special casing for loading transaction accounts.
484 : Because of the `enable_transaction_loading_failure_fees` feature, it is imperative that we
485 : are conformant with Agave's logic here and reject / accept transactions here where they do.
486 :
487 : In the firedancer client only some of these steps are necessary because
488 : all of the accounts are loaded in from the accounts db into borrowed
489 : accounts already.
490 :
491 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L691-L807 */
492 : static int
493 0 : fd_executor_load_transaction_accounts_old( fd_exec_txn_ctx_t * txn_ctx ) {
494 0 : ulong requested_loaded_accounts_data_size = txn_ctx->compute_budget_details.loaded_accounts_data_size_limit;
495 :
496 0 : ulong const epoch = fd_bank_epoch_get( txn_ctx->bank );
497 :
498 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/svm/src/account_loader.rs#L429-L443 */
499 0 : for( ushort i=0; i<txn_ctx->accounts_cnt; i++ ) {
500 0 : fd_txn_account_t * acct = &txn_ctx->accounts[i];
501 0 : uchar unknown_acc = !!(fd_exec_txn_ctx_get_account_at_index( txn_ctx, i, &acct, fd_txn_account_check_exists ) ||
502 0 : fd_txn_account_get_lamports( acct )==0UL);
503 0 : uchar is_writable = !!(fd_exec_txn_ctx_account_is_writable_idx( txn_ctx, i ));
504 :
505 : /* Collect the fee payer account separately (since it was already)
506 : loaded during fee payer validation.
507 :
508 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L727-L729 */
509 0 : if( FD_UNLIKELY( i==FD_FEE_PAYER_TXN_IDX ) ) {
510 : /* Note that the dlen for most fee payers is 0, but we want to
511 : consider the case where the fee payer is a nonce account.
512 : We also don't need to add a base account size to this value
513 : because this branch would only be taken BEFORE SIMD-0186
514 : is enabled. */
515 0 : int err = accumulate_and_check_loaded_account_data_size( fd_txn_account_get_data_len( acct ),
516 0 : requested_loaded_accounts_data_size,
517 0 : &txn_ctx->loaded_accounts_data_size );
518 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
519 0 : return err;
520 0 : }
521 0 : continue;
522 0 : }
523 :
524 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L733-L740 */
525 0 : ulong loaded_acc_size = load_transaction_account( txn_ctx, acct, is_writable, epoch, unknown_acc );
526 0 : int err = accumulate_and_check_loaded_account_data_size( loaded_acc_size,
527 0 : requested_loaded_accounts_data_size,
528 0 : &txn_ctx->loaded_accounts_data_size );
529 :
530 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
531 0 : return err;
532 0 : }
533 0 : }
534 :
535 : /* TODO: Consider using a hash set (if its more performant) */
536 0 : ushort instr_cnt = TXN( &txn_ctx->txn )->instr_cnt;
537 0 : fd_pubkey_t validated_loaders[instr_cnt];
538 0 : ushort validated_loaders_cnt = 0;
539 :
540 : /* The logic below handles special casing with loading instruction accounts.
541 : https://github.com/anza-xyz/agave/blob/v2.2.0/svm/src/account_loader.rs#L445-L525 */
542 0 : for( ushort i=0; i<instr_cnt; i++ ) {
543 0 : fd_txn_instr_t const * instr = &TXN( &txn_ctx->txn )->instr[i];
544 :
545 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/svm/src/account_loader.rs#L449-L451 */
546 0 : if( FD_UNLIKELY( !memcmp( txn_ctx->account_keys[ instr->program_id ].key, fd_solana_native_loader_id.key, sizeof(fd_pubkey_t) ) ) ) {
547 0 : continue;
548 0 : }
549 :
550 : /* Mimicking `load_account()` here with 0-lamport check as well.
551 : https://github.com/anza-xyz/agave/blob/v2.2.0/svm/src/account_loader.rs#L455-L462 */
552 0 : fd_txn_account_t * program_account = NULL;
553 0 : int err = fd_exec_txn_ctx_get_account_at_index( txn_ctx,
554 0 : instr->program_id,
555 0 : &program_account,
556 0 : fd_txn_account_check_exists );
557 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS || fd_txn_account_get_lamports( program_account )==0UL ) ) {
558 0 : return FD_RUNTIME_TXN_ERR_PROGRAM_ACCOUNT_NOT_FOUND;
559 0 : }
560 :
561 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/svm/src/account_loader.rs#L464-L471 */
562 0 : if( FD_UNLIKELY( !FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, remove_accounts_executable_flag_checks ) &&
563 0 : !fd_txn_account_is_executable( program_account ) ) ) {
564 0 : return FD_RUNTIME_TXN_ERR_INVALID_PROGRAM_FOR_EXECUTION;
565 0 : }
566 :
567 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/svm/src/account_loader.rs#L474-L477 */
568 0 : if( !memcmp( fd_txn_account_get_owner( program_account ), fd_solana_native_loader_id.key, sizeof(fd_pubkey_t) ) ) {
569 0 : continue;
570 0 : }
571 :
572 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/svm/src/account_loader.rs#L479-L522 */
573 0 : uchar loader_seen = 0;
574 0 : for( ushort j=0; j<validated_loaders_cnt; j++ ) {
575 0 : if( !memcmp( validated_loaders[j].key, fd_txn_account_get_owner( program_account ), sizeof(fd_pubkey_t) ) ) {
576 : /* If the owner account has already been seen, skip the owner checks
577 : and do not acccumulate the account size. */
578 0 : loader_seen = 1;
579 0 : break;
580 0 : }
581 0 : }
582 0 : if( loader_seen ) continue;
583 :
584 : /* The agave client does checks on the program account's owners as well.
585 : However, it is important to not do these checks multiple times as the
586 : total size of accounts and their owners are accumulated: duplicate owners
587 : should be avoided.
588 : https://github.com/anza-xyz/agave/blob/v2.2.0/svm/src/account_loader.rs#L496-L517 */
589 0 : FD_TXN_ACCOUNT_DECL( owner_account );
590 0 : err = fd_txn_account_init_from_funk_readonly( owner_account,
591 0 : fd_txn_account_get_owner( program_account ),
592 0 : txn_ctx->funk,
593 0 : txn_ctx->funk_txn );
594 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
595 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/svm/src/account_loader.rs#L520 */
596 0 : return FD_RUNTIME_TXN_ERR_PROGRAM_ACCOUNT_NOT_FOUND;
597 0 : }
598 :
599 :
600 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/svm/src/account_loader.rs#L502-L510 */
601 0 : if( FD_UNLIKELY( memcmp( fd_txn_account_get_owner( owner_account ), fd_solana_native_loader_id.key, sizeof(fd_pubkey_t) ) ||
602 0 : ( !FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, remove_accounts_executable_flag_checks ) &&
603 0 : !fd_txn_account_is_executable( owner_account ) ) ) ) {
604 0 : return FD_RUNTIME_TXN_ERR_INVALID_PROGRAM_FOR_EXECUTION;
605 0 : }
606 :
607 : /* Count the owner's data in the loaded account size for program accounts.
608 : However, it is important to not double count repeated owners.
609 : https://github.com/anza-xyz/agave/blob/v2.2.0/svm/src/account_loader.rs#L511-L517 */
610 0 : err = accumulate_and_check_loaded_account_data_size( fd_txn_account_get_data_len( owner_account ),
611 0 : requested_loaded_accounts_data_size,
612 0 : &txn_ctx->loaded_accounts_data_size );
613 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
614 0 : return err;
615 0 : }
616 :
617 0 : fd_memcpy( validated_loaders[ validated_loaders_cnt++ ].key, owner_account->pubkey, sizeof(fd_pubkey_t) );
618 0 : }
619 :
620 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
621 0 : }
622 :
623 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L494-L515 */
624 : static int
625 : fd_increase_calculated_data_size( fd_exec_txn_ctx_t * txn_ctx,
626 0 : ulong data_size_delta ) {
627 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L500-L503 */
628 0 : if( FD_UNLIKELY( data_size_delta>UINT_MAX ) ) {
629 0 : return FD_RUNTIME_TXN_ERR_MAX_LOADED_ACCOUNTS_DATA_SIZE_EXCEEDED;
630 0 : }
631 :
632 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L505-L507 */
633 0 : txn_ctx->loaded_accounts_data_size = fd_ulong_sat_add( txn_ctx->loaded_accounts_data_size, data_size_delta );
634 :
635 0 : if( FD_UNLIKELY( txn_ctx->loaded_accounts_data_size>txn_ctx->compute_budget_details.loaded_accounts_data_size_limit ) ) {
636 0 : return FD_RUNTIME_TXN_ERR_MAX_LOADED_ACCOUNTS_DATA_SIZE_EXCEEDED;
637 0 : }
638 :
639 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
640 0 : }
641 :
642 : /* This function is represented as a closure in Agave.
643 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L578-L640 */
644 : static int
645 : fd_collect_loaded_account( fd_exec_txn_ctx_t * txn_ctx,
646 : fd_txn_account_t const * account,
647 : ulong loaded_acc_size,
648 : fd_pubkey_t * additional_loaded_account_keys,
649 0 : ulong * additional_loaded_account_keys_cnt ) {
650 :
651 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L586-L590 */
652 0 : int err = fd_increase_calculated_data_size( txn_ctx, loaded_acc_size );
653 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
654 0 : return err;
655 0 : }
656 :
657 : /* The remainder of this function is a deep-nested set of if
658 : statements. I've inverted the logic to make it easier to read.
659 : The purpose of the following code is to ensure that loader v3
660 : programdata accounts are accounted for exactly once in the account
661 : loading logic.
662 :
663 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L611 */
664 0 : if( FD_LIKELY( memcmp( fd_txn_account_get_owner( account ), fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) ) ) {
665 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
666 0 : }
667 :
668 : /* Try to read the program state
669 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L612-L634 */
670 0 : fd_bpf_upgradeable_loader_state_t * loader_state = fd_bpf_loader_program_get_state( account, txn_ctx->spad, NULL );
671 0 : if( FD_UNLIKELY( !loader_state ) ) {
672 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
673 0 : }
674 :
675 : /* Make sure the account is a v3 program */
676 0 : if( !fd_bpf_upgradeable_loader_state_is_program( loader_state ) ) {
677 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
678 0 : }
679 :
680 : /* Iterate through the account keys and make sure the programdata
681 : account is not present so it doesn't get loaded twice.
682 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L617 */
683 0 : for( ushort i=0; i<txn_ctx->accounts_cnt; i++ ) {
684 0 : if( FD_UNLIKELY( !memcmp( &txn_ctx->account_keys[i], &loader_state->inner.program.programdata_address, sizeof(fd_pubkey_t) ) ) ) {
685 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
686 0 : }
687 0 : }
688 :
689 : /* Check that the programdata account has not been already counted
690 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L618 */
691 0 : for( ushort i=0; i<*additional_loaded_account_keys_cnt; i++ ) {
692 0 : if( FD_UNLIKELY( !memcmp( &additional_loaded_account_keys[i], &loader_state->inner.program.programdata_address, sizeof(fd_pubkey_t) ) ) ) {
693 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
694 0 : }
695 0 : }
696 :
697 : /* Load the programdata account from Funk to read the programdata length */
698 0 : FD_TXN_ACCOUNT_DECL( programdata_account );
699 0 : err = fd_txn_account_init_from_funk_readonly( programdata_account,
700 0 : &loader_state->inner.program.programdata_address,
701 0 : txn_ctx->funk,
702 0 : txn_ctx->funk_txn );
703 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
704 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
705 0 : }
706 :
707 : /* Try to accumulate the programdata's data size
708 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L625-L630 */
709 0 : ulong programdata_size_delta = fd_ulong_sat_add( FD_TRANSACTION_ACCOUNT_BASE_SIZE,
710 0 : fd_txn_account_get_data_len( programdata_account ) );
711 0 : err = fd_increase_calculated_data_size( txn_ctx, programdata_size_delta );
712 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
713 0 : return err;
714 0 : }
715 :
716 : /* Add the programdata account to the list of loaded programdata accounts
717 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L631 */
718 0 : fd_memcpy(
719 0 : &additional_loaded_account_keys[(*additional_loaded_account_keys_cnt)++],
720 0 : &loader_state->inner.program.programdata_address,
721 0 : sizeof(fd_pubkey_t) );
722 :
723 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
724 0 : }
725 :
726 : /* Simplified transaction loading logic for SIMD-0186 which does the
727 : following:
728 : - Calculates the loaded data size for each address lookup table
729 : - Calculates the loaded data size for each transaction account
730 : - Calculates the loaded data size for each v3 programdata account
731 : not directly referenced in the transaction accounts
732 : - Collects rent from all referenced transaction accounts (excluding
733 : the fee payer)
734 : - Validates that each program invoked in a top-level instruction
735 : exists, is executable, and is owned by either the native loader
736 : or a bpf loader
737 :
738 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L550-L689 */
739 : static int
740 0 : fd_executor_load_transaction_accounts_simd_186( fd_exec_txn_ctx_t * txn_ctx ) {
741 0 : fd_epoch_schedule_t schedule[1] = { fd_sysvar_cache_epoch_schedule_read_nofail( fd_bank_sysvar_cache_query( txn_ctx->bank ) ) };
742 :
743 0 : ulong epoch = fd_slot_to_epoch( schedule, txn_ctx->slot, NULL );
744 :
745 : /* Programdata accounts that are loaded by this transaction.
746 : We keep track of these to ensure they are not counted twice.
747 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L559 */
748 0 : fd_pubkey_t additional_loaded_account_keys[ FD_TXN_ACCT_ADDR_MAX ] = { 0 };
749 0 : ulong additional_loaded_account_keys_cnt = 0UL;
750 :
751 : /* Charge a base fee for each address lookup table.
752 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L570-L576 */
753 0 : ulong aluts_size = fd_ulong_sat_mul( TXN( &txn_ctx->txn )->addr_table_lookup_cnt,
754 0 : FD_ADDRESS_LOOKUP_TABLE_BASE_SIZE );
755 0 : int err = fd_increase_calculated_data_size( txn_ctx, aluts_size );
756 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
757 0 : return err;
758 0 : }
759 :
760 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L642-L660 */
761 0 : for( ushort i=0; i<txn_ctx->accounts_cnt; i++ ) {
762 0 : fd_txn_account_t * acct = &txn_ctx->accounts[i];
763 0 : uchar unknown_acc = !!(fd_exec_txn_ctx_get_account_at_index( txn_ctx, i, &acct, fd_txn_account_check_exists ) ||
764 0 : fd_txn_account_get_lamports( acct )==0UL);
765 0 : uchar is_writable = !!(fd_exec_txn_ctx_account_is_writable_idx( txn_ctx, i ));
766 :
767 : /* Collect the fee payer account separately (since it was already)
768 : loaded during fee payer validation.
769 :
770 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L644-L648 */
771 0 : if( FD_UNLIKELY( i==FD_FEE_PAYER_TXN_IDX ) ) {
772 : /* Note that the dlen for most fee payers is 0, but we want to
773 : consider the case where the fee payer is a nonce account.
774 : We also must add a base account size to this value
775 : because this branch would only be taken AFTER SIMD-0186
776 : is enabled. */
777 0 : ulong loaded_acc_size = fd_ulong_sat_add( FD_TRANSACTION_ACCOUNT_BASE_SIZE,
778 0 : fd_txn_account_get_data_len( acct ) );
779 0 : int err = fd_collect_loaded_account(
780 0 : txn_ctx,
781 0 : acct,
782 0 : loaded_acc_size,
783 0 : additional_loaded_account_keys,
784 0 : &additional_loaded_account_keys_cnt );
785 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
786 0 : return err;
787 0 : }
788 0 : continue;
789 0 : }
790 :
791 : /* Load and collect any remaining accounts
792 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L652-L659 */
793 0 : ulong loaded_acc_size = load_transaction_account( txn_ctx, acct, is_writable, epoch, unknown_acc );
794 0 : int err = fd_collect_loaded_account(
795 0 : txn_ctx,
796 0 : acct,
797 0 : loaded_acc_size,
798 0 : additional_loaded_account_keys,
799 0 : &additional_loaded_account_keys_cnt );
800 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
801 0 : return err;
802 0 : }
803 0 : }
804 :
805 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L662-L686 */
806 0 : ushort instr_cnt = TXN( &txn_ctx->txn )->instr_cnt;
807 0 : for( ushort i=0; i<instr_cnt; i++ ) {
808 0 : fd_txn_instr_t const * instr = &TXN( &txn_ctx->txn )->instr[i];
809 :
810 : /* Mimicking `load_account()` here with 0-lamport check as well.
811 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L663-L666 */
812 0 : fd_txn_account_t * program_account;
813 0 : int err = fd_exec_txn_ctx_get_account_at_index( txn_ctx,
814 0 : instr->program_id,
815 0 : &program_account,
816 0 : fd_txn_account_check_exists );
817 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS || fd_txn_account_get_lamports( program_account )==0UL ) ) {
818 0 : return FD_RUNTIME_TXN_ERR_PROGRAM_ACCOUNT_NOT_FOUND;
819 0 : }
820 :
821 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L668-L675 */
822 0 : if( FD_UNLIKELY( !FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, remove_accounts_executable_flag_checks ) &&
823 0 : !fd_txn_account_is_executable( program_account ) ) ) {
824 0 : return FD_RUNTIME_TXN_ERR_INVALID_PROGRAM_FOR_EXECUTION;
825 0 : }
826 :
827 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L677-L681 */
828 0 : fd_pubkey_t const * owner_id = fd_txn_account_get_owner( program_account );
829 0 : if( FD_UNLIKELY( memcmp( owner_id->key, fd_solana_native_loader_id.key, sizeof(fd_pubkey_t) ) &&
830 0 : !fd_executor_pubkey_is_bpf_loader( owner_id ) ) ) {
831 0 : return FD_RUNTIME_TXN_ERR_INVALID_PROGRAM_FOR_EXECUTION;
832 0 : }
833 0 : }
834 :
835 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
836 0 : }
837 :
838 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L518-L548 */
839 : int
840 0 : fd_executor_load_transaction_accounts( fd_exec_txn_ctx_t * txn_ctx ) {
841 0 : if( FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, formalize_loaded_transaction_data_size ) ) {
842 0 : return fd_executor_load_transaction_accounts_simd_186( txn_ctx );
843 0 : } else {
844 0 : return fd_executor_load_transaction_accounts_old( txn_ctx );
845 0 : }
846 0 : }
847 :
848 : /* https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L118 */
849 : int
850 0 : fd_executor_validate_account_locks( fd_exec_txn_ctx_t const * txn_ctx ) {
851 : /* Ensure the number of account keys does not exceed the transaction lock limit
852 : https://github.com/anza-xyz/agave/blob/v2.2.17/accounts-db/src/account_locks.rs#L121 */
853 0 : ulong tx_account_lock_limit = get_transaction_account_lock_limit( txn_ctx );
854 0 : if( FD_UNLIKELY( txn_ctx->accounts_cnt>tx_account_lock_limit ) ) {
855 0 : return FD_RUNTIME_TXN_ERR_TOO_MANY_ACCOUNT_LOCKS;
856 0 : }
857 :
858 : /* Duplicate account check
859 : https://github.com/anza-xyz/agave/blob/v2.2.17/accounts-db/src/account_locks.rs#L123 */
860 0 : for( ushort i=0; i<txn_ctx->accounts_cnt; i++ ) {
861 0 : for( ushort j=(ushort)(i+1U); j<txn_ctx->accounts_cnt; j++ ) {
862 0 : if( FD_UNLIKELY( !memcmp( &txn_ctx->account_keys[i], &txn_ctx->account_keys[j], sizeof(fd_pubkey_t) ) ) ) {
863 0 : return FD_RUNTIME_TXN_ERR_ACCOUNT_LOADED_TWICE;
864 0 : }
865 0 : }
866 0 : }
867 :
868 : /* https://github.com/anza-xyz/agave/blob/v2.2.17/accounts-db/src/account_locks.rs#L124-L126 */
869 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
870 0 : }
871 :
872 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/compute-budget/src/compute_budget_limits.rs#L62-L70 */
873 : static ulong
874 0 : fd_get_prioritization_fee( fd_compute_budget_details_t const * compute_budget_details ) {
875 0 : uint128 micro_lamport_fee = fd_uint128_sat_mul( compute_budget_details->compute_unit_price, compute_budget_details->compute_unit_limit );
876 0 : uint128 fee = fd_uint128_sat_add( micro_lamport_fee, MICRO_LAMPORTS_PER_LAMPORT-1UL ) / MICRO_LAMPORTS_PER_LAMPORT;
877 0 : return fee>(uint128)ULONG_MAX ? ULONG_MAX : (ulong)fee;
878 0 : }
879 :
880 : static ulong
881 0 : fd_executor_lamports_per_signature( fd_fee_rate_governor_t const * fee_rate_governor ) {
882 : // https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/sdk/program/src/fee_calculator.rs#L110
883 0 : return fee_rate_governor->target_lamports_per_signature / 2;
884 0 : }
885 :
886 : static void
887 : fd_executor_calculate_fee( fd_exec_txn_ctx_t * txn_ctx,
888 : fd_txn_t const * txn_descriptor,
889 : uchar const * payload,
890 : ulong * ret_execution_fee,
891 0 : ulong * ret_priority_fee) {
892 : /* The execution fee is just the signature fee. The priority fee
893 : is calculated based on the compute budget details.
894 : https://github.com/anza-xyz/agave/blob/v2.3.1/fee/src/lib.rs#L66-L83 */
895 :
896 : // let signature_fee = Self::get_num_signatures_in_message(message) .saturating_mul(fee_structure.lamports_per_signature);
897 0 : ulong num_signatures = txn_descriptor->signature_cnt;
898 0 : for (ushort i=0; i<txn_descriptor->instr_cnt; ++i ) {
899 0 : fd_txn_instr_t const * txn_instr = &txn_descriptor->instr[i];
900 0 : fd_pubkey_t * program_id = &txn_ctx->account_keys[txn_instr->program_id];
901 0 : if( !memcmp(program_id->uc, fd_solana_keccak_secp_256k_program_id.key, sizeof(fd_pubkey_t)) ||
902 0 : !memcmp(program_id->uc, fd_solana_ed25519_sig_verify_program_id.key, sizeof(fd_pubkey_t)) ||
903 0 : (!memcmp(program_id->uc, fd_solana_secp256r1_program_id.key, sizeof(fd_pubkey_t)) && FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, enable_secp256r1_precompile )) ) {
904 0 : if( !txn_instr->data_sz ) {
905 0 : continue;
906 0 : }
907 0 : uchar const * data = payload + txn_instr->data_off;
908 0 : num_signatures = fd_ulong_sat_add(num_signatures, (ulong)(data[0]));
909 0 : }
910 0 : }
911 :
912 0 : *ret_execution_fee = fd_executor_lamports_per_signature( fd_bank_fee_rate_governor_query( txn_ctx->bank ) ) * num_signatures;
913 0 : *ret_priority_fee = fd_get_prioritization_fee( &txn_ctx->compute_budget_details );
914 0 : }
915 :
916 : /* This function creates a rollback account for just the fee payer. Although Agave
917 : also sets up rollback accounts for both the fee payer and nonce account here,
918 : we already set up the rollback nonce account in earlier sanitization checks. Here
919 : we have to capture the entire fee payer record so that if the transaction fails,
920 : the fee payer state can be rolled back to it's state pre-transaction, and then debited
921 : any transaction fees.
922 :
923 : Our implementation is slightly different than Agave's in several ways:
924 : 1. The rollback nonce account has already been set up when checking the transaction age
925 : 2. When the nonce and fee payer accounts are the same...
926 : - Agave copies the data from the rollback nonce account into the rollback fee payer account,
927 : and then uses that new fee payer account as the rollback account.
928 : - We simply set the rent epoch and lamports of the rollback nonce account (since the other fields
929 : of the account do not change)
930 :
931 : https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/rollback_accounts.rs#L34-L77 */
932 : static void
933 : fd_executor_create_rollback_fee_payer_account( fd_exec_txn_ctx_t * txn_ctx,
934 0 : ulong total_fee ) {
935 0 : fd_pubkey_t * fee_payer_key = &txn_ctx->account_keys[FD_FEE_PAYER_TXN_IDX];
936 0 : fd_txn_account_t * rollback_fee_payer_acc;
937 :
938 :
939 : /* When setting the data of the rollback fee payer, there is an edge case where the fee payer is the nonce account.
940 : In this case, we can just deduct fees from the nonce account and return, because we save the nonce account in the
941 : commit phase anyways. */
942 0 : if( FD_UNLIKELY( txn_ctx->nonce_account_idx_in_txn==FD_FEE_PAYER_TXN_IDX ) ) {
943 0 : rollback_fee_payer_acc = txn_ctx->rollback_nonce_account;
944 0 : } else {
945 0 : int err = FD_ACC_MGR_SUCCESS;
946 0 : fd_account_meta_t const * meta = fd_funk_get_acc_meta_readonly(
947 0 : txn_ctx->funk,
948 0 : txn_ctx->funk_txn,
949 0 : fee_payer_key,
950 0 : NULL,
951 0 : &err,
952 0 : NULL );
953 :
954 0 : ulong data_len = fd_txn_account_get_data_len( &txn_ctx->accounts[FD_FEE_PAYER_TXN_IDX] );
955 0 : void * fee_payer_data = fd_spad_alloc( txn_ctx->spad, FD_ACCOUNT_REC_ALIGN, sizeof(fd_account_meta_t) + data_len );
956 0 : fd_memcpy( fee_payer_data, (uchar *)meta, sizeof(fd_account_meta_t) + data_len );
957 0 : if( FD_UNLIKELY( !fd_txn_account_join( fd_txn_account_new(
958 0 : txn_ctx->rollback_fee_payer_account,
959 0 : fee_payer_key,
960 0 : (fd_account_meta_t *)fee_payer_data,
961 0 : 1 ), txn_ctx->spad_wksp ) ) ) {
962 0 : FD_LOG_CRIT(( "Failed to join txn account" ));
963 0 : }
964 :
965 0 : rollback_fee_payer_acc = txn_ctx->rollback_fee_payer_account;
966 0 : }
967 :
968 : /* Deduct the transaction fees from the rollback account. Because of prior checks, this should never fail. */
969 0 : if( FD_UNLIKELY( fd_txn_account_checked_sub_lamports( rollback_fee_payer_acc, total_fee ) ) ) {
970 0 : FD_LOG_ERR(( "fd_executor_create_rollback_fee_payer_account(): failed to deduct fees from rollback account" ));
971 0 : }
972 0 : }
973 :
974 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L557-L634 */
975 : int
976 0 : fd_executor_validate_transaction_fee_payer( fd_exec_txn_ctx_t * txn_ctx ) {
977 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L574-L580 */
978 0 : fd_txn_account_t * fee_payer_rec = NULL;
979 0 : int err = fd_exec_txn_ctx_get_account_at_index( txn_ctx,
980 0 : FD_FEE_PAYER_TXN_IDX,
981 0 : &fee_payer_rec,
982 0 : fd_txn_account_check_fee_payer_writable );
983 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
984 0 : return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
985 0 : }
986 :
987 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( txn_ctx->bank );
988 0 : fd_rent_t const * rent = fd_bank_rent_query( txn_ctx->bank );
989 :
990 : /* Collect rent from the fee payer
991 : https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L583-L589 */
992 0 : txn_ctx->collected_rent += fd_runtime_collect_rent_from_account(
993 0 : epoch_schedule, rent,
994 0 : fd_bank_slots_per_year_get( txn_ctx->bank ),
995 0 : fee_payer_rec,
996 0 : fd_slot_to_epoch( epoch_schedule, txn_ctx->slot, NULL ) );
997 :
998 : /* Calculate transaction fees
999 : https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L597-L606 */
1000 0 : ulong execution_fee = 0UL;
1001 0 : ulong priority_fee = 0UL;
1002 :
1003 0 : fd_executor_calculate_fee( txn_ctx, TXN( &txn_ctx->txn ), txn_ctx->txn.payload, &execution_fee, &priority_fee );
1004 0 : ulong total_fee = fd_ulong_sat_add( execution_fee, priority_fee );
1005 :
1006 0 : if( !FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, remove_rounding_in_fee_calculation ) ) {
1007 0 : total_fee = fd_rust_cast_double_to_ulong( round( (double)total_fee ) );
1008 0 : }
1009 :
1010 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L609-L616 */
1011 0 : err = fd_validate_fee_payer( fee_payer_rec, rent, total_fee, txn_ctx->spad );
1012 0 : if( FD_UNLIKELY( err ) ) {
1013 0 : return err;
1014 0 : }
1015 :
1016 : /* Create the rollback fee payer account
1017 : https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L620-L626 */
1018 0 : fd_executor_create_rollback_fee_payer_account( txn_ctx, total_fee );
1019 :
1020 : /* Set the starting lamports (to avoid unbalanced lamports issues in instruction execution) */
1021 0 : fee_payer_rec->starting_lamports = fd_txn_account_get_lamports( fee_payer_rec ); /* TODO: why do we do this everywhere? */
1022 :
1023 0 : txn_ctx->execution_fee = execution_fee;
1024 0 : txn_ctx->priority_fee = priority_fee;
1025 :
1026 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
1027 0 : }
1028 :
1029 : /* Simply unpacks the account keys from the serialized transaction and sets them in the txn_ctx. */
1030 : void
1031 0 : fd_executor_setup_txn_account_keys( fd_exec_txn_ctx_t * txn_ctx ) {
1032 0 : txn_ctx->accounts_cnt = (uchar)TXN( &txn_ctx->txn )->acct_addr_cnt;
1033 0 : fd_pubkey_t * tx_accs = (fd_pubkey_t *)((uchar *)txn_ctx->txn.payload + TXN( &txn_ctx->txn )->acct_addr_off);
1034 :
1035 : // Set up accounts in the transaction body and perform checks
1036 0 : for( ulong i = 0UL; i < TXN( &txn_ctx->txn )->acct_addr_cnt; i++ ) {
1037 0 : txn_ctx->account_keys[i] = tx_accs[i];
1038 0 : }
1039 0 : }
1040 :
1041 : /* Resolves any address lookup tables referenced in the transaction and adds
1042 : them to the transaction's account keys. Returns 0 on success or if the transaction
1043 : is a legacy transaction, and 1 on failure. */
1044 : int
1045 0 : fd_executor_setup_txn_alut_account_keys( fd_exec_txn_ctx_t * txn_ctx ) {
1046 0 : if( TXN( &txn_ctx->txn )->transaction_version == FD_TXN_V0 ) {
1047 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/runtime/src/bank/address_lookup_table.rs#L44-L48 */
1048 0 : fd_sysvar_cache_t const * sysvar_cache = fd_bank_sysvar_cache_query( txn_ctx->bank );
1049 0 : fd_slot_hash_t const * slot_hashes = fd_sysvar_cache_slot_hashes_join_const( sysvar_cache );
1050 0 : if( FD_UNLIKELY( !slot_hashes ) ) {
1051 0 : return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
1052 0 : }
1053 :
1054 0 : fd_acct_addr_t * accts_alt = (fd_acct_addr_t *) fd_type_pun( &txn_ctx->account_keys[txn_ctx->accounts_cnt] );
1055 0 : int err = fd_runtime_load_txn_address_lookup_tables( TXN( &txn_ctx->txn ),
1056 0 : txn_ctx->txn.payload,
1057 0 : txn_ctx->funk,
1058 0 : txn_ctx->funk_txn,
1059 0 : txn_ctx->slot,
1060 0 : slot_hashes,
1061 0 : accts_alt );
1062 0 : fd_sysvar_cache_slot_hashes_leave_const( sysvar_cache, slot_hashes );
1063 0 : txn_ctx->accounts_cnt += TXN( &txn_ctx->txn )->addr_table_adtl_cnt;
1064 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) return err;
1065 :
1066 0 : }
1067 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
1068 0 : }
1069 :
1070 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L319-L357 */
1071 : static inline int
1072 : fd_txn_ctx_push( fd_exec_txn_ctx_t * txn_ctx,
1073 0 : fd_instr_info_t * instr ) {
1074 : /* Earlier checks in the permalink are redundant since Agave maintains instr stack and trace accounts separately
1075 : https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L327-L328 */
1076 0 : ulong starting_lamports_h = 0UL;
1077 0 : ulong starting_lamports_l = 0UL;
1078 0 : int err = fd_instr_info_sum_account_lamports( instr,
1079 0 : txn_ctx,
1080 0 : &starting_lamports_h,
1081 0 : &starting_lamports_l );
1082 0 : if( FD_UNLIKELY( err ) ) {
1083 0 : return err;
1084 0 : }
1085 0 : instr->starting_lamports_h = starting_lamports_h;
1086 0 : instr->starting_lamports_l = starting_lamports_l;
1087 :
1088 : /* Check that the caller's lamport sum has not changed.
1089 : https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L329-L340 */
1090 0 : if( txn_ctx->instr_stack_sz>0 ) {
1091 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L330 */
1092 0 : fd_exec_instr_ctx_t const * caller_instruction_context = &txn_ctx->instr_stack[ txn_ctx->instr_stack_sz-1 ];
1093 :
1094 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L331-L332 */
1095 0 : ulong original_caller_lamport_sum_h = caller_instruction_context->instr->starting_lamports_h;
1096 0 : ulong original_caller_lamport_sum_l = caller_instruction_context->instr->starting_lamports_l;
1097 :
1098 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L333-L334 */
1099 0 : ulong current_caller_lamport_sum_h = 0UL;
1100 0 : ulong current_caller_lamport_sum_l = 0UL;
1101 0 : int err = fd_instr_info_sum_account_lamports( caller_instruction_context->instr,
1102 0 : caller_instruction_context->txn_ctx,
1103 0 : ¤t_caller_lamport_sum_h,
1104 0 : ¤t_caller_lamport_sum_l );
1105 0 : if( FD_UNLIKELY( err ) ) {
1106 0 : return err;
1107 0 : }
1108 :
1109 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L335-L339 */
1110 0 : if( FD_UNLIKELY( current_caller_lamport_sum_h!=original_caller_lamport_sum_h ||
1111 0 : current_caller_lamport_sum_l!=original_caller_lamport_sum_l ) ) {
1112 0 : return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
1113 0 : }
1114 0 : }
1115 :
1116 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L347-L351 */
1117 0 : if( FD_UNLIKELY( txn_ctx->instr_trace_length>=FD_MAX_INSTRUCTION_TRACE_LENGTH ) ) {
1118 0 : return FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED;
1119 0 : }
1120 0 : txn_ctx->instr_trace_length++;
1121 :
1122 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L352-L356 */
1123 0 : if( FD_UNLIKELY( txn_ctx->instr_stack_sz>=FD_MAX_INSTRUCTION_STACK_DEPTH ) ) {
1124 0 : return FD_EXECUTOR_INSTR_ERR_CALL_DEPTH;
1125 0 : }
1126 0 : txn_ctx->instr_stack_sz++;
1127 :
1128 : /* A beloved refactor moves sysvar instructions updating to the instruction level as of v2.2.12...
1129 : https://github.com/anza-xyz/agave/blob/v2.2.12/transaction-context/src/lib.rs#L396-L407 */
1130 0 : int idx = fd_exec_txn_ctx_find_index_of_account( txn_ctx, &fd_sysvar_instructions_id );
1131 0 : if( FD_UNLIKELY( idx!=-1 ) ) {
1132 : /* https://github.com/anza-xyz/agave/blob/v2.2.12/transaction-context/src/lib.rs#L397-L400 */
1133 0 : fd_txn_account_t * sysvar_instructions_account = NULL;
1134 0 : err = fd_exec_txn_ctx_get_account_at_index( txn_ctx, (ushort)idx, &sysvar_instructions_account, NULL );
1135 0 : if( FD_UNLIKELY( err ) ) {
1136 0 : return FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS;
1137 0 : }
1138 :
1139 : /* https://github.com/anza-xyz/agave/blob/v2.2.12/transaction-context/src/lib.rs#L401-L402 */
1140 0 : if( FD_UNLIKELY( !fd_txn_account_try_borrow_mut( sysvar_instructions_account ) ) ) {
1141 0 : return FD_EXECUTOR_INSTR_ERR_ACC_BORROW_FAILED;
1142 0 : }
1143 :
1144 : /* https://github.com/anza-xyz/agave/blob/v2.2.12/transaction-context/src/lib.rs#L403-L406 */
1145 0 : fd_sysvar_instructions_update_current_instr_idx( sysvar_instructions_account, (ushort)txn_ctx->current_instr_idx );
1146 0 : fd_txn_account_drop( sysvar_instructions_account );
1147 0 : }
1148 :
1149 0 : return FD_EXECUTOR_INSTR_SUCCESS;
1150 0 : }
1151 :
1152 : /* Pushes a new instruction onto the instruction stack and trace. This check loops through all instructions in the current call stack
1153 : and checks for reentrancy violations. If successful, simply increments the instruction stack and trace size and returns. It is
1154 : the responsibility of the caller to populate the newly pushed instruction fields, which are undefined otherwise.
1155 :
1156 : https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L246-L290 */
1157 : int
1158 : fd_instr_stack_push( fd_exec_txn_ctx_t * txn_ctx,
1159 0 : fd_instr_info_t * instr ) {
1160 : /* Agave keeps a vector of vectors called program_indices that stores the program_id index for each instruction within the transaction.
1161 : https://github.com/anza-xyz/agave/blob/v2.1.7/svm/src/account_loader.rs#L347-L402
1162 : If and only if the program_id is the native loader, then the vector for respective specific instruction (account_indices) is empty.
1163 : https://github.com/anza-xyz/agave/blob/v2.1.7/svm/src/account_loader.rs#L350-L358
1164 : While trying to push a new instruction onto the instruction stack, if the vector for the respective instruction is empty, Agave throws UnsupportedProgramId
1165 : https://github.com/anza-xyz/agave/blob/v2.1.7/program-runtime/src/invoke_context.rs#L253-L255
1166 : The only way for the vector to be empty is if the program_id is the native loader, so we can a program_id check here
1167 : */
1168 :
1169 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/program-runtime/src/invoke_context.rs#L250-L252 */
1170 0 : fd_pubkey_t const * program_id_pubkey = NULL;
1171 0 : int err = fd_exec_txn_ctx_get_key_of_account_at_index( txn_ctx,
1172 0 : instr->program_id,
1173 0 : &program_id_pubkey );
1174 0 : if( FD_UNLIKELY( err ) ) {
1175 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
1176 0 : }
1177 :
1178 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L256-L286 */
1179 0 : if( txn_ctx->instr_stack_sz ) {
1180 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L261-L285 */
1181 0 : uchar contains = 0;
1182 0 : uchar is_last = 0;
1183 :
1184 : // Checks all previous instructions in the stack for reentrancy
1185 0 : for( uchar level=0; level<txn_ctx->instr_stack_sz; level++ ) {
1186 0 : fd_exec_instr_ctx_t * instr_ctx = &txn_ctx->instr_stack[level];
1187 : // Optimization: compare program id index instead of pubkey since account keys are unique
1188 0 : if( instr->program_id == instr_ctx->instr->program_id ) {
1189 : // Reentrancy not allowed unless caller is calling itself
1190 0 : if( level == txn_ctx->instr_stack_sz-1 ) {
1191 0 : is_last = 1;
1192 0 : }
1193 0 : contains = 1;
1194 0 : }
1195 0 : }
1196 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L282-L285 */
1197 0 : if( FD_UNLIKELY( contains && !is_last ) ) {
1198 0 : return FD_EXECUTOR_INSTR_ERR_REENTRANCY_NOT_ALLOWED;
1199 0 : }
1200 0 : }
1201 : /* "Push" a new instruction onto the stack by simply incrementing the stack and trace size counters
1202 : https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L289 */
1203 0 : return fd_txn_ctx_push( txn_ctx, instr );
1204 0 : }
1205 :
1206 : /* Pops an instruction from the instruction stack. Agave's implementation performs instruction balancing checks every time pop is called,
1207 : but error codes returned from `pop` are only used if the program's execution was successful. Therefore, we can optimize our code by only
1208 : checking for unbalanced instructions if the program execution was successful within fd_execute_instr.
1209 :
1210 : https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L293-L298 */
1211 : int
1212 : fd_instr_stack_pop( fd_exec_txn_ctx_t * txn_ctx,
1213 0 : fd_instr_info_t const * instr ) {
1214 : /* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L362-L364 */
1215 0 : if( FD_UNLIKELY( txn_ctx->instr_stack_sz==0 ) ) {
1216 0 : return FD_EXECUTOR_INSTR_ERR_CALL_DEPTH;
1217 0 : }
1218 0 : txn_ctx->instr_stack_sz--;
1219 :
1220 : /* Verify all executable accounts have no outstanding refs
1221 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L367-L371 */
1222 0 : for( ushort i=0; i<instr->acct_cnt; i++ ) {
1223 0 : ushort idx_in_txn = instr->accounts[i].index_in_transaction;
1224 0 : fd_txn_account_t * account = &txn_ctx->accounts[ idx_in_txn ];
1225 0 : if( FD_UNLIKELY( fd_txn_account_is_executable( account ) &&
1226 0 : fd_txn_account_is_borrowed( account ) ) ) {
1227 0 : return FD_EXECUTOR_INSTR_ERR_ACC_BORROW_OUTSTANDING;
1228 0 : }
1229 0 : }
1230 :
1231 : /* Verify lamports are balanced before and after instruction
1232 : https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L366-L380 */
1233 0 : ulong ending_lamports_h = 0UL;
1234 0 : ulong ending_lamports_l = 0UL;
1235 0 : int err = fd_instr_info_sum_account_lamports( instr,
1236 0 : txn_ctx,
1237 0 : &ending_lamports_h,
1238 0 : &ending_lamports_l );
1239 0 : if( FD_UNLIKELY( err ) ) {
1240 0 : return err;
1241 0 : }
1242 0 : if( FD_UNLIKELY( ending_lamports_l != instr->starting_lamports_l || ending_lamports_h != instr->starting_lamports_h ) ) {
1243 0 : return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
1244 0 : }
1245 :
1246 0 : return FD_EXECUTOR_INSTR_SUCCESS;;
1247 0 : }
1248 :
1249 : /* This function mimics Agave's `.and(self.pop())` functionality,
1250 : where we always pop the instruction stack no matter what the error code is.
1251 : https://github.com/anza-xyz/agave/blob/v2.2.12/program-runtime/src/invoke_context.rs#L480 */
1252 : static inline int
1253 : fd_execute_instr_end( fd_exec_instr_ctx_t * instr_ctx,
1254 : fd_instr_info_t * instr,
1255 0 : int instr_exec_result ) {
1256 0 : int stack_pop_err = fd_instr_stack_pop( instr_ctx->txn_ctx, instr );
1257 :
1258 : /* Only report the stack pop error on success */
1259 0 : if( FD_UNLIKELY( instr_exec_result==FD_EXECUTOR_INSTR_SUCCESS && stack_pop_err ) ) {
1260 0 : FD_TXN_PREPARE_ERR_OVERWRITE( instr_ctx->txn_ctx );
1261 0 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, stack_pop_err, instr_ctx->txn_ctx->instr_err_idx );
1262 0 : instr_exec_result = stack_pop_err;
1263 0 : }
1264 :
1265 0 : if( FD_UNLIKELY( instr_exec_result && !instr_ctx->txn_ctx->failed_instr ) ) {
1266 0 : instr_ctx->txn_ctx->failed_instr = instr_ctx;
1267 0 : }
1268 :
1269 0 : return instr_exec_result;
1270 0 : }
1271 :
1272 : int
1273 : fd_execute_instr( fd_exec_txn_ctx_t * txn_ctx,
1274 0 : fd_instr_info_t * instr ) {
1275 0 : fd_sysvar_cache_t const * sysvar_cache = fd_bank_sysvar_cache_query( txn_ctx->bank );
1276 0 : FD_RUNTIME_TXN_SPAD_FRAME_BEGIN( txn_ctx->spad, txn_ctx ) {
1277 0 : int instr_exec_result = fd_instr_stack_push( txn_ctx, instr );
1278 0 : if( FD_UNLIKELY( instr_exec_result ) ) {
1279 0 : FD_TXN_PREPARE_ERR_OVERWRITE( txn_ctx );
1280 0 : FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, instr_exec_result, txn_ctx->instr_err_idx );
1281 0 : return instr_exec_result;
1282 0 : }
1283 :
1284 : /* `process_executable_chain()`
1285 : https://github.com/anza-xyz/agave/blob/v2.2.12/program-runtime/src/invoke_context.rs#L512-L619 */
1286 0 : fd_exec_instr_ctx_t * ctx = &txn_ctx->instr_stack[ txn_ctx->instr_stack_sz - 1 ];
1287 0 : *ctx = (fd_exec_instr_ctx_t) {
1288 0 : .instr = instr,
1289 0 : .txn_ctx = txn_ctx,
1290 0 : .sysvar_cache = sysvar_cache,
1291 0 : };
1292 0 : fd_base58_encode_32( txn_ctx->accounts[ instr->program_id ].pubkey->uc, NULL, ctx->program_id_base58 );
1293 :
1294 0 : txn_ctx->instr_trace[ txn_ctx->instr_trace_length - 1 ] = (fd_exec_instr_trace_entry_t) {
1295 0 : .instr_info = instr,
1296 0 : .stack_height = txn_ctx->instr_stack_sz,
1297 0 : };
1298 :
1299 : /* Look up the native program. We check for precompiles within the lookup function as well.
1300 : https://github.com/anza-xyz/agave/blob/v2.1.6/svm/src/message_processor.rs#L88 */
1301 0 : fd_exec_instr_fn_t native_prog_fn;
1302 0 : uchar is_precompile;
1303 0 : int err = fd_executor_lookup_native_program( &txn_ctx->accounts[ instr->program_id ],
1304 0 : txn_ctx,
1305 0 : &native_prog_fn,
1306 0 : &is_precompile );
1307 :
1308 0 : if( FD_UNLIKELY( err ) ) {
1309 0 : FD_TXN_PREPARE_ERR_OVERWRITE( txn_ctx );
1310 0 : FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, err, txn_ctx->instr_err_idx );
1311 0 : return err;
1312 0 : }
1313 :
1314 0 : if( FD_LIKELY( native_prog_fn!=NULL ) ) {
1315 : /* If this branch is taken, we've found an entrypoint to execute. */
1316 0 : fd_log_collector_program_invoke( ctx );
1317 :
1318 : /* Only reset the return data when executing a native builtin program (not a precompile)
1319 : https://github.com/anza-xyz/agave/blob/v2.1.6/program-runtime/src/invoke_context.rs#L536-L537 */
1320 0 : if( FD_LIKELY( !is_precompile ) ) {
1321 0 : fd_exec_txn_ctx_reset_return_data( txn_ctx );
1322 0 : }
1323 :
1324 : /* Execute the native program. */
1325 0 : instr_exec_result = native_prog_fn( ctx );
1326 0 : } else {
1327 : /* Unknown program. In this case specifically, we should not log the program id. */
1328 0 : instr_exec_result = FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
1329 0 : FD_TXN_PREPARE_ERR_OVERWRITE( txn_ctx );
1330 0 : FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, instr_exec_result, txn_ctx->instr_err_idx );
1331 0 : return fd_execute_instr_end( ctx, instr, instr_exec_result );
1332 0 : }
1333 :
1334 0 : if( FD_LIKELY( instr_exec_result==FD_EXECUTOR_INSTR_SUCCESS ) ) {
1335 : /* Log success */
1336 0 : fd_log_collector_program_success( ctx );
1337 0 : } else {
1338 : /* Log failure cases.
1339 : We assume that the correct type of error is stored in ctx.
1340 : Syscalls are expected to log when the error is generated, while
1341 : native programs will be logged here.
1342 : (This is because syscall errors often carry data with them.)
1343 :
1344 : TODO: This hackily handles cases where the exec_err and exec_err_kind
1345 : is not set yet. We should change our native programs to set
1346 : this in their respective processors. */
1347 0 : if( !txn_ctx->exec_err ) {
1348 0 : FD_TXN_PREPARE_ERR_OVERWRITE( txn_ctx );
1349 0 : FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, instr_exec_result, txn_ctx->instr_err_idx );
1350 0 : fd_log_collector_program_failure( ctx );
1351 0 : } else {
1352 0 : fd_log_collector_program_failure( ctx );
1353 0 : FD_TXN_PREPARE_ERR_OVERWRITE( txn_ctx );
1354 0 : FD_TXN_ERR_FOR_LOG_INSTR( txn_ctx, instr_exec_result, txn_ctx->instr_err_idx );
1355 0 : }
1356 0 : }
1357 :
1358 0 : return fd_execute_instr_end( ctx, instr, instr_exec_result );
1359 0 : } FD_RUNTIME_TXN_SPAD_FRAME_END;
1360 0 : }
1361 :
1362 : void
1363 : fd_executor_reclaim_account( fd_exec_txn_ctx_t * txn_ctx,
1364 0 : fd_txn_account_t * account ) {
1365 0 : fd_txn_account_set_slot( account, txn_ctx->slot );
1366 0 : if( FD_UNLIKELY( fd_txn_account_get_lamports( account )==0UL ) ) {
1367 0 : fd_txn_account_set_data_len( account, 0UL );
1368 0 : fd_txn_account_clear_owner( account );
1369 0 : }
1370 0 : }
1371 :
1372 : void
1373 : fd_exec_txn_ctx_from_exec_slot_ctx( fd_exec_slot_ctx_t const * slot_ctx,
1374 : fd_exec_txn_ctx_t * ctx,
1375 : fd_wksp_t const * funk_wksp,
1376 : ulong funk_txn_gaddr,
1377 : ulong funk_gaddr,
1378 0 : fd_bank_hash_cmp_t * bank_hash_cmp ) {
1379 0 : ctx->funk_txn = fd_wksp_laddr( funk_wksp, funk_txn_gaddr );
1380 0 : if( FD_UNLIKELY( !ctx->funk_txn ) ) {
1381 0 : FD_LOG_ERR(( "Could not find valid funk transaction" ));
1382 0 : }
1383 :
1384 0 : if( FD_UNLIKELY( !fd_funk_join( ctx->funk, fd_wksp_laddr( funk_wksp, funk_gaddr ) ) ) ) {
1385 0 : FD_LOG_ERR(( "Could not find valid funk %lu", funk_gaddr ));
1386 0 : }
1387 :
1388 0 : ctx->status_cache = slot_ctx->status_cache;
1389 :
1390 0 : ctx->bank_hash_cmp = bank_hash_cmp;
1391 :
1392 0 : ctx->enable_exec_recording = !!( slot_ctx->bank->flags & FD_BANK_FLAGS_EXEC_RECORDING );
1393 :
1394 0 : ctx->bank = slot_ctx->bank;
1395 :
1396 0 : ctx->slot = fd_bank_slot_get( slot_ctx->bank );
1397 :
1398 0 : ctx->features = fd_bank_features_get( ctx->bank );
1399 0 : }
1400 :
1401 : fd_txn_account_t *
1402 : fd_executor_setup_txn_account( fd_exec_txn_ctx_t * txn_ctx,
1403 0 : ushort idx ) {
1404 :
1405 : /* To setup a transaction account, we need to first retrieve a
1406 : read-only handle to the account from the database. */
1407 :
1408 0 : fd_pubkey_t * acc = &txn_ctx->account_keys[ idx ];
1409 :
1410 0 : int err = FD_ACC_MGR_SUCCESS;
1411 0 : fd_account_meta_t const * meta = fd_funk_get_acc_meta_readonly(
1412 0 : txn_ctx->funk,
1413 0 : txn_ctx->funk_txn,
1414 0 : acc,
1415 0 : NULL,
1416 0 : &err,
1417 0 : NULL );
1418 :
1419 0 : fd_txn_account_t * txn_account = &txn_ctx->accounts[ idx ];
1420 :
1421 : /* If there is an error with a read from the accounts database, it is
1422 : unexpected unless the account does not exist. */
1423 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS && err!=FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) {
1424 0 : FD_LOG_CRIT(( "fd_txn_account_init_from_funk_readonly err=%d", err ));
1425 0 : }
1426 :
1427 0 : int is_writable = fd_exec_txn_ctx_account_is_writable_idx( txn_ctx, idx ) || idx==FD_FEE_PAYER_TXN_IDX;
1428 0 : fd_account_meta_t * account_meta = NULL;
1429 0 : fd_wksp_t * data_wksp = NULL;
1430 :
1431 0 : if( is_writable ) {
1432 : /* If the account is writable or a fee payer, then we need to create
1433 : staging regions for the account. If the account exists, we need to
1434 : copy the account data into the staging area; otherwise, we need to
1435 : initialize a new metadata. */
1436 :
1437 0 : uchar * new_raw_data = fd_spad_alloc( txn_ctx->spad, FD_ACCOUNT_REC_ALIGN, FD_ACC_TOT_SZ_MAX );
1438 0 : ulong dlen = !!meta ? meta->dlen : 0UL;
1439 :
1440 0 : if( FD_LIKELY( meta ) ) {
1441 : /* Account exists, copy the data into the staging area */
1442 0 : fd_memcpy( new_raw_data, (uchar *)meta, sizeof(fd_account_meta_t)+dlen );
1443 0 : } else {
1444 : /* Account did not exist, set up metadata */
1445 0 : fd_account_meta_init( (fd_account_meta_t *)new_raw_data );
1446 0 : }
1447 :
1448 0 : account_meta = (fd_account_meta_t *)new_raw_data;
1449 0 : data_wksp = txn_ctx->spad_wksp;
1450 :
1451 0 : } else {
1452 : /* If the account is not writable, then we can simply initialize
1453 : the txn account with the read-only accountsdb record. However,
1454 : if the account does not exist, we need to initialize a new
1455 : metadata. */
1456 :
1457 0 : if( FD_LIKELY( err==FD_ACC_MGR_SUCCESS ) ) {
1458 0 : account_meta = (fd_account_meta_t *)meta;
1459 0 : data_wksp = fd_funk_wksp( txn_ctx->funk );
1460 0 : } else {
1461 0 : uchar * mem = fd_spad_alloc( txn_ctx->spad, FD_TXN_ACCOUNT_ALIGN, sizeof(fd_account_meta_t) );
1462 0 : account_meta = (fd_account_meta_t *)mem;
1463 0 : data_wksp = txn_ctx->spad_wksp;
1464 0 : fd_account_meta_init( account_meta );
1465 0 : }
1466 0 : }
1467 :
1468 0 : if( FD_UNLIKELY( !fd_txn_account_join( fd_txn_account_new(
1469 0 : txn_account,
1470 0 : acc,
1471 0 : account_meta,
1472 0 : is_writable ), data_wksp ) ) ) {
1473 0 : FD_LOG_CRIT(( "Failed to join txn account" ));
1474 0 : }
1475 :
1476 0 : return txn_account;
1477 0 : }
1478 :
1479 : static void
1480 : fd_executor_setup_executable_account( fd_exec_txn_ctx_t * txn_ctx,
1481 : fd_txn_account_t const * account,
1482 0 : ushort * executable_idx ) {
1483 0 : int err = 0;
1484 0 : fd_bpf_upgradeable_loader_state_t * program_loader_state = fd_bpf_loader_program_get_state( account, txn_ctx->spad, &err );
1485 0 : if( FD_UNLIKELY( !program_loader_state ) ) {
1486 0 : return;
1487 0 : }
1488 :
1489 0 : if( !fd_bpf_upgradeable_loader_state_is_program( program_loader_state ) ) {
1490 0 : return;
1491 0 : }
1492 :
1493 : /* Attempt to load the program data account from funk. This prevents any unknown program
1494 : data accounts from getting loaded into the executable accounts list. If such a program is
1495 : invoked, the call will fail at the instruction execution level since the programdata
1496 : account will not exist within the executable accounts list. */
1497 0 : fd_pubkey_t * programdata_acc = &program_loader_state->inner.program.programdata_address;
1498 0 : if( FD_LIKELY( fd_txn_account_init_from_funk_readonly( &txn_ctx->executable_accounts[ *executable_idx ],
1499 0 : programdata_acc,
1500 0 : txn_ctx->funk,
1501 0 : txn_ctx->funk_txn )==0 ) ) {
1502 0 : (*executable_idx)++;
1503 0 : }
1504 0 : }
1505 :
1506 : void
1507 0 : fd_executor_setup_accounts_for_txn( fd_exec_txn_ctx_t * txn_ctx ) {
1508 0 : ushort j = 0UL;
1509 0 : fd_memset( txn_ctx->accounts, 0, sizeof(fd_txn_account_t) * txn_ctx->accounts_cnt );
1510 :
1511 0 : for( ushort i=0; i<txn_ctx->accounts_cnt; i++ ) {
1512 :
1513 0 : fd_txn_account_t * txn_account = fd_executor_setup_txn_account( txn_ctx, i );
1514 :
1515 0 : if( FD_UNLIKELY( txn_account &&
1516 0 : memcmp( fd_txn_account_get_owner( txn_account ), fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) == 0 ) ) {
1517 0 : fd_executor_setup_executable_account( txn_ctx, txn_account, &j );
1518 0 : }
1519 0 : }
1520 :
1521 : /* Dumping ELF files to protobuf, if applicable */
1522 0 : int dump_elf_to_pb = txn_ctx->capture_ctx &&
1523 0 : txn_ctx->slot >= txn_ctx->capture_ctx->dump_proto_start_slot &&
1524 0 : txn_ctx->capture_ctx->dump_elf_to_pb;
1525 0 : if( FD_UNLIKELY( dump_elf_to_pb ) ) {
1526 0 : for( ushort i=0; i<txn_ctx->accounts_cnt; i++ ) {
1527 0 : fd_dump_elf_to_protobuf( txn_ctx, &txn_ctx->accounts[i] );
1528 0 : }
1529 0 : }
1530 :
1531 0 : txn_ctx->nonce_account_idx_in_txn = ULONG_MAX;
1532 0 : txn_ctx->executable_cnt = j;
1533 :
1534 : /* Set up instr infos from the txn descriptor. No Agave equivalent to this function. */
1535 0 : fd_executor_setup_instr_infos_from_txn_instrs( txn_ctx );
1536 0 : }
1537 :
1538 : int
1539 0 : fd_executor_txn_verify( fd_exec_txn_ctx_t * txn_ctx ) {
1540 0 : fd_sha512_t * shas[ FD_TXN_ACTUAL_SIG_MAX ];
1541 0 : for ( ulong i=0UL; i<FD_TXN_ACTUAL_SIG_MAX; i++ ) {
1542 0 : fd_sha512_t * sha = fd_sha512_join( fd_sha512_new( fd_spad_alloc( txn_ctx->spad, alignof(fd_sha512_t), sizeof(fd_sha512_t) ) ) );
1543 0 : if( FD_UNLIKELY( !sha ) ) FD_LOG_ERR(( "fd_sha512_join failed" ));
1544 0 : shas[i] = sha;
1545 0 : }
1546 :
1547 0 : fd_txn_t const * txn = TXN( &txn_ctx->txn );
1548 :
1549 0 : uchar signature_cnt = txn->signature_cnt;
1550 0 : ushort signature_off = txn->signature_off;
1551 0 : ushort acct_addr_off = txn->acct_addr_off;
1552 0 : ushort message_off = txn->message_off;
1553 :
1554 0 : uchar const * signatures = (uchar *)txn_ctx->txn.payload + signature_off;
1555 0 : uchar const * pubkeys = (uchar *)txn_ctx->txn.payload + acct_addr_off;
1556 0 : uchar const * msg = (uchar *)txn_ctx->txn.payload + message_off;
1557 0 : ulong msg_sz = (ulong)txn_ctx->txn.payload_sz - message_off;
1558 :
1559 : /* Verify signatures */
1560 0 : int res = fd_ed25519_verify_batch_single_msg( msg, msg_sz, signatures, pubkeys, shas, signature_cnt );
1561 0 : if( FD_UNLIKELY( res != FD_ED25519_SUCCESS ) ) {
1562 0 : return FD_RUNTIME_TXN_ERR_SIGNATURE_FAILURE;
1563 0 : }
1564 :
1565 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
1566 0 : }
1567 :
1568 : int
1569 0 : fd_execute_txn( fd_exec_txn_ctx_t * txn_ctx ) {
1570 :
1571 0 : bool dump_insn = txn_ctx->capture_ctx && txn_ctx->slot >= txn_ctx->capture_ctx->dump_proto_start_slot && txn_ctx->capture_ctx->dump_instr_to_pb;
1572 :
1573 : /* Initialize log collection */
1574 0 : fd_log_collector_init( &txn_ctx->log_collector, txn_ctx->enable_exec_recording );
1575 :
1576 0 : for( ushort i=0; i<TXN( &txn_ctx->txn )->instr_cnt; i++ ) {
1577 0 : txn_ctx->current_instr_idx = i;
1578 0 : if( FD_UNLIKELY( dump_insn ) ) {
1579 : // Capture the input and convert it into a Protobuf message
1580 0 : fd_dump_instr_to_protobuf( txn_ctx, &txn_ctx->instr_infos[i], i );
1581 0 : }
1582 :
1583 0 : int instr_exec_result = fd_execute_instr( txn_ctx, &txn_ctx->instr_infos[i] );
1584 0 : if( FD_UNLIKELY( instr_exec_result!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
1585 0 : if ( txn_ctx->instr_err_idx==INT_MAX ) {
1586 0 : txn_ctx->instr_err_idx = i;
1587 0 : }
1588 0 : return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR;
1589 0 : }
1590 0 : }
1591 :
1592 : /* TODO: This function needs to be split out of fd_execute_txn and be placed
1593 : into the replay tile once it is implemented. */
1594 0 : int err = fd_executor_txn_check( txn_ctx );
1595 0 : if( FD_UNLIKELY( err!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
1596 0 : FD_LOG_DEBUG(( "fd_executor_txn_check failed (%d)", err ));
1597 0 : return err;
1598 0 : }
1599 0 : return 0;
1600 0 : }
1601 :
1602 : int
1603 0 : fd_executor_txn_check( fd_exec_txn_ctx_t * txn_ctx ) {
1604 0 : fd_rent_t const * rent = fd_bank_rent_query( txn_ctx->bank );
1605 :
1606 0 : ulong starting_lamports_l = 0;
1607 0 : ulong starting_lamports_h = 0;
1608 :
1609 0 : ulong ending_lamports_l = 0;
1610 0 : ulong ending_lamports_h = 0;
1611 :
1612 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L63 */
1613 0 : for( ulong idx = 0; idx < txn_ctx->accounts_cnt; idx++ ) {
1614 0 : fd_txn_account_t * b = &txn_ctx->accounts[idx];
1615 :
1616 : // Was this account written to?
1617 : /* TODO: Clean this logic up... lots of redundant checks with our newer account loading model.
1618 : We should be using the rent transition checking logic instead, along with a small refactor
1619 : to keep check ordering consistent. */
1620 0 : if( fd_txn_account_get_meta( b )!=NULL ) {
1621 0 : fd_uwide_inc( &ending_lamports_h, &ending_lamports_l, ending_lamports_h, ending_lamports_l, fd_txn_account_get_lamports( b ) );
1622 :
1623 : /* Rent states are defined as followed:
1624 : - lamports == 0 -> Uninitialized
1625 : - 0 < lamports < rent_exempt_minimum -> RentPaying
1626 : - lamports >= rent_exempt_minimum -> RentExempt
1627 : In Agave, 'self' refers to our 'after' state. */
1628 0 : uchar after_uninitialized = fd_txn_account_get_lamports( b ) == 0;
1629 0 : uchar after_rent_exempt = fd_txn_account_get_lamports( b ) >= fd_rent_exempt_minimum_balance( rent, fd_txn_account_get_data_len( b ) );
1630 :
1631 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L96 */
1632 0 : if( FD_LIKELY( memcmp( b->pubkey->key, fd_sysvar_incinerator_id.key, sizeof(fd_pubkey_t) ) != 0 ) ) {
1633 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L44 */
1634 0 : if( after_uninitialized || after_rent_exempt ) {
1635 : // no-op
1636 0 : } else {
1637 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L45-L59 */
1638 0 : uchar before_uninitialized = b->starting_dlen == ULONG_MAX || b->starting_lamports == 0;
1639 0 : uchar before_rent_exempt = b->starting_dlen != ULONG_MAX && b->starting_lamports >= fd_rent_exempt_minimum_balance( rent, b->starting_dlen );
1640 :
1641 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L50 */
1642 0 : if( before_uninitialized || before_rent_exempt ) {
1643 0 : 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",
1644 0 : FD_BASE58_ENC_32_ALLOCA( b->pubkey->uc ),
1645 0 : fd_txn_account_get_data_len( b ),
1646 0 : b->starting_dlen,
1647 0 : fd_txn_account_get_lamports( b ),
1648 0 : b->starting_lamports,
1649 0 : fd_rent_exempt_minimum_balance( rent, fd_txn_account_get_data_len( b ) ),
1650 0 : fd_rent_exempt_minimum_balance( rent, b->starting_dlen ) ));
1651 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L104 */
1652 0 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
1653 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L56 */
1654 0 : } else if( (fd_txn_account_get_data_len( b ) == b->starting_dlen) && fd_txn_account_get_lamports( b ) <= b->starting_lamports ) {
1655 : // no-op
1656 0 : } else {
1657 0 : 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",
1658 0 : FD_BASE58_ENC_32_ALLOCA( b->pubkey->uc ),
1659 0 : fd_txn_account_get_data_len( b ),
1660 0 : b->starting_dlen,
1661 0 : fd_txn_account_get_lamports( b ),
1662 0 : b->starting_lamports,
1663 0 : fd_rent_exempt_minimum_balance( rent, fd_txn_account_get_data_len( b ) ),
1664 0 : fd_rent_exempt_minimum_balance( rent, b->starting_dlen ) ));
1665 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L104 */
1666 0 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
1667 0 : }
1668 0 : }
1669 0 : }
1670 :
1671 0 : if( b->starting_lamports != ULONG_MAX ) {
1672 0 : fd_uwide_inc( &starting_lamports_h, &starting_lamports_l, starting_lamports_h, starting_lamports_l, b->starting_lamports );
1673 0 : }
1674 0 : }
1675 0 : }
1676 :
1677 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/transaction_processor.rs#L839-L845 */
1678 0 : if( FD_UNLIKELY( ending_lamports_l!=starting_lamports_l || ending_lamports_h!=starting_lamports_h ) ) {
1679 0 : FD_LOG_DEBUG(( "Lamport sum mismatch: starting %lx%lx ending %lx%lx", starting_lamports_h, starting_lamports_l, ending_lamports_h, ending_lamports_l ));
1680 0 : return FD_RUNTIME_TXN_ERR_UNBALANCED_TRANSACTION;
1681 0 : }
1682 :
1683 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
1684 0 : }
1685 :
1686 : /* fd_executor_instr_strerror() returns the error message corresponding to err,
1687 : intended to be logged by log_collector, or an empty string if the error code
1688 : should be omitted in logs for whatever reason. Omitted examples are success,
1689 : fatal (placeholder just in firedancer), custom error.
1690 : See also fd_log_collector_program_failure(). */
1691 : FD_FN_CONST char const *
1692 0 : fd_executor_instr_strerror( int err ) {
1693 :
1694 0 : switch( err ) {
1695 0 : case FD_EXECUTOR_INSTR_SUCCESS : return ""; // not used
1696 0 : case FD_EXECUTOR_INSTR_ERR_FATAL : return ""; // not used
1697 0 : case FD_EXECUTOR_INSTR_ERR_GENERIC_ERR : return "generic instruction error";
1698 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_ARG : return "invalid program argument";
1699 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA : return "invalid instruction data";
1700 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA : return "invalid account data for instruction";
1701 0 : case FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL : return "account data too small for instruction";
1702 0 : case FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS : return "insufficient funds for instruction";
1703 0 : case FD_EXECUTOR_INSTR_ERR_INCORRECT_PROGRAM_ID : return "incorrect program id for instruction";
1704 0 : case FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE : return "missing required signature for instruction";
1705 0 : case FD_EXECUTOR_INSTR_ERR_ACC_ALREADY_INITIALIZED : return "instruction requires an uninitialized account";
1706 0 : case FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT : return "instruction requires an initialized account";
1707 0 : case FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR : return "sum of account balances before and after instruction do not match";
1708 0 : case FD_EXECUTOR_INSTR_ERR_MODIFIED_PROGRAM_ID : return "instruction illegally modified the program id of an account";
1709 0 : case FD_EXECUTOR_INSTR_ERR_EXTERNAL_ACCOUNT_LAMPORT_SPEND : return "instruction spent from the balance of an account it does not own";
1710 0 : case FD_EXECUTOR_INSTR_ERR_EXTERNAL_DATA_MODIFIED : return "instruction modified data of an account it does not own";
1711 0 : case FD_EXECUTOR_INSTR_ERR_READONLY_LAMPORT_CHANGE : return "instruction changed the balance of a read-only account";
1712 0 : case FD_EXECUTOR_INSTR_ERR_READONLY_DATA_MODIFIED : return "instruction modified data of a read-only account";
1713 0 : case FD_EXECUTOR_INSTR_ERR_DUPLICATE_ACCOUNT_IDX : return "instruction contains duplicate accounts";
1714 0 : case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_MODIFIED : return "instruction changed executable bit of an account";
1715 0 : case FD_EXECUTOR_INSTR_ERR_RENT_EPOCH_MODIFIED : return "instruction modified rent epoch of an account";
1716 0 : case FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS : return "insufficient account keys for instruction";
1717 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";
1718 0 : case FD_EXECUTOR_INSTR_ERR_ACC_NOT_EXECUTABLE : return "instruction expected an executable account";
1719 0 : case FD_EXECUTOR_INSTR_ERR_ACC_BORROW_FAILED : return "instruction tries to borrow reference for an account which is already borrowed";
1720 0 : case FD_EXECUTOR_INSTR_ERR_ACC_BORROW_OUTSTANDING : return "instruction left account with an outstanding borrowed reference";
1721 0 : case FD_EXECUTOR_INSTR_ERR_DUPLICATE_ACCOUNT_OUT_OF_SYNC : return "instruction modifications of multiply-passed account differ";
1722 0 : case FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR : return ""; // custom handling via txn_ctx->custom_err
1723 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_ERR : return "program returned invalid error code";
1724 0 : case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_DATA_MODIFIED : return "instruction changed executable accounts data";
1725 0 : case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_LAMPORT_CHANGE : return "instruction changed the balance of an executable account";
1726 0 : case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_ACCOUNT_NOT_RENT_EXEMPT : return "executable accounts must be rent exempt";
1727 0 : case FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID : return "Unsupported program id";
1728 0 : case FD_EXECUTOR_INSTR_ERR_CALL_DEPTH : return "Cross-program invocation call depth too deep";
1729 0 : case FD_EXECUTOR_INSTR_ERR_MISSING_ACC : return "An account required by the instruction is missing";
1730 0 : case FD_EXECUTOR_INSTR_ERR_REENTRANCY_NOT_ALLOWED : return "Cross-program invocation reentrancy not allowed for this instruction";
1731 0 : case FD_EXECUTOR_INSTR_ERR_MAX_SEED_LENGTH_EXCEEDED : return "Length of the seed is too long for address generation";
1732 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_SEEDS : return "Provided seeds do not result in a valid address";
1733 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC : return "Failed to reallocate account data";
1734 0 : case FD_EXECUTOR_INSTR_ERR_COMPUTE_BUDGET_EXCEEDED : return "Computational budget exceeded";
1735 0 : case FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION : return "Cross-program invocation with unauthorized signer or writable account";
1736 0 : case FD_EXECUTOR_INSTR_ERR_PROGRAM_ENVIRONMENT_SETUP_FAILURE : return "Failed to create program execution environment";
1737 0 : case FD_EXECUTOR_INSTR_ERR_PROGRAM_FAILED_TO_COMPLETE : return "Program failed to complete";
1738 0 : case FD_EXECUTOR_INSTR_ERR_PROGRAM_FAILED_TO_COMPILE : return "Program failed to compile";
1739 0 : case FD_EXECUTOR_INSTR_ERR_ACC_IMMUTABLE : return "Account is immutable";
1740 0 : case FD_EXECUTOR_INSTR_ERR_INCORRECT_AUTHORITY : return "Incorrect authority provided";
1741 0 : case FD_EXECUTOR_INSTR_ERR_BORSH_IO_ERROR : return "Failed to serialize or deserialize account data"; // truncated
1742 0 : case FD_EXECUTOR_INSTR_ERR_ACC_NOT_RENT_EXEMPT : return "An account does not have enough lamports to be rent-exempt";
1743 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_ACC_OWNER : return "Invalid account owner";
1744 0 : case FD_EXECUTOR_INSTR_ERR_ARITHMETIC_OVERFLOW : return "Program arithmetic overflowed";
1745 0 : case FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR : return "Unsupported sysvar";
1746 0 : case FD_EXECUTOR_INSTR_ERR_ILLEGAL_OWNER : return "Provided owner is not allowed";
1747 0 : case FD_EXECUTOR_INSTR_ERR_MAX_ACCS_DATA_ALLOCS_EXCEEDED : return "Accounts data allocations exceeded the maximum allowed per transaction";
1748 0 : case FD_EXECUTOR_INSTR_ERR_MAX_ACCS_EXCEEDED : return "Max accounts exceeded";
1749 0 : case FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED : return "Max instruction trace length exceeded";
1750 0 : case FD_EXECUTOR_INSTR_ERR_BUILTINS_MUST_CONSUME_CUS : return "Builtin programs must consume compute units";
1751 0 : default: break;
1752 0 : }
1753 :
1754 0 : return "";
1755 0 : }
1756 :
1757 : // This is purely linker magic to force the inclusion of the yaml type walker so that it is
1758 : // available for debuggers
1759 : void
1760 0 : fd_debug_symbology(void) {
1761 0 : (void)fd_get_types_yaml();
1762 0 : }
|