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