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