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