Line data Source code
1 : #include "fd_executor.h"
2 : #include "fd_runtime.h"
3 : #include "fd_runtime_helpers.h"
4 : #include "fd_compute_budget_details.h"
5 :
6 : #include "tests/fd_dump_pb.h"
7 : #include "fd_system_ids.h"
8 : #include "program/fd_bpf_loader_program.h"
9 : #include "program/fd_compute_budget_program.h"
10 : #include "program/fd_precompiles.h"
11 : #include "program/fd_system_program.h"
12 : #include "program/fd_builtin_programs.h"
13 : #include "program/fd_vote_program.h"
14 : #include "program/fd_zk_elgamal_proof_program.h"
15 : #include "sysvar/fd_sysvar_instructions.h"
16 : #include "sysvar/fd_sysvar_rent.h"
17 :
18 : #include "../log_collector/fd_log_collector.h"
19 :
20 : #include "../../disco/pack/fd_pack_tip_prog_blacklist.h"
21 :
22 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/rent_state.rs#L5-L15 */
23 : struct fd_rent_state {
24 : uint discriminant;
25 : ulong lamports;
26 : ulong data_size;
27 : };
28 : typedef struct fd_rent_state fd_rent_state_t;
29 :
30 0 : #define FD_RENT_STATE_UNINITIALIZED (0U)
31 0 : #define FD_RENT_STATE_RENT_PAYING (1U)
32 801 : #define FD_RENT_STATE_RENT_EXEMPT (2U)
33 :
34 : #define MAP_PERFECT_NAME fd_native_program_fn_lookup_tbl
35 : #define MAP_PERFECT_LG_TBL_SZ 3
36 : #define MAP_PERFECT_T fd_native_prog_info_t
37 7788 : #define MAP_PERFECT_HASH_C 1069U
38 : #define MAP_PERFECT_KEY key.uc
39 : #define MAP_PERFECT_KEY_T fd_pubkey_t const *
40 : #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)
41 : #define MAP_PERFECT_COMPLEX_KEY 1
42 7788 : #define MAP_PERFECT_KEYS_EQUAL(k1,k2) (!memcmp( (k1), (k2), 32UL ))
43 :
44 7788 : #define PERFECT_HASH( u ) (((MAP_PERFECT_HASH_C*(u))>>29)&0x7U)
45 :
46 : #define MAP_PERFECT_HASH_PP( a00,a01,a02,a03,a04,a05,a06,a07,a08,a09,a10,a11,a12,a13,a14,a15, \
47 : a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31) \
48 : PERFECT_HASH( (a08 | (a09<<8) | (a10<<16) | (a11<<24)) )
49 7788 : #define MAP_PERFECT_HASH_R( ptr ) PERFECT_HASH( fd_uint_load_4( (uchar const *)ptr + 8UL ) )
50 :
51 : #define MAP_PERFECT_0 ( VOTE_PROG_ID ), .fn = fd_vote_program_execute, .is_bpf_loader = 0, .feature_enable_offset = ULONG_MAX
52 : #define MAP_PERFECT_1 ( SYS_PROG_ID ), .fn = fd_system_program_execute, .is_bpf_loader = 0, .feature_enable_offset = ULONG_MAX
53 : #define MAP_PERFECT_2 ( COMPUTE_BUDGET_PROG_ID ), .fn = fd_compute_budget_program_execute, .is_bpf_loader = 0, .feature_enable_offset = ULONG_MAX
54 : #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
55 : #define MAP_PERFECT_4 ( BPF_LOADER_1_PROG_ID ), .fn = fd_bpf_loader_program_execute, .is_bpf_loader = 1, .feature_enable_offset = ULONG_MAX
56 : #define MAP_PERFECT_5 ( BPF_LOADER_2_PROG_ID ), .fn = fd_bpf_loader_program_execute, .is_bpf_loader = 1, .feature_enable_offset = ULONG_MAX
57 : #define MAP_PERFECT_6 ( BPF_UPGRADEABLE_PROG_ID ), .fn = fd_bpf_loader_program_execute, .is_bpf_loader = 1, .feature_enable_offset = ULONG_MAX
58 : #define MAP_PERFECT_7 ( LOADER_V4_PROG_ID ), .fn = NULL, .is_bpf_loader = 1, .feature_enable_offset = offsetof( fd_features_t, enable_loader_v4 )
59 :
60 : #include "../../util/tmpl/fd_map_perfect.c"
61 : #undef PERFECT_HASH
62 :
63 : uchar
64 2526 : fd_executor_pubkey_is_bpf_loader( fd_pubkey_t const * pubkey ) {
65 2526 : fd_native_prog_info_t const null_function = {0};
66 2526 : return fd_native_program_fn_lookup_tbl_query( pubkey, &null_function )->is_bpf_loader;
67 2526 : }
68 :
69 : uchar
70 : fd_executor_program_is_active( fd_bank_t * bank,
71 2631 : fd_pubkey_t const * pubkey ) {
72 2631 : fd_native_prog_info_t const null_function = {0};
73 2631 : ulong feature_offset = fd_native_program_fn_lookup_tbl_query( pubkey, &null_function )->feature_enable_offset;
74 :
75 2631 : return feature_offset==ULONG_MAX ||
76 2631 : FD_FEATURE_ACTIVE_BANK_OFFSET( bank, feature_offset );
77 2631 : }
78 :
79 : /* fd_executor_lookup_native_program returns the appropriate instruction
80 : processor for the given native program ID. Returns NULL if given ID
81 : is not a recognized native program.
82 :
83 : https://github.com/anza-xyz/agave/blob/v2.2.6/program-runtime/src/invoke_context.rs#L520-L544 */
84 :
85 : static int
86 : fd_executor_lookup_native_program( fd_acc_t const * meta,
87 : fd_bank_t * bank,
88 : fd_exec_instr_fn_t * native_prog_fn,
89 2631 : uchar * is_precompile ) {
90 : /* First lookup to see if the program key is a precompile */
91 2631 : *is_precompile = 0;
92 2631 : *native_prog_fn = fd_executor_lookup_native_precompile_program( (fd_pubkey_t*)meta->pubkey );
93 2631 : if( FD_UNLIKELY( *native_prog_fn!=NULL ) ) {
94 0 : *is_precompile = 1;
95 0 : return 0;
96 0 : }
97 :
98 : /* Native programs should be owned by the native loader... This will
99 : not be the case though once core programs are migrated to BPF. */
100 2631 : int is_native_program = !memcmp( meta->owner, fd_solana_native_loader_id.key, 32UL );
101 :
102 2631 : if( FD_UNLIKELY( !is_native_program ) ) {
103 2490 : if( FD_UNLIKELY( !fd_executor_pubkey_is_bpf_loader( (fd_pubkey_t*)meta->owner ) ) ) {
104 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
105 0 : }
106 2490 : }
107 :
108 2631 : fd_pubkey_t const * lookup_pubkey = is_native_program ? (fd_pubkey_t*)meta->pubkey : (fd_pubkey_t*)meta->owner;
109 :
110 : /* Migrated programs must be executed via the corresponding BPF
111 : loader(s), not natively. This check is performed at the transaction
112 : level, but we re-check to please the instruction level (and below)
113 : fuzzers. */
114 2631 : uchar has_migrated;
115 2631 : if( FD_UNLIKELY( fd_is_migrating_builtin_program( bank, lookup_pubkey, &has_migrated ) && has_migrated ) ) {
116 0 : *native_prog_fn = NULL;
117 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
118 0 : }
119 :
120 : /* We perform feature gate checks here to emulate the absence of
121 : a native program in Agave's ProgramCache when the program's feature
122 : gate is not activated.
123 : https://github.com/anza-xyz/agave/blob/v3.0.3/program-runtime/src/invoke_context.rs#L546-L549 */
124 :
125 2631 : if( FD_UNLIKELY( !fd_executor_program_is_active( bank, lookup_pubkey ) ) ) {
126 0 : *native_prog_fn = NULL;
127 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
128 0 : }
129 :
130 2631 : fd_native_prog_info_t const null_function = {0};
131 2631 : *native_prog_fn = fd_native_program_fn_lookup_tbl_query( lookup_pubkey, &null_function )->fn;
132 2631 : return 0;
133 2631 : }
134 :
135 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L117-L136 */
136 : static uchar
137 : fd_executor_rent_transition_allowed( fd_rent_state_t const * pre_rent_state,
138 267 : fd_rent_state_t const * post_rent_state ) {
139 267 : switch( post_rent_state->discriminant ) {
140 0 : case FD_RENT_STATE_UNINITIALIZED:
141 267 : case FD_RENT_STATE_RENT_EXEMPT: {
142 267 : return 1;
143 0 : }
144 0 : case FD_RENT_STATE_RENT_PAYING: {
145 0 : switch( pre_rent_state->discriminant ) {
146 0 : case FD_RENT_STATE_UNINITIALIZED:
147 0 : case FD_RENT_STATE_RENT_EXEMPT: {
148 0 : return 0;
149 0 : }
150 0 : case FD_RENT_STATE_RENT_PAYING: {
151 0 : return post_rent_state->data_size==pre_rent_state->data_size &&
152 0 : post_rent_state->lamports<=pre_rent_state->lamports;
153 0 : }
154 0 : default: {
155 0 : FD_LOG_CRIT(( "unexpected pre-rent state discriminant %u", pre_rent_state->discriminant ));
156 0 : }
157 0 : }
158 0 : }
159 0 : default: {
160 0 : FD_LOG_CRIT(( "unexpected post-rent state discriminant %u", post_rent_state->discriminant ));
161 0 : }
162 267 : }
163 267 : }
164 :
165 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L61-L77 */
166 : static int
167 : fd_executor_check_rent_state_with_account( fd_pubkey_t const * pubkey,
168 : fd_rent_state_t const * pre_rent_state,
169 267 : fd_rent_state_t const * post_rent_state ) {
170 267 : if( FD_UNLIKELY( memcmp( pubkey, fd_sysvar_incinerator_id.key, sizeof(fd_pubkey_t) ) &&
171 267 : !fd_executor_rent_transition_allowed( pre_rent_state, post_rent_state ) ) ) {
172 0 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
173 0 : }
174 267 : return FD_RUNTIME_EXECUTE_SUCCESS;
175 267 : }
176 :
177 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L87-L101 */
178 : fd_rent_state_t
179 : fd_executor_get_account_rent_state( fd_acc_t const * acc,
180 534 : fd_rent_t const * rent ) {
181 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L88-L89 */
182 534 : if( FD_UNLIKELY( !acc->lamports ) ) return (fd_rent_state_t){ .discriminant = FD_RENT_STATE_UNINITIALIZED };
183 :
184 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L90-L94 */
185 534 : if( FD_UNLIKELY( acc->lamports>=fd_rent_exempt_minimum_balance( rent, acc->data_len ) ) ) {
186 534 : return (fd_rent_state_t){ .discriminant = FD_RENT_STATE_RENT_EXEMPT };
187 534 : }
188 :
189 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm-rent-collector/src/svm_rent_collector.rs#L95-L99 */
190 0 : return (fd_rent_state_t){
191 0 : .discriminant = FD_RENT_STATE_RENT_PAYING,
192 0 : .lamports = acc->lamports,
193 0 : .data_size = acc->data_len,
194 0 : };
195 534 : }
196 :
197 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L293-L342 */
198 : static int
199 : fd_validate_fee_payer( fd_acc_t * acc,
200 : fd_rent_t const * rent,
201 267 : ulong fee ) {
202 :
203 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L301-L304 */
204 267 : if( FD_UNLIKELY( !acc->lamports ) ) return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
205 :
206 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L305-L308 */
207 267 : int system_account_kind = fd_get_system_account_kind( acc );
208 267 : if( FD_UNLIKELY( system_account_kind==FD_SYSTEM_PROGRAM_NONCE_ACCOUNT_KIND_UNKNOWN ) ) {
209 0 : return FD_RUNTIME_TXN_ERR_INVALID_ACCOUNT_FOR_FEE;
210 0 : }
211 :
212 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L309-L318 */
213 267 : ulong min_balance = 0UL;
214 267 : if( FD_UNLIKELY( system_account_kind==FD_SYSTEM_PROGRAM_NONCE_ACCOUNT_KIND_NONCE ) ) {
215 3 : min_balance = fd_rent_exempt_minimum_balance( rent, FD_SYSTEM_PROGRAM_NONCE_DLEN );
216 3 : }
217 :
218 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L320-L327 */
219 267 : if( FD_UNLIKELY( min_balance>acc->lamports || fee>acc->lamports-min_balance ) ) {
220 0 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_FEE;
221 0 : }
222 :
223 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L329 */
224 267 : fd_rent_state_t payer_pre_rent_state = fd_executor_get_account_rent_state( acc, rent );
225 :
226 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L330-L332 */
227 267 : int err = fd_account_meta_checked_sub_lamports( acc, fee );
228 267 : if( FD_UNLIKELY( err!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
229 0 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_FEE;
230 0 : }
231 :
232 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L334 */
233 267 : fd_rent_state_t payer_post_rent_state = fd_executor_get_account_rent_state( acc, rent );
234 :
235 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/account_loader.rs#L335-L342 */
236 267 : return fd_executor_check_rent_state_with_account( (fd_pubkey_t*)acc->pubkey, &payer_pre_rent_state, &payer_post_rent_state );
237 267 : }
238 :
239 : static int
240 : fd_executor_check_status_cache( fd_txncache_t * status_cache,
241 : fd_bank_t * bank,
242 : fd_txn_in_t const * txn_in,
243 267 : fd_txn_out_t * txn_out ) {
244 267 : if( FD_UNLIKELY( !status_cache ) ) {
245 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
246 0 : }
247 :
248 267 : if( FD_UNLIKELY( txn_out->accounts.nonce_idx_in_txn!=ULONG_MAX ) ) {
249 : /* In Agave, durable nonce transactions are inserted to the status
250 : cache the same as any others, but this is only to serve RPC
251 : requests, they do not need to be in there for correctness as the
252 : nonce mechanism itself prevents double spend. We skip this logic
253 : entirely to simplify and improve performance of the txn cache. */
254 15 : return FD_RUNTIME_EXECUTE_SUCCESS;
255 15 : }
256 :
257 : /* Compute the blake3 hash of the transaction message
258 : https://github.com/anza-xyz/agave/blob/v2.1.7/sdk/program/src/message/versions/mod.rs#L159-L167 */
259 252 : fd_blake3_t b3[1];
260 252 : fd_blake3_init( b3 );
261 252 : fd_blake3_append( b3, "solana-tx-message-v1", 20UL );
262 252 : 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 ) );
263 252 : fd_blake3_fini( b3, &txn_out->details.blake_txn_msg_hash );
264 :
265 252 : fd_hash_t * blockhash = (fd_hash_t *)((uchar *)txn_in->txn->payload + TXN( txn_in->txn )->recent_blockhash_off);
266 252 : int found = fd_txncache_query( status_cache, bank->txncache_fork_id, blockhash->uc, txn_out->details.blake_txn_msg_hash.uc );
267 252 : if( FD_UNLIKELY( found ) ) return FD_RUNTIME_TXN_ERR_ALREADY_PROCESSED;
268 :
269 252 : if( FD_UNLIKELY( txn_in->bundle.is_bundle ) ) {
270 : /* It is possible for users to send transactions in a bundle with
271 : identical transaction message hashes. The message hashes are
272 : only added to the txncache when transactions are committed. So
273 : message hashes must be checked against the txncache AND previous
274 : transactions in the bundle. */
275 174 : for( ulong i=0UL; i<txn_in->bundle.prev_txn_cnt; i++ ) {
276 78 : fd_txn_out_t const * prev_txn_out = txn_in->bundle.prev_txn_outs[i];
277 78 : if( FD_UNLIKELY( !memcmp( &prev_txn_out->details.blake_txn_msg_hash, &txn_out->details.blake_txn_msg_hash, sizeof(fd_hash_t) ) ) ) {
278 0 : return FD_RUNTIME_TXN_ERR_ALREADY_PROCESSED;
279 0 : }
280 78 : }
281 96 : }
282 :
283 252 : return FD_RUNTIME_EXECUTE_SUCCESS;
284 252 : }
285 :
286 : /* https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/bank/check_transactions.rs#L71-L136 */
287 : static int
288 : fd_executor_check_transaction_age_and_compute_budget_limits( fd_bank_t * bank,
289 : fd_txn_in_t const * txn_in,
290 270 : fd_txn_out_t * txn_out ) {
291 :
292 : /* https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/bank/check_transactions.rs#L94-L123 */
293 270 : int err = fd_sanitize_compute_unit_limits( txn_out );
294 270 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
295 0 : return err;
296 0 : }
297 :
298 : /* https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/bank/check_transactions.rs#L124-L131 */
299 270 : err = fd_check_transaction_age( bank, txn_in, txn_out );
300 270 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
301 3 : return err;
302 3 : }
303 :
304 267 : return FD_RUNTIME_EXECUTE_SUCCESS;
305 270 : }
306 :
307 : /* https://github.com/anza-xyz/agave/blob/v2.0.9/runtime/src/bank.rs#L3239-L3251 */
308 : static inline ulong
309 273 : get_transaction_account_lock_limit( void ) {
310 273 : return 64UL;
311 273 : }
312 :
313 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/check_transactions.rs#L61-L75 */
314 : int
315 : fd_executor_check_transactions( fd_runtime_t * runtime,
316 : fd_bank_t * bank,
317 : fd_txn_in_t const * txn_in,
318 270 : fd_txn_out_t * txn_out ) {
319 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/check_transactions.rs#L68-L73 */
320 270 : int err = fd_executor_check_transaction_age_and_compute_budget_limits( bank, txn_in, txn_out );
321 270 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
322 3 : return err;
323 3 : }
324 :
325 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank/check_transactions.rs#L74 */
326 267 : err = fd_executor_check_status_cache( runtime->status_cache, bank, txn_in, txn_out );
327 267 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
328 0 : return err;
329 0 : }
330 :
331 267 : return FD_RUNTIME_EXECUTE_SUCCESS;
332 267 : }
333 :
334 : /* `verify_transaction()` is the first function called in the
335 : transaction execution pipeline. It is responsible for deserializing
336 : the transaction, verifying the message hash (sigverify), verifying
337 : the precompiles, and processing compute budget instructions. We
338 : leave sigverify out for now to easily bypass this function's
339 : checks for fuzzing.
340 :
341 : TODO: Maybe support adding sigverify in here, and toggling it
342 : on/off with a flag.
343 :
344 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank.rs#L5725-L5753 */
345 : int
346 : fd_executor_verify_transaction( fd_bank_t const * bank,
347 : fd_txn_in_t const * txn_in,
348 273 : fd_txn_out_t * txn_out ) {
349 273 : int err = FD_RUNTIME_EXECUTE_SUCCESS;
350 :
351 : /* SIMD-0406: enforce limit on number of instruction accounts.
352 :
353 : TODO: when limit_instruction_accounts is activated everywhere,
354 : remove this and make the transaction parser check stricter.
355 :
356 : https://github.com/anza-xyz/agave/blob/v4.0.0-alpha.0/runtime-transaction/src/runtime_transaction/sdk_transactions.rs#L93-L99 */
357 273 : if( FD_UNLIKELY( FD_FEATURE_ACTIVE_BANK( bank, limit_instruction_accounts ) ) ) {
358 6 : fd_txn_t const * txn = TXN( txn_in->txn );
359 9 : for( ushort i=0; i<txn->instr_cnt; i++ ) {
360 6 : if( FD_UNLIKELY( txn->instr[i].acct_cnt > FD_BPF_INSTR_ACCT_MAX ) ) {
361 3 : return FD_RUNTIME_TXN_ERR_SANITIZE_FAILURE;
362 3 : }
363 6 : }
364 6 : }
365 :
366 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L566-L569 */
367 270 : err = fd_executor_compute_budget_program_execute_instructions( bank, txn_in, txn_out );
368 270 : if( FD_UNLIKELY( err ) ) return err;
369 :
370 270 : return FD_RUNTIME_EXECUTE_SUCCESS;
371 270 : }
372 :
373 : /* This function contains special casing for loading and collecting rent from
374 : each transaction account. The logic is as follows:
375 : 1. If the account is the instructions sysvar, then load in the compiled
376 : instructions from the transactions into the sysvar's data.
377 : 2. If the account is a fee payer, then it is already loaded.
378 : 3. Otherwise load in the account from the accounts DB. If the account is
379 : writable and exists, try to collect rent from it.
380 :
381 : Returns the loaded transaction account size, which is the value that
382 : must be used when accumulating and checking against the
383 : transactions's loaded account data size limit.
384 :
385 : Agave relies on this function to actually load accounts from their
386 : accounts db. However, since our accounts model is slightly different,
387 : our account loading logic is handled earlier in the transaction
388 : execution pipeline within `fd_executor_setup_accounts_for_txn()`.
389 : Therefore, the name of this function is slightly misleading - we
390 : don't actually load accounts here, but we still need to collect
391 : rent from writable accounts and accumulate the transaction's
392 : total loaded account size.
393 :
394 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L199-L228 */
395 : static ulong
396 : load_transaction_account( fd_bank_t * bank,
397 : fd_txn_in_t const * txn_in,
398 : fd_txn_out_t * txn_out,
399 : fd_pubkey_t const * pubkey,
400 : fd_acc_t * acc,
401 : uchar unknown_acc,
402 1125 : ulong txn_idx ) {
403 :
404 : /* Handling the sysvar instructions account explictly.
405 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L817-L824 */
406 1125 : if( FD_UNLIKELY( !memcmp( pubkey, fd_sysvar_instructions_id.key, sizeof(fd_pubkey_t) ) ) ) {
407 : /* The sysvar instructions account cannot be "loaded" since it's
408 : constructed by the SVM and modified within each transaction's
409 : instruction execution only, so it incurs a loaded size cost
410 : of 0. */
411 0 : fd_sysvar_instructions_serialize_account( txn_in, txn_out, txn_idx );
412 0 : return 0UL;
413 0 : }
414 :
415 : /* This next block calls `account_loader::load_transaction_account()`
416 : which loads the account from the accounts db. If the account exists
417 : and is writable, collect rent from it.
418 :
419 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L828-L835 */
420 1125 : if( FD_LIKELY( !unknown_acc ) ) {
421 : /* SIMD-0186 introduces a base account size of 64 bytes for all
422 : transaction counts that exist prior to the transaction's
423 : execution.
424 :
425 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L204-L208 */
426 1035 : ulong base_account_size = FD_FEATURE_ACTIVE_BANK( bank, formalize_loaded_transaction_data_size ) ? FD_TRANSACTION_ACCOUNT_BASE_SIZE : 0UL;
427 :
428 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L828-L835 */
429 1035 : return fd_ulong_sat_add( base_account_size, acc->data_len );
430 1035 : }
431 :
432 : /* The rest of this function is a no-op for us since we already set up
433 : the transaction accounts for unknown accounts within
434 : `fd_executor_setup_accounts_for_txn()`. We also do not need to
435 : add a base cost to the loaded account size because the SIMD
436 : states that accounts that do not exist prior to the transaction's
437 : execution should not incur a loaded size cost.
438 : https://github.com/anza-xyz/agave/blob/v2.2.0/svm/src/account_loader.rs#L566-L577 */
439 90 : return 0UL;
440 1125 : }
441 :
442 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L494-L515 */
443 : static int
444 : fd_increase_calculated_data_size( fd_txn_out_t * txn_out,
445 1695 : ulong data_size_delta ) {
446 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L500-L503 */
447 1695 : if( FD_UNLIKELY( data_size_delta>UINT_MAX ) ) {
448 0 : return FD_RUNTIME_TXN_ERR_MAX_LOADED_ACCOUNTS_DATA_SIZE_EXCEEDED;
449 0 : }
450 :
451 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L505-L507 */
452 1695 : txn_out->details.loaded_accounts_data_size = fd_ulong_sat_add( txn_out->details.loaded_accounts_data_size, data_size_delta );
453 :
454 1695 : if( FD_UNLIKELY( txn_out->details.loaded_accounts_data_size>txn_out->details.compute_budget.loaded_accounts_data_size_limit ) ) {
455 9 : return FD_RUNTIME_TXN_ERR_MAX_LOADED_ACCOUNTS_DATA_SIZE_EXCEEDED;
456 9 : }
457 :
458 1686 : return FD_RUNTIME_EXECUTE_SUCCESS;
459 1695 : }
460 :
461 : /* This function is represented as a closure in Agave.
462 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L578-L640 */
463 : static int
464 : fd_collect_loaded_account( fd_txn_out_t * txn_out,
465 : fd_acc_t const * acc,
466 : ulong loaded_acc_size,
467 : fd_pubkey_t * additional_loaded_account_keys,
468 1392 : ulong * additional_loaded_account_keys_cnt ) {
469 :
470 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L586-L590 */
471 1392 : int err = fd_increase_calculated_data_size( txn_out, loaded_acc_size );
472 1392 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
473 9 : return err;
474 9 : }
475 :
476 : /* The remainder of this function is a deep-nested set of if
477 : statements. I've inverted the logic to make it easier to read.
478 : The purpose of the following code is to ensure that loader v3
479 : programdata accounts are accounted for exactly once in the account
480 : loading logic.
481 :
482 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L611 */
483 1383 : if( FD_LIKELY( memcmp( acc->owner, fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) ) ) {
484 1311 : return FD_RUNTIME_EXECUTE_SUCCESS;
485 1311 : }
486 :
487 : /* Try to read the program state
488 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L612-L634 */
489 72 : fd_bpf_state_t loader_state[1];
490 72 : err = fd_bpf_loader_program_get_state( acc, loader_state );
491 72 : if( FD_UNLIKELY( err!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
492 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
493 0 : }
494 :
495 : /* Make sure the account is a v3 program */
496 72 : if( loader_state->discriminant!=FD_BPF_STATE_PROGRAM ) {
497 24 : return FD_RUNTIME_EXECUTE_SUCCESS;
498 24 : }
499 :
500 : /* Iterate through the account keys and make sure the programdata
501 : account is not present so it doesn't get loaded twice.
502 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L617 */
503 141 : for( ushort i=0; i<txn_out->accounts.cnt; i++ ) {
504 105 : if( FD_UNLIKELY( !memcmp( &txn_out->accounts.keys[i], &loader_state->inner.program.programdata_address, sizeof(fd_pubkey_t) ) ) ) {
505 12 : return FD_RUNTIME_EXECUTE_SUCCESS;
506 12 : }
507 105 : }
508 :
509 : /* Check that the programdata account has not been already counted
510 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L618 */
511 36 : for( ushort i=0; i<*additional_loaded_account_keys_cnt; i++ ) {
512 0 : if( FD_UNLIKELY( !memcmp( &additional_loaded_account_keys[i], &loader_state->inner.program.programdata_address, sizeof(fd_pubkey_t) ) ) ) {
513 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
514 0 : }
515 0 : }
516 :
517 : /* Programdata account size check. Agave counts the current-fork
518 : size, which the parent-fork executable[] copy can lag, so prefer
519 : executable_cur_len when the probe reported one. Programdata
520 : deployed this slot has no executable[] copy at all; its size is
521 : in the skipped list. */
522 36 : fd_acc_t const * programdata_ref = NULL;
523 36 : ushort pd_idx = USHORT_MAX;
524 36 : for( ushort i=0; i<txn_out->accounts.executable_cnt; i++ ) {
525 30 : fd_acc_t const * exe = txn_out->accounts.executable[ i ];
526 30 : if( !memcmp( exe->pubkey, &loader_state->inner.program.programdata_address, 32UL ) ) {
527 30 : programdata_ref = exe;
528 30 : pd_idx = i;
529 30 : break;
530 30 : }
531 30 : }
532 36 : ulong programdata_sz;
533 36 : if( FD_LIKELY( programdata_ref ) ) {
534 30 : if( FD_UNLIKELY( !programdata_ref->lamports ) ) return FD_RUNTIME_EXECUTE_SUCCESS;
535 30 : ulong cur = txn_out->accounts.executable_cur_len[ pd_idx ];
536 30 : programdata_sz = ( cur!=ULONG_MAX ) ? cur : programdata_ref->data_len;
537 30 : } else {
538 6 : programdata_sz = 0UL;
539 6 : for( ushort i=0; i<txn_out->accounts.executable_skipped_cnt; i++ ) {
540 6 : if( !memcmp( txn_out->accounts.executable_skipped_key[ i ].uc, &loader_state->inner.program.programdata_address, 32UL ) ) {
541 6 : programdata_sz = txn_out->accounts.executable_skipped_len[ i ];
542 6 : break;
543 6 : }
544 6 : }
545 6 : if( FD_UNLIKELY( !programdata_sz ) ) return FD_RUNTIME_EXECUTE_SUCCESS;
546 6 : }
547 :
548 : /* Try to accumulate the programdata's data size
549 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L625-L630 */
550 36 : ulong programdata_size_delta = fd_ulong_sat_add( FD_TRANSACTION_ACCOUNT_BASE_SIZE, programdata_sz );
551 36 : err = fd_increase_calculated_data_size( txn_out, programdata_size_delta );
552 36 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
553 0 : return err;
554 0 : }
555 :
556 : /* Add the programdata account to the list of loaded programdata accounts
557 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L631 */
558 36 : fd_memcpy(
559 36 : &additional_loaded_account_keys[(*additional_loaded_account_keys_cnt)++],
560 36 : &loader_state->inner.program.programdata_address,
561 36 : sizeof(fd_pubkey_t) );
562 :
563 36 : return FD_RUNTIME_EXECUTE_SUCCESS;
564 36 : }
565 :
566 : /* Simplified transaction loading logic for SIMD-0186 which does the
567 : following:
568 : - Calculates the loaded data size for each address lookup table
569 : - Calculates the loaded data size for each transaction account
570 : - Calculates the loaded data size for each v3 programdata account
571 : not directly referenced in the transaction accounts
572 : - Collects rent from all referenced transaction accounts (excluding
573 : the fee payer)
574 : - Validates that each program invoked in a top-level instruction
575 : exists, is executable, and is owned by either the native loader
576 : or a bpf loader
577 :
578 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L550-L689
579 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L518-L548 */
580 : int
581 : fd_executor_load_transaction_accounts( fd_bank_t * bank,
582 : fd_txn_in_t const * txn_in,
583 267 : fd_txn_out_t * txn_out ) {
584 : /* Programdata accounts that are loaded by this transaction.
585 : We keep track of these to ensure they are not counted twice.
586 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L559 */
587 267 : fd_pubkey_t additional_loaded_account_keys[ FD_TXN_ACCT_ADDR_MAX ] = { 0 };
588 267 : ulong additional_loaded_account_keys_cnt = 0UL;
589 :
590 : /* Charge a base fee for each address lookup table.
591 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L570-L576 */
592 267 : ulong aluts_size = fd_ulong_sat_mul( TXN( txn_in->txn )->addr_table_lookup_cnt,
593 267 : FD_ADDRESS_LOOKUP_TABLE_BASE_SIZE );
594 267 : int err = fd_increase_calculated_data_size( txn_out, aluts_size );
595 267 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
596 0 : return err;
597 0 : }
598 :
599 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L642-L660 */
600 1650 : for( ushort i=0; i<txn_out->accounts.cnt; i++ ) {
601 1392 : fd_acc_t * acc = txn_out->accounts.account[ i ];
602 :
603 1392 : uchar unknown_acc = !!( !fd_runtime_get_account_at_index( txn_in, txn_out, i, fd_runtime_account_check_exists ) ||
604 1392 : acc->lamports==0UL);
605 :
606 : /* Collect the fee payer account separately (since it was already)
607 : loaded during fee payer validation.
608 :
609 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L644-L648 */
610 1392 : if( FD_UNLIKELY( i==FD_FEE_PAYER_TXN_IDX ) ) {
611 267 : ulong loaded_acc_size = fd_ulong_sat_add( FD_TRANSACTION_ACCOUNT_BASE_SIZE,
612 267 : acc->data_len );
613 267 : int err = fd_collect_loaded_account(
614 267 : txn_out,
615 267 : acc,
616 267 : loaded_acc_size,
617 267 : additional_loaded_account_keys,
618 267 : &additional_loaded_account_keys_cnt );
619 267 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
620 0 : return err;
621 0 : }
622 267 : continue;
623 267 : }
624 :
625 : /* Load and collect any remaining accounts
626 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L652-L659 */
627 1125 : ulong loaded_acc_size = load_transaction_account( bank, txn_in, txn_out, &txn_out->accounts.keys[i], acc, unknown_acc, i );
628 1125 : int err = fd_collect_loaded_account(
629 1125 : txn_out,
630 1125 : acc,
631 1125 : loaded_acc_size,
632 1125 : additional_loaded_account_keys,
633 1125 : &additional_loaded_account_keys_cnt );
634 1125 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
635 9 : return err;
636 9 : }
637 1125 : }
638 :
639 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L662-L686 */
640 258 : ushort instr_cnt = TXN( txn_in->txn )->instr_cnt;
641 438 : for( ushort i=0; i<instr_cnt; i++ ) {
642 192 : fd_txn_instr_t const * instr = &TXN( txn_in->txn )->instr[i];
643 :
644 : /* Mimicking `load_account()` here with 0-lamport check as well.
645 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L663-L666 */
646 192 : fd_acc_t * ref = fd_runtime_get_account_at_index(
647 192 : txn_in, txn_out, instr->program_id, fd_runtime_account_check_exists );
648 192 : if( FD_UNLIKELY( !ref ) ) return FD_RUNTIME_TXN_ERR_PROGRAM_ACCOUNT_NOT_FOUND;
649 :
650 180 : if( FD_UNLIKELY( !ref->lamports ) ) return FD_RUNTIME_TXN_ERR_PROGRAM_ACCOUNT_NOT_FOUND;
651 :
652 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L677-L681 */
653 180 : if( FD_UNLIKELY( memcmp( ref->owner, &fd_solana_native_loader_id, 32UL ) &&
654 180 : !fd_executor_pubkey_is_bpf_loader( (fd_pubkey_t*)ref->owner ) ) ) {
655 0 : return FD_RUNTIME_TXN_ERR_INVALID_PROGRAM_FOR_EXECUTION;
656 0 : }
657 180 : }
658 :
659 246 : return FD_RUNTIME_EXECUTE_SUCCESS;
660 258 : }
661 :
662 : /* https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L118 */
663 : int
664 273 : fd_executor_validate_account_locks( fd_txn_out_t const * txn_out ) {
665 : /* Ensure the number of account keys does not exceed the transaction lock limit
666 : https://github.com/anza-xyz/agave/blob/v2.2.17/accounts-db/src/account_locks.rs#L121 */
667 273 : ulong tx_account_lock_limit = get_transaction_account_lock_limit();
668 273 : if( FD_UNLIKELY( txn_out->accounts.cnt>tx_account_lock_limit ) ) {
669 0 : return FD_RUNTIME_TXN_ERR_TOO_MANY_ACCOUNT_LOCKS;
670 0 : }
671 :
672 : /* Duplicate account check
673 : https://github.com/anza-xyz/agave/blob/v2.2.17/accounts-db/src/account_locks.rs#L123 */
674 1704 : for( ushort i=0; i<txn_out->accounts.cnt; i++ ) {
675 5946 : for( ushort j=(ushort)(i+1U); j<txn_out->accounts.cnt; j++ ) {
676 4515 : if( FD_UNLIKELY( !memcmp( &txn_out->accounts.keys[i], &txn_out->accounts.keys[j], sizeof(fd_pubkey_t) ) ) ) {
677 0 : return FD_RUNTIME_TXN_ERR_ACCOUNT_LOADED_TWICE;
678 0 : }
679 4515 : }
680 1431 : }
681 :
682 : /* https://github.com/anza-xyz/agave/blob/v2.2.17/accounts-db/src/account_locks.rs#L124-L126 */
683 273 : return FD_RUNTIME_EXECUTE_SUCCESS;
684 273 : }
685 :
686 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/compute-budget/src/compute_budget_limits.rs#L62-L70 */
687 : static ulong
688 267 : fd_get_prioritization_fee( fd_compute_budget_details_t const * compute_budget_details ) {
689 267 : uint128 micro_lamport_fee = fd_uint128_sat_mul( compute_budget_details->compute_unit_price, compute_budget_details->compute_unit_limit );
690 267 : uint128 fee = fd_uint128_sat_add( micro_lamport_fee, MICRO_LAMPORTS_PER_LAMPORT-1UL ) / MICRO_LAMPORTS_PER_LAMPORT;
691 267 : return fee>(uint128)ULONG_MAX ? ULONG_MAX : (ulong)fee;
692 267 : }
693 :
694 : static void
695 : fd_executor_calculate_fee( fd_txn_out_t * txn_out,
696 : fd_txn_t const * txn_descriptor,
697 : uchar const * payload,
698 : ulong * ret_execution_fee,
699 267 : ulong * ret_priority_fee ) {
700 : /* The execution fee is just the signature fee. The priority fee
701 : is calculated based on the compute budget details.
702 : https://github.com/anza-xyz/agave/blob/v3.0.3/fee/src/lib.rs#L65-L84 */
703 :
704 : // let signature_fee = Self::get_num_signatures_in_message(message) .saturating_mul(fee_structure.lamports_per_signature);
705 267 : ulong num_signatures = txn_descriptor->signature_cnt;
706 474 : for (ushort i=0; i<txn_descriptor->instr_cnt; ++i ) {
707 207 : fd_txn_instr_t const * txn_instr = &txn_descriptor->instr[i];
708 207 : fd_pubkey_t * program_id = &txn_out->accounts.keys[txn_instr->program_id];
709 207 : if( !memcmp(program_id->uc, fd_solana_keccak_secp_256k_program_id.key, sizeof(fd_pubkey_t)) ||
710 207 : !memcmp(program_id->uc, fd_solana_ed25519_sig_verify_program_id.key, sizeof(fd_pubkey_t)) ||
711 207 : !memcmp(program_id->uc, fd_solana_secp256r1_program_id.key, sizeof(fd_pubkey_t)) ) {
712 0 : if( !txn_instr->data_sz ) {
713 0 : continue;
714 0 : }
715 0 : uchar const * data = payload + txn_instr->data_off;
716 0 : num_signatures = fd_ulong_sat_add(num_signatures, (ulong)(data[0]));
717 0 : }
718 207 : }
719 267 : *ret_execution_fee = FD_RUNTIME_FEE_STRUCTURE_LAMPORTS_PER_SIGNATURE * num_signatures;
720 267 : *ret_priority_fee = fd_get_prioritization_fee( &txn_out->details.compute_budget );
721 267 : }
722 :
723 : /* This function creates a rollback account for just the fee payer.
724 : Although Agave also sets up rollback accounts for both the fee payer
725 : and nonce account here, we already set up the rollback nonce account
726 : in earlier sanitization checks. Here we have to capture the entire
727 : fee payer record so that if the transaction fails, the fee payer
728 : state can be rolled back to it's state pre-transaction, and then
729 : debited any transaction fees.
730 :
731 : Our implementation is slightly different than Agave's in several
732 : ways:
733 :
734 : 1. The rollback nonce account has already been set up when checking
735 : the transaction age
736 : 2. When the nonce and fee payer accounts are the same...
737 : - Agave copies the data from the rollback nonce account into the
738 : rollback fee payer account, and then uses that new fee payer
739 : account as the rollback account.
740 : - We simply set the rent epoch and lamports of the rollback nonce
741 : account (since the other fields of the account do not change).
742 :
743 : https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/rollback_accounts.rs#L34-L77 */
744 :
745 : static void
746 267 : fd_executor_create_rollback_fee_payer_account( fd_txn_out_t * txn_out ) {
747 267 : fd_acc_t const * fee_payer = txn_out->accounts.account[ FD_FEE_PAYER_TXN_IDX ];
748 267 : txn_out->accounts.fee_payer_rollback_lamports = fee_payer->lamports;
749 267 : }
750 :
751 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L557-L634 */
752 : int
753 : fd_executor_validate_transaction_fee_payer( fd_bank_t * bank,
754 : fd_txn_in_t const * txn_in,
755 267 : fd_txn_out_t * txn_out ) {
756 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L574-L580 */
757 267 : fd_acc_t * ref = fd_runtime_get_account_at_index( txn_in, txn_out, FD_FEE_PAYER_TXN_IDX, fd_runtime_account_check_fee_payer_writable );
758 267 : if( FD_UNLIKELY( !ref ) ) return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
759 :
760 267 : fd_acc_t * fee_payer = txn_out->accounts.account[ FD_FEE_PAYER_TXN_IDX ];
761 :
762 : /* Calculate transaction fees
763 : https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L597-L606 */
764 267 : ulong execution_fee = 0UL;
765 267 : ulong priority_fee = 0UL;
766 :
767 267 : fd_executor_calculate_fee( txn_out, TXN( txn_in->txn ), txn_in->txn->payload, &execution_fee, &priority_fee );
768 267 : ulong total_fee = fd_ulong_sat_add( execution_fee, priority_fee );
769 :
770 : /* https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L609-L616 */
771 :
772 267 : int err = fd_validate_fee_payer( fee_payer, &bank->f.rent, total_fee );
773 267 : if( FD_UNLIKELY( err ) ) return err;
774 :
775 : /* Create the rollback fee payer account
776 : https://github.com/anza-xyz/agave/blob/v2.2.13/svm/src/transaction_processor.rs#L620-L626 */
777 267 : fd_executor_create_rollback_fee_payer_account( txn_out );
778 :
779 267 : txn_out->details.execution_fee = execution_fee;
780 267 : txn_out->details.priority_fee = priority_fee;
781 :
782 267 : return FD_RUNTIME_EXECUTE_SUCCESS;
783 267 : }
784 :
785 : /* Resolves any address lookup tables referenced in the transaction and
786 : adds them to the transaction's account keys. Returns 0 on success or
787 : if the transaction is a legacy transaction, and an
788 : FD_RUNTIME_TXN_ERR_* on failure. */
789 : int
790 : fd_executor_setup_txn_alut_account_keys( fd_runtime_t * runtime,
791 : fd_bank_t * bank,
792 : fd_txn_in_t const * txn_in,
793 276 : fd_txn_out_t * txn_out ) {
794 276 : if( FD_LIKELY( TXN( txn_in->txn )->transaction_version==FD_TXN_V0 ) ) {
795 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/runtime/src/bank/address_lookup_table.rs#L44-L48 */
796 132 : fd_sysvar_cache_t const * sysvar_cache = &bank->f.sysvar_cache;
797 132 : fd_slot_hashes_t slot_hashes_view[1];
798 132 : if( FD_UNLIKELY( !fd_sysvar_cache_slot_hashes_view( sysvar_cache, slot_hashes_view ) ) ) {
799 0 : FD_LOG_DEBUG(( "fd_executor_setup_txn_alut_account_keys(): failed to get slot hashes" ));
800 0 : return FD_RUNTIME_TXN_ERR_ACCOUNT_NOT_FOUND;
801 0 : }
802 132 : fd_acct_addr_t * accts_alt = fd_type_pun( &txn_out->accounts.keys[txn_out->accounts.cnt] );
803 132 : int err = fd_runtime_load_txn_address_lookup_tables( TXN( txn_in->txn ),
804 132 : txn_in->txn->payload,
805 132 : runtime->accdb,
806 132 : bank->accdb_fork_id,
807 132 : bank->f.slot,
808 132 : slot_hashes_view,
809 132 : accts_alt );
810 132 : txn_out->accounts.cnt += TXN( txn_in->txn )->addr_table_adtl_cnt;
811 132 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) return err;
812 :
813 132 : }
814 273 : return FD_RUNTIME_EXECUTE_SUCCESS;
815 276 : }
816 :
817 : /* https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L319-L357 */
818 : static inline int
819 : fd_txn_ctx_push( fd_runtime_t * runtime,
820 : fd_txn_in_t const * txn_in,
821 : fd_txn_out_t * txn_out,
822 2982 : fd_instr_info_t * instr ) {
823 : /* Earlier checks in the permalink are redundant since Agave maintains instr stack and trace accounts separately
824 : https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L327-L328 */
825 2982 : ulong starting_lamports_h = 0UL;
826 2982 : ulong starting_lamports_l = 0UL;
827 2982 : int err = fd_instr_info_sum_account_lamports( instr,
828 2982 : txn_out,
829 2982 : &starting_lamports_h,
830 2982 : &starting_lamports_l );
831 2982 : if( FD_UNLIKELY( err ) ) {
832 0 : return err;
833 0 : }
834 2982 : instr->starting_lamports_h = starting_lamports_h;
835 2982 : instr->starting_lamports_l = starting_lamports_l;
836 :
837 : /* Check that the caller's lamport sum has not changed.
838 : https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L329-L340 */
839 2982 : if( runtime->instr.stack_sz>0 ) {
840 : /* https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L330 */
841 2808 : fd_exec_instr_ctx_t const * caller_instruction_context = &runtime->instr.stack[ runtime->instr.stack_sz-1 ];
842 :
843 : /* https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L331-L332 */
844 2808 : ulong original_caller_lamport_sum_h = caller_instruction_context->instr->starting_lamports_h;
845 2808 : ulong original_caller_lamport_sum_l = caller_instruction_context->instr->starting_lamports_l;
846 :
847 : /* https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L333-L334 */
848 2808 : ulong current_caller_lamport_sum_h = 0UL;
849 2808 : ulong current_caller_lamport_sum_l = 0UL;
850 2808 : int err = fd_instr_info_sum_account_lamports( caller_instruction_context->instr,
851 2808 : caller_instruction_context->txn_out,
852 2808 : ¤t_caller_lamport_sum_h,
853 2808 : ¤t_caller_lamport_sum_l );
854 2808 : if( FD_UNLIKELY( err ) ) {
855 0 : return err;
856 0 : }
857 :
858 : /* https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L335-L339 */
859 2808 : if( FD_UNLIKELY( current_caller_lamport_sum_h!=original_caller_lamport_sum_h ||
860 2808 : current_caller_lamport_sum_l!=original_caller_lamport_sum_l ) ) {
861 303 : return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
862 303 : }
863 2808 : }
864 :
865 : /* Note that we don't update the trace length here - since the caller
866 : allocates out of the trace array, they are also responsible for
867 : incrementing the trace length variable.
868 : https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L347-L351 */
869 2679 : if( FD_UNLIKELY( runtime->instr.trace_length>FD_MAX_INSTRUCTION_TRACE_LENGTH ) ) {
870 0 : return FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED;
871 0 : }
872 :
873 : /* https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L352-L356 */
874 2679 : if( FD_UNLIKELY( runtime->instr.stack_sz>=FD_MAX_INSTRUCTION_STACK_DEPTH ) ) {
875 48 : return FD_EXECUTOR_INSTR_ERR_CALL_DEPTH;
876 48 : }
877 2631 : runtime->instr.stack_sz++;
878 :
879 : /* A beloved refactor moves sysvar instructions updating to the instruction level as of v2.2.12...
880 : https://github.com/anza-xyz/agave/blob/v2.2.12/transaction-context/src/lib.rs#L396-L407 */
881 2631 : ulong idx = fd_runtime_find_index_of_account( txn_out, &fd_sysvar_instructions_id );
882 2631 : if( FD_UNLIKELY( idx!=ULONG_MAX ) ) {
883 : /* https://github.com/anza-xyz/agave/blob/v2.2.12/transaction-context/src/lib.rs#L397-L400 */
884 0 : fd_acc_t * ref = fd_runtime_get_account_at_index( txn_in, txn_out, (ushort)idx, NULL );
885 0 : if( FD_UNLIKELY( !ref ) ) return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
886 :
887 : /* https://github.com/anza-xyz/agave/blob/v2.2.12/transaction-context/src/lib.rs#L401-L402 */
888 0 : if( FD_UNLIKELY( runtime->accounts.refcnt[ idx ]!=0UL ) ) {
889 0 : return FD_EXECUTOR_INSTR_ERR_ACC_BORROW_FAILED;
890 0 : }
891 :
892 : /* https://github.com/anza-xyz/agave/blob/v2.2.12/transaction-context/src/lib.rs#L403-L406 */
893 0 : fd_sysvar_instructions_update_current_instr_idx( txn_out->accounts.account[ idx ], (ushort)runtime->instr.current_idx );
894 0 : }
895 :
896 2631 : return FD_EXECUTOR_INSTR_SUCCESS;
897 2631 : }
898 :
899 : /* Pushes a new instruction onto the instruction stack and trace. This check loops through all instructions in the current call stack
900 : and checks for reentrancy violations. If successful, simply increments the instruction stack and trace size and returns. It is
901 : the responsibility of the caller to populate the newly pushed instruction fields, which are undefined otherwise.
902 :
903 : https://github.com/anza-xyz/agave/blob/v2.0.0/program-runtime/src/invoke_context.rs#L246-L290 */
904 : int
905 : fd_instr_stack_push( fd_runtime_t * runtime,
906 : fd_txn_in_t const * txn_in,
907 : fd_txn_out_t * txn_out,
908 2982 : fd_instr_info_t * instr ) {
909 : /* Agave keeps a vector of vectors called program_indices that stores the program_id index for each instruction within the transaction.
910 : https://github.com/anza-xyz/agave/blob/v2.1.7/svm/src/account_loader.rs#L347-L402
911 : If and only if the program_id is the native loader, then the vector for respective specific instruction (account_indices) is empty.
912 : https://github.com/anza-xyz/agave/blob/v2.1.7/svm/src/account_loader.rs#L350-L358
913 : While trying to push a new instruction onto the instruction stack, if the vector for the respective instruction is empty, Agave throws UnsupportedProgramId
914 : https://github.com/anza-xyz/agave/blob/v2.1.7/program-runtime/src/invoke_context.rs#L253-L255
915 : 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
916 : */
917 :
918 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/program-runtime/src/invoke_context.rs#L250-L252 */
919 2982 : fd_pubkey_t const * program_id_pubkey = NULL;
920 2982 : int err = fd_runtime_get_key_of_account_at_index( txn_out,
921 2982 : instr->program_id,
922 2982 : &program_id_pubkey );
923 2982 : if( FD_UNLIKELY( err ) ) {
924 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
925 0 : }
926 :
927 : /* https://github.com/anza-xyz/agave/blob/v2.0.0/program-runtime/src/invoke_context.rs#L256-L286 */
928 2982 : if( runtime->instr.stack_sz ) {
929 : /* https://github.com/anza-xyz/agave/blob/v2.0.0/program-runtime/src/invoke_context.rs#L261-L285 */
930 2808 : uchar contains = 0;
931 2808 : uchar is_last = 0;
932 :
933 : // Checks all previous instructions in the stack for reentrancy
934 5808 : for( uchar level=0; level<runtime->instr.stack_sz; level++ ) {
935 3000 : fd_exec_instr_ctx_t * instr_ctx = &runtime->instr.stack[level];
936 : // Optimization: compare program id index instead of pubkey since account keys are unique
937 3000 : if( instr->program_id == instr_ctx->instr->program_id ) {
938 : // Reentrancy not allowed unless caller is calling itself
939 2997 : if( level == runtime->instr.stack_sz-1 ) {
940 2805 : is_last = 1;
941 2805 : }
942 2997 : contains = 1;
943 2997 : }
944 3000 : }
945 : /* https://github.com/anza-xyz/agave/blob/v2.0.0/program-runtime/src/invoke_context.rs#L282-L285 */
946 2808 : if( FD_UNLIKELY( contains && !is_last ) ) {
947 0 : return FD_EXECUTOR_INSTR_ERR_REENTRANCY_NOT_ALLOWED;
948 0 : }
949 2808 : }
950 : /* "Push" a new instruction onto the stack by simply incrementing the stack and trace size counters
951 : https://github.com/anza-xyz/agave/blob/v2.0.0/program-runtime/src/invoke_context.rs#L289 */
952 2982 : return fd_txn_ctx_push( runtime, txn_in, txn_out, instr );
953 2982 : }
954 :
955 : /* Pops an instruction from the instruction stack. Agave's implementation performs instruction balancing checks every time pop is called,
956 : but error codes returned from `pop` are only used if the program's execution was successful. Therefore, we can optimize our code by only
957 : checking for unbalanced instructions if the program execution was successful within fd_execute_instr.
958 :
959 : https://github.com/anza-xyz/agave/blob/v2.0.0/program-runtime/src/invoke_context.rs#L293-L298 */
960 : int
961 : fd_instr_stack_pop( fd_runtime_t * runtime,
962 : fd_txn_out_t * txn_out,
963 2631 : fd_instr_info_t const * instr ) {
964 : /* https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L362-L364 */
965 2631 : if( FD_UNLIKELY( runtime->instr.stack_sz==0 ) ) {
966 0 : return FD_EXECUTOR_INSTR_ERR_CALL_DEPTH;
967 0 : }
968 2631 : runtime->instr.stack_sz--;
969 :
970 : /* Verify all executable accounts have no outstanding refs
971 : https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L367-L371 */
972 12057 : for( ushort i=0; i<instr->acct_cnt; i++ ) {
973 9426 : ushort idx_in_txn = instr->accounts[ i ].index_in_transaction;
974 9426 : fd_acc_t const * acc = txn_out->accounts.account[ idx_in_txn ];
975 9426 : ulong refcnt = runtime->accounts.refcnt[idx_in_txn];
976 9426 : if( FD_UNLIKELY( acc->executable && refcnt ) ) return FD_EXECUTOR_INSTR_ERR_ACC_BORROW_OUTSTANDING;
977 9426 : }
978 :
979 : /* Verify lamports are balanced before and after instruction
980 : https://github.com/anza-xyz/agave/blob/v2.0.0/sdk/src/transaction_context.rs#L366-L380 */
981 2631 : ulong ending_lamports_h = 0UL;
982 2631 : ulong ending_lamports_l = 0UL;
983 2631 : int err = fd_instr_info_sum_account_lamports( instr,
984 2631 : txn_out,
985 2631 : &ending_lamports_h,
986 2631 : &ending_lamports_l );
987 2631 : if( FD_UNLIKELY( err ) ) {
988 0 : return err;
989 0 : }
990 2631 : if( FD_UNLIKELY( ending_lamports_l != instr->starting_lamports_l || ending_lamports_h != instr->starting_lamports_h ) ) {
991 0 : return FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR;
992 0 : }
993 :
994 2631 : return FD_EXECUTOR_INSTR_SUCCESS;
995 2631 : }
996 :
997 : /* This function mimics Agave's `.and(self.pop())` functionality,
998 : where we always pop the instruction stack no matter what the error code is.
999 : https://github.com/anza-xyz/agave/blob/v2.2.12/program-runtime/src/invoke_context.rs#L480 */
1000 : int
1001 : fd_execute_instr_end( fd_exec_instr_ctx_t * instr_ctx,
1002 : fd_instr_info_t const * instr,
1003 2631 : int instr_exec_result ) {
1004 2631 : int stack_pop_err = fd_instr_stack_pop( instr_ctx->runtime, instr_ctx->txn_out, instr );
1005 :
1006 : /* Only report the stack pop error on success */
1007 2631 : if( FD_UNLIKELY( instr_exec_result==FD_EXECUTOR_INSTR_SUCCESS && stack_pop_err ) ) {
1008 0 : FD_TXN_PREPARE_ERR_OVERWRITE( instr_ctx->txn_out );
1009 0 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_out, stack_pop_err, instr_ctx->txn_out->err.exec_err_idx );
1010 0 : instr_exec_result = stack_pop_err;
1011 0 : }
1012 :
1013 2631 : return instr_exec_result;
1014 2631 : }
1015 :
1016 : int
1017 : fd_execute_instr( fd_runtime_t * runtime,
1018 : fd_bank_t * bank,
1019 : fd_txn_in_t const * txn_in,
1020 : fd_txn_out_t * txn_out,
1021 2982 : fd_instr_info_t * instr ) {
1022 2982 : fd_sysvar_cache_t const * sysvar_cache = &bank->f.sysvar_cache;
1023 2982 : int instr_exec_result = fd_instr_stack_push( runtime, txn_in, txn_out, instr );
1024 2982 : if( FD_UNLIKELY( instr_exec_result ) ) {
1025 351 : FD_TXN_PREPARE_ERR_OVERWRITE( txn_out );
1026 351 : FD_TXN_ERR_FOR_LOG_INSTR( txn_out, instr_exec_result, txn_out->err.exec_err_idx );
1027 351 : return instr_exec_result;
1028 351 : }
1029 :
1030 : /* `process_executable_chain()`
1031 : https://github.com/anza-xyz/agave/blob/v2.2.12/program-runtime/src/invoke_context.rs#L512-L619 */
1032 2631 : fd_exec_instr_ctx_t * ctx = &runtime->instr.stack[ runtime->instr.stack_sz - 1 ];
1033 2631 : *ctx = (fd_exec_instr_ctx_t) {
1034 2631 : .instr = instr,
1035 2631 : .sysvar_cache = sysvar_cache,
1036 2631 : .runtime = runtime,
1037 2631 : .txn_in = txn_in,
1038 2631 : .txn_out = txn_out,
1039 2631 : .bank = bank,
1040 2631 : };
1041 2631 : fd_base58_encode_32( txn_out->accounts.keys[ instr->program_id ].uc, NULL, ctx->program_id_base58 );
1042 :
1043 : /* Look up the native program. We check for precompiles within the lookup function as well.
1044 : https://github.com/anza-xyz/agave/blob/v2.1.6/svm/src/message_processor.rs#L88 */
1045 2631 : fd_exec_instr_fn_t native_prog_fn;
1046 2631 : uchar is_precompile;
1047 2631 : int err = fd_executor_lookup_native_program( txn_out->accounts.account[ instr->program_id ],
1048 2631 : bank,
1049 2631 : &native_prog_fn,
1050 2631 : &is_precompile );
1051 :
1052 2631 : if( FD_UNLIKELY( err ) ) {
1053 0 : FD_TXN_PREPARE_ERR_OVERWRITE( txn_out );
1054 0 : FD_TXN_ERR_FOR_LOG_INSTR( txn_out, err, txn_out->err.exec_err_idx );
1055 0 : return fd_execute_instr_end( ctx, instr, err );
1056 0 : }
1057 :
1058 2631 : if( FD_LIKELY( native_prog_fn!=NULL ) ) {
1059 : /* If this branch is taken, we've found an entrypoint to execute. */
1060 2631 : fd_log_collector_program_invoke( ctx );
1061 :
1062 : /* Only reset the return data when executing a native builtin program (not a precompile)
1063 : https://github.com/anza-xyz/agave/blob/v2.1.6/program-runtime/src/invoke_context.rs#L536-L537 */
1064 2631 : if( FD_LIKELY( !is_precompile ) ) {
1065 2631 : txn_out->details.return_data.len = 0;
1066 2631 : }
1067 :
1068 : /* Execute the native program. */
1069 2631 : instr_exec_result = native_prog_fn( ctx );
1070 2631 : } else {
1071 : /* Unknown program. In this case specifically, we should not log the program id. */
1072 0 : instr_exec_result = FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID;
1073 0 : FD_TXN_PREPARE_ERR_OVERWRITE( txn_out );
1074 0 : FD_TXN_ERR_FOR_LOG_INSTR( txn_out, instr_exec_result, txn_out->err.exec_err_idx );
1075 0 : return fd_execute_instr_end( ctx, instr, instr_exec_result );
1076 0 : }
1077 :
1078 2631 : if( FD_LIKELY( instr_exec_result==FD_EXECUTOR_INSTR_SUCCESS ) ) {
1079 : /* Log success */
1080 2364 : fd_log_collector_program_success( ctx );
1081 2364 : } else {
1082 : /* Log failure cases.
1083 : We assume that the correct type of error is stored in ctx.
1084 : Syscalls are expected to log when the error is generated, while
1085 : native programs will be logged here.
1086 : (This is because syscall errors often carry data with them.)
1087 :
1088 : TODO: This hackily handles cases where the exec_err and exec_err_kind
1089 : is not set yet. We should change our native programs to set
1090 : this in their respective processors. */
1091 267 : if( !txn_out->err.exec_err ) {
1092 186 : FD_TXN_PREPARE_ERR_OVERWRITE( txn_out );
1093 186 : FD_TXN_ERR_FOR_LOG_INSTR( txn_out, instr_exec_result, txn_out->err.exec_err_idx );
1094 186 : fd_log_collector_program_failure( ctx );
1095 186 : } else {
1096 81 : fd_log_collector_program_failure( ctx );
1097 81 : FD_TXN_PREPARE_ERR_OVERWRITE( txn_out );
1098 81 : FD_TXN_ERR_FOR_LOG_INSTR( txn_out, instr_exec_result, txn_out->err.exec_err_idx );
1099 81 : }
1100 267 : }
1101 :
1102 2631 : return fd_execute_instr_end( ctx, instr, instr_exec_result );
1103 2631 : }
1104 :
1105 : /* fd_executor_setup_accounts_for_txn_bundle is the bundle counterpart of
1106 : fd_executor_setup_accounts_for_txn. A bundle's accounts are acquired
1107 : once, up-front, for the whole bundle by
1108 : fd_runtime_prepare_bundle_accounts, so this never acquires and never
1109 : resets the shared pool (prepare owns it): it resolves the txn's keys
1110 : and binds every account (and programdata) to the pre-acquired pool.
1111 : Each bind must succeed; a miss means prepare did not acquire an
1112 : account this txn references, which is a bug. */
1113 :
1114 : void
1115 : fd_executor_setup_accounts_for_txn_bundle( fd_runtime_t * runtime,
1116 : fd_txn_in_t const * txn_in,
1117 99 : fd_txn_out_t * txn_out ) {
1118 : /* The key list was resolved once up-front by
1119 : fd_runtime_prepare_bundle_accounts. Bind every transaction account
1120 : to the shared pre-acquired pool. */
1121 936 : for( ushort i=0; i<txn_out->accounts.cnt; i++ ) {
1122 837 : txn_out->accounts.is_writable[ i ] = (uchar)fd_runtime_account_is_writable_idx( txn_in, txn_out, i );
1123 837 : txn_out->accounts.account_acquired[ i ] = 0U;
1124 837 : runtime->accounts.refcnt[ i ] = 0UL;
1125 :
1126 837 : fd_acc_t * acc = txn_out->accounts.account[ i ];
1127 837 : FD_TEST( acc );
1128 :
1129 : /* If the prior bundle txn drained the account to zero lamports,
1130 : the next txn must observe the reclaimed account, matching the
1131 : tombstone reset the accdb applies on a fresh acquire: empty
1132 : data, non-executable, system owner.
1133 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm/src/account_loader.rs#L199-L228 */
1134 837 : if( FD_UNLIKELY( !acc->lamports ) ) {
1135 54 : acc->data_len = 0UL;
1136 54 : acc->executable = 0;
1137 54 : memset( acc->owner, 0, 32UL );
1138 54 : }
1139 :
1140 837 : txn_out->accounts.starting_lamports[ i ] = acc->lamports;
1141 837 : txn_out->accounts.starting_data_len[ i ] = acc->data_len;
1142 :
1143 : /* Iterate backwards through previous bundle txns to find the relevant
1144 : account. No duplicate accounts can be loaded in a bundle. */
1145 837 : int found = 0;
1146 1551 : for( ulong p=txn_in->bundle.prev_txn_cnt; p>0UL && !found; p-- ) {
1147 714 : fd_txn_out_t * prev_txn = txn_in->bundle.prev_txn_outs[ p-1UL ];
1148 5220 : for( ushort k=0; k<prev_txn->accounts.cnt; k++ ) {
1149 : /* Only match if the txn owns the reference to the account. */
1150 4974 : if( !prev_txn->accounts.account_acquired[ k ] ||
1151 4974 : !fd_pubkey_eq( &prev_txn->accounts.keys[ k ], &txn_out->accounts.keys[ i ] ) ) continue;
1152 468 : found = 1;
1153 :
1154 : /* If this txn writes the account, transfer ownership of the accdb
1155 : ref to it and carry forward the vote and stake cache update
1156 : flags. */
1157 468 : if( txn_out->accounts.is_writable[ i ] ) {
1158 93 : txn_out->accounts.stake_update[ i ] |= prev_txn->accounts.stake_update[ k ]; prev_txn->accounts.stake_update[ k ] = 0;
1159 93 : txn_out->accounts.vote_update [ i ] |= prev_txn->accounts.vote_update [ k ]; prev_txn->accounts.vote_update [ k ] = 0;
1160 93 : prev_txn->accounts.account_acquired[ k ] = 0U;
1161 93 : txn_out->accounts.account_acquired[ i ] = 1U;
1162 93 : }
1163 468 : break;
1164 4974 : }
1165 714 : }
1166 :
1167 837 : if( !found ) txn_out->accounts.account_acquired[ i ] = 1U;
1168 837 : }
1169 :
1170 : /* The executable (programdata) accounts were bound for every txn
1171 : up-front by fd_runtime_prepare_bundle_accounts and are preserved
1172 : across fd_runtime_new_txn_out for bundle txns, so there is nothing
1173 : to do here. */
1174 99 : txn_out->accounts.is_setup = 1;
1175 99 : txn_out->accounts.nonce_idx_in_txn = ULONG_MAX;
1176 99 : }
1177 :
1178 : int
1179 : fd_executor_setup_accounts_for_txn( fd_runtime_t * runtime,
1180 : fd_bank_t * bank,
1181 : fd_txn_in_t const * txn_in,
1182 171 : fd_txn_out_t * txn_out ) {
1183 :
1184 171 : ulong acquire_idx[ MAX_TX_ACCOUNT_LOCKS ];
1185 171 : int acquire_writable[ MAX_TX_ACCOUNT_LOCKS ];
1186 171 : uchar const * acquire_pubkeys[ MAX_TX_ACCOUNT_LOCKS ];
1187 171 : ulong acquire_cnt = 0UL;
1188 :
1189 171 : runtime->accounts.account_cnt = 0UL;
1190 171 : runtime->accounts.executable_cnt = 0UL;
1191 :
1192 171 : txn_out->accounts.cnt = (uchar)TXN( txn_in->txn )->acct_addr_cnt;
1193 171 : fd_pubkey_t * tx_accs = (fd_pubkey_t *)((uchar *)txn_in->txn->payload + TXN( txn_in->txn )->acct_addr_off);
1194 756 : for( ulong i=0UL; i<TXN( txn_in->txn )->acct_addr_cnt; i++ ) txn_out->accounts.keys[ i ] = tx_accs[ i ];
1195 :
1196 171 : txn_out->accounts.executable_cnt = 0UL;
1197 :
1198 171 : int err = fd_executor_setup_txn_alut_account_keys( runtime, bank, txn_in, txn_out );
1199 171 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) return err;
1200 :
1201 : /* Validate account locks before acquiring; the accdb acquire
1202 : hard-asserts the lock count is within bounds. */
1203 171 : err = fd_executor_validate_account_locks( txn_out );
1204 171 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) return err;
1205 :
1206 : /* Resolve all transaction accounts and queue them for acquisition. */
1207 :
1208 756 : for( ushort i=0; i<txn_out->accounts.cnt; i++ ) {
1209 585 : txn_out->accounts.is_writable[ i ] = (uchar)fd_runtime_account_is_writable_idx( txn_in, txn_out, i );
1210 585 : txn_out->accounts.account_acquired[ i ] = 0U;
1211 585 : txn_out->accounts.account[ i ] = NULL;
1212 585 : runtime->accounts.refcnt[ i ] = 0UL;
1213 :
1214 585 : FD_TEST( runtime->accounts.account_cnt+acquire_cnt<FD_PACK_MAX_TXN_PER_BUNDLE*MAX_TX_ACCOUNT_LOCKS );
1215 585 : acquire_idx[ acquire_cnt ] = i;
1216 585 : acquire_pubkeys[ acquire_cnt ] = txn_out->accounts.keys[ i ].uc;
1217 585 : acquire_writable[ acquire_cnt ] = (int)txn_out->accounts.is_writable[ i ];
1218 585 : acquire_cnt++;
1219 585 : }
1220 :
1221 : /* The hilariously poorly designed account loader semantics require a
1222 : two phase acquire ... since the programdata accounts do not need to
1223 : be declared in the transaction account keys. We first have to
1224 : acquire all accounts expressly referenced in the transaction, and
1225 : overcommit the reservations by double the number of them that could
1226 : be executable. This is because acquire must grab all locks it
1227 : needs atomically, if we went back to grab more locks later it could
1228 : deadlock with two threads both holding half the locks each and
1229 : unable to acquire.
1230 :
1231 : So first, just acquire and atomically reserve double the locks,
1232 : then figure out which accounts are executable, keep those
1233 : reservations, and release the extras back to the pool in the second
1234 : phase. */
1235 :
1236 171 : if( FD_LIKELY( acquire_cnt ) ) {
1237 171 : fd_acc_t * acquire_base = &runtime->accounts.account[ runtime->accounts.account_cnt ];
1238 171 : fd_accdb_acquire_a( runtime->accdb, bank->accdb_fork_id, acquire_cnt, acquire_pubkeys, acquire_writable, acquire_base );
1239 :
1240 756 : for( ulong i=0UL; i<acquire_cnt; i++ ) {
1241 585 : ulong txn_idx = acquire_idx[ i ];
1242 585 : txn_out->accounts.account[ txn_idx ] = &acquire_base[ i ];
1243 585 : txn_out->accounts.account_acquired[ txn_idx ] = 1U;
1244 585 : txn_out->accounts.starting_lamports[ txn_idx ] = acquire_base[ i ].prior_lamports;
1245 585 : txn_out->accounts.starting_data_len[ txn_idx ] = acquire_base[ i ].prior_data_len;
1246 585 : }
1247 171 : runtime->accounts.account_cnt += acquire_cnt;
1248 171 : }
1249 :
1250 171 : ushort executable_account_cnt = 0;
1251 171 : ushort executable_acquire_cnt = 0;
1252 171 : ushort executable_acquire_idx[ MAX_TX_ACCOUNT_LOCKS ];
1253 171 : fd_pubkey_t programdata_keys[ MAX_TX_ACCOUNT_LOCKS ];
1254 171 : int writable[ MAX_TX_ACCOUNT_LOCKS ];
1255 171 : uchar const * pubkeys[ MAX_TX_ACCOUNT_LOCKS ];
1256 756 : for( ushort i=0; i<txn_out->accounts.cnt; i++ ) {
1257 585 : fd_acc_t * acc = txn_out->accounts.account[ i ];
1258 585 : if( FD_UNLIKELY( memcmp( acc->owner, fd_solana_bpf_loader_upgradeable_program_id.key, 32UL ) ) ) continue;
1259 54 : fd_bpf_state_t program_loader_state[1];
1260 54 : err = fd_bpf_loader_program_get_state( txn_out->accounts.account[ i ], program_loader_state );
1261 54 : if( FD_UNLIKELY( err!=FD_EXECUTOR_INSTR_SUCCESS ) ) continue;
1262 54 : if( FD_UNLIKELY( program_loader_state->discriminant!=FD_BPF_STATE_PROGRAM ) ) continue;
1263 :
1264 33 : fd_pubkey_t const * programdata_key = &program_loader_state->inner.program.programdata_address;
1265 :
1266 : /* If the programdata account is already one of the transaction's
1267 : declared accounts (e.g. a loader Upgrade/Extend lists it as a
1268 : writable instruction account), do NOT re-acquire it here. A
1269 : second acquire of the same (pubkey, fork) while it is held
1270 : writable from acquire_a above violates the accdb acquire contract
1271 : (a writable acquire must not overlap any other acquire of the
1272 : same account on the same fork; see fd_accdb.h). It is also
1273 : redundant: fd_runtime_get_executable_account() always prefers the
1274 : declared account over runtime->accounts.executable[], so the
1275 : read-only copy would never be read. */
1276 33 : if( FD_UNLIKELY( fd_runtime_find_index_of_account( txn_out, programdata_key )!=ULONG_MAX ) ) continue;
1277 :
1278 24 : FD_TEST( bank->parent_accdb_fork_id.val!=USHORT_MAX );
1279 24 : if( FD_UNLIKELY( !fd_accdb_exists( runtime->accdb, bank->parent_accdb_fork_id, programdata_key->uc ) ) ) {
1280 3 : int skip_pd = 0;
1281 3 : ulong skip_len = 0UL;
1282 3 : if( fd_accdb_probe_pd_this_fork( runtime->accdb, bank->accdb_fork_id, programdata_key->uc, &skip_pd, &skip_len ) ) {
1283 3 : ushort s = txn_out->accounts.executable_skipped_cnt++;
1284 3 : txn_out->accounts.executable_skipped_key[ s ] = *programdata_key;
1285 3 : txn_out->accounts.executable_skipped_len[ s ] = skip_len;
1286 3 : }
1287 3 : continue;
1288 3 : }
1289 :
1290 21 : writable[ executable_acquire_cnt ] = 0;
1291 21 : executable_acquire_idx[ executable_acquire_cnt ] = executable_account_cnt;
1292 : /* Keep the derived programdata address in stable storage until
1293 : fd_accdb_acquire_b() consumes the pubkey array below. */
1294 21 : programdata_keys[ executable_acquire_cnt ] = *programdata_key;
1295 21 : pubkeys[ executable_acquire_cnt ] = programdata_keys[ executable_acquire_cnt ].uc;
1296 21 : executable_acquire_cnt++;
1297 21 : executable_account_cnt++;
1298 21 : }
1299 :
1300 : /* acquire_b must refund exactly what acquire_a reserved. acquire_a
1301 : ran over acquire_cnt pubkeys, so the reserved count is acquire_cnt
1302 : and not txn_out->accounts.cnt. */
1303 171 : FD_TEST( runtime->accounts.executable_cnt+executable_acquire_cnt<=FD_PACK_MAX_TXN_PER_BUNDLE*MAX_TX_ACCOUNT_LOCKS );
1304 171 : fd_acc_t * acquire_base = &runtime->accounts.executable[ runtime->accounts.executable_cnt ];
1305 171 : fd_accdb_acquire_b( runtime->accdb, bank->parent_accdb_fork_id, acquire_cnt, executable_acquire_cnt, pubkeys, writable, acquire_base );
1306 171 : int acquired_from_parent = bank->parent_accdb_fork_id.val!=bank->accdb_fork_id.val;
1307 192 : for( ushort i=0; i<executable_acquire_cnt; i++ ) {
1308 21 : ushort exe_idx = executable_acquire_idx[ i ];
1309 21 : txn_out->accounts.executable[ exe_idx ] = &acquire_base[ i ];
1310 21 : txn_out->accounts.executable_from_parent[ exe_idx ] = acquired_from_parent;
1311 21 : int pd = 0;
1312 21 : ulong len = ULONG_MAX;
1313 21 : fd_accdb_probe_pd_this_fork( runtime->accdb, bank->accdb_fork_id, pubkeys[ i ], &pd, &len );
1314 21 : txn_out->accounts.executable_pd_write[ exe_idx ] = pd;
1315 21 : txn_out->accounts.executable_cur_len[ exe_idx ] = len;
1316 21 : }
1317 171 : runtime->accounts.executable_cnt += executable_acquire_cnt;
1318 :
1319 :
1320 171 : txn_out->accounts.is_setup = 1;
1321 171 : txn_out->accounts.nonce_idx_in_txn = ULONG_MAX;
1322 171 : txn_out->accounts.executable_cnt = executable_account_cnt;
1323 171 : return FD_RUNTIME_EXECUTE_SUCCESS;
1324 171 : }
1325 :
1326 : int
1327 : fd_executor_txn_verify( fd_txn_p_t * txn_p,
1328 0 : fd_sha512_t * shas[ FD_TXN_ACTUAL_SIG_MAX ] ) {
1329 0 : fd_txn_t * txn = TXN( txn_p );
1330 :
1331 0 : uchar * signatures = txn_p->payload + txn->signature_off;
1332 0 : uchar * pubkeys = txn_p->payload + txn->acct_addr_off;
1333 0 : uchar * msg = txn_p->payload + txn->message_off;
1334 0 : ulong msg_sz = txn_p->payload_sz - txn->message_off;
1335 :
1336 0 : int res = fd_ed25519_verify_batch_single_msg( msg, msg_sz, signatures, pubkeys, shas, txn->signature_cnt );
1337 0 : if( FD_UNLIKELY( res!=FD_ED25519_SUCCESS ) ) return FD_RUNTIME_TXN_ERR_SIGNATURE_FAILURE;
1338 :
1339 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
1340 0 : }
1341 :
1342 : static int
1343 : fd_executor_txn_check( fd_bank_t * bank,
1344 180 : fd_txn_out_t * txn_out ) {
1345 180 : ulong starting_lamports_l = 0UL;
1346 180 : ulong starting_lamports_h = 0UL;
1347 180 : ulong ending_lamports_l = 0UL;
1348 180 : ulong ending_lamports_h = 0UL;
1349 :
1350 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L63 */
1351 1308 : for( ulong i=0UL; i<txn_out->accounts.cnt; i++ ) {
1352 1128 : fd_acc_t * acc = txn_out->accounts.account[ i ];
1353 1128 : if( FD_UNLIKELY( !txn_out->accounts.is_writable[ i ] ) ) continue;
1354 :
1355 : /* The fee payer's starting balance is its post-fee-deduction
1356 : balance. */
1357 345 : ulong starting_lamports = i!=FD_FEE_PAYER_TXN_IDX ? txn_out->accounts.starting_lamports[ i ]
1358 345 : : txn_out->accounts.fee_payer_rollback_lamports;
1359 :
1360 : /* Tips for bundles are collected in the bank: a user submitting a
1361 : bundle must include a instruction that transfers lamports to
1362 : a specific tip account. Tips accumulated through the slot.
1363 :
1364 : The delta must be measured against this txn's starting balance,
1365 : not acc->prior_lamports. For a tip account reused across bundle
1366 : txns, prior_lamports stays anchored to the on-chain pre-image, so
1367 : using it here would re-count the credit from earlier bundle txns
1368 : and inflate bank->f.tips. starting_lamports[] holds the carried-
1369 : forward balance (== prior_lamports for freshly acquired accounts). */
1370 345 : if( FD_UNLIKELY( fd_pack_tip_is_tip_account( fd_type_pun_const( txn_out->accounts.keys[ i ].uc ) ) ) ) {
1371 0 : txn_out->details.tips += fd_ulong_sat_sub( acc->lamports, starting_lamports );
1372 0 : }
1373 :
1374 345 : fd_uwide_inc( &ending_lamports_h, &ending_lamports_l, ending_lamports_h, ending_lamports_l, acc->lamports );
1375 345 : fd_uwide_inc( &starting_lamports_h, &starting_lamports_l, starting_lamports_h, starting_lamports_l, starting_lamports );
1376 :
1377 : /* Rent states are defined as followed:
1378 : - lamports == 0 -> Uninitialized
1379 : - 0 < lamports < rent_exempt_minimum -> RentPaying
1380 : - lamports >= rent_exempt_minimum -> RentExempt
1381 : In Agave, 'self' refers to our 'after' state. */
1382 345 : uchar after_uninitialized = acc->lamports==0UL;
1383 345 : uchar after_rent_exempt = acc->lamports>=fd_rent_exempt_minimum_balance( &bank->f.rent, acc->data_len );
1384 :
1385 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L96 */
1386 345 : if( FD_LIKELY( memcmp( &txn_out->accounts.keys[i], fd_sysvar_incinerator_id.key, sizeof(fd_pubkey_t) ) ) ) {
1387 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L44 */
1388 345 : if( after_uninitialized || after_rent_exempt ) {
1389 : // no-op
1390 333 : } else {
1391 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L45-L59 */
1392 : /* Use this carried-forward starting state, not acc->prior_*.
1393 : Equals prior_* for freshly acquired accounts. */
1394 12 : ulong before_lamports = starting_lamports;
1395 12 : ulong before_data_len = txn_out->accounts.starting_data_len[ i ];
1396 12 : uchar before_uninitialized = before_lamports==0UL;
1397 12 : uchar before_rent_exempt = before_lamports>=fd_rent_exempt_minimum_balance( &bank->f.rent, before_data_len );
1398 :
1399 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L50 */
1400 12 : if( FD_UNLIKELY( before_uninitialized || before_rent_exempt ) ) {
1401 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L104 */
1402 0 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
1403 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L56 */
1404 12 : } else if( (acc->data_len==before_data_len) && acc->lamports<=before_lamports ) {
1405 : // no-op
1406 12 : } else {
1407 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/account_rent_state.rs#L104 */
1408 0 : return FD_RUNTIME_TXN_ERR_INSUFFICIENT_FUNDS_FOR_RENT;
1409 0 : }
1410 12 : }
1411 345 : }
1412 :
1413 345 : if ( !memcmp( acc->owner, &fd_solana_stake_program_id, sizeof(fd_pubkey_t) ) ) txn_out->accounts.stake_update[ i ] = 1;
1414 336 : else if( !memcmp( acc->owner, &fd_solana_vote_program_id, sizeof(fd_pubkey_t) ) ) txn_out->accounts.vote_update[ i ] = 1;
1415 345 : }
1416 :
1417 : /* https://github.com/anza-xyz/agave/blob/b2c388d6cbff9b765d574bbb83a4378a1fc8af32/svm/src/transaction_processor.rs#L839-L845 */
1418 180 : if( FD_UNLIKELY( ending_lamports_l!=starting_lamports_l || ending_lamports_h!=starting_lamports_h ) ) {
1419 0 : return FD_RUNTIME_TXN_ERR_UNBALANCED_TRANSACTION;
1420 0 : }
1421 :
1422 180 : return FD_RUNTIME_EXECUTE_SUCCESS;
1423 180 : }
1424 :
1425 :
1426 : int
1427 : fd_execute_txn( fd_runtime_t * runtime,
1428 : fd_bank_t * bank,
1429 : fd_txn_in_t const * txn_in,
1430 246 : fd_txn_out_t * txn_out ) {
1431 :
1432 246 : bool dump_insn = runtime->log.dump_proto_ctx &&
1433 246 : bank->f.slot>=runtime->log.dump_proto_ctx->dump_proto_start_slot &&
1434 246 : runtime->log.dump_proto_ctx->dump_instr_to_pb;
1435 :
1436 246 : if( FD_UNLIKELY( runtime->log.log_collector ) ) fd_log_collector_init( runtime->log.log_collector, runtime->log.enable_log_collector );
1437 :
1438 246 : fd_txn_t const * txn = TXN( txn_in->txn );
1439 354 : for( ushort i=0; i<txn->instr_cnt; i++ ) {
1440 : /* Set up the instr info for the current instruction */
1441 174 : fd_instr_info_t * instr_info = &runtime->instr.trace[ runtime->instr.trace_length++ ];
1442 174 : fd_instr_info_init_from_txn_instr( instr_info, txn_in, txn_out, &txn->instr[ i ] );
1443 :
1444 174 : if( FD_UNLIKELY( dump_insn ) ) {
1445 : // Capture the input and convert it into a Protobuf message
1446 0 : fd_dump_instr_to_protobuf( runtime, bank, txn_in, txn_out, instr_info, i );
1447 0 : }
1448 :
1449 : /* Update the current executing instruction index */
1450 174 : runtime->instr.current_idx = i;
1451 :
1452 : /* Execute the current instruction */
1453 174 : int instr_exec_result = fd_execute_instr( runtime, bank, txn_in, txn_out, instr_info );
1454 174 : if( FD_UNLIKELY( instr_exec_result!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
1455 66 : if( FD_UNLIKELY( txn_out->err.exec_err_idx==UINT_MAX ) ) txn_out->err.exec_err_idx = i;
1456 66 : return FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR;
1457 66 : }
1458 174 : }
1459 :
1460 : /* TODO: This function needs to be split out of fd_execute_txn and be placed
1461 : into the replay tile once it is implemented. */
1462 180 : return fd_executor_txn_check( bank, txn_out );
1463 246 : }
1464 :
1465 : int
1466 : fd_executor_consume_cus( fd_txn_out_t * txn_out,
1467 2610 : ulong cus ) {
1468 2610 : ulong new_cus = txn_out->details.compute_budget.compute_meter - cus;
1469 2610 : int underflow = (txn_out->details.compute_budget.compute_meter < cus);
1470 2610 : if( FD_UNLIKELY( underflow ) ) {
1471 0 : txn_out->details.compute_budget.compute_meter = 0UL;
1472 0 : return FD_EXECUTOR_INSTR_ERR_COMPUTE_BUDGET_EXCEEDED;
1473 0 : }
1474 2610 : txn_out->details.compute_budget.compute_meter = new_cus;
1475 2610 : return FD_EXECUTOR_INSTR_SUCCESS;
1476 2610 : }
1477 :
1478 : /* fd_executor_instr_strerror() returns the error message corresponding to err,
1479 : intended to be logged by log_collector, or an empty string if the error code
1480 : should be omitted in logs for whatever reason. Omitted examples are success,
1481 : custom error. See also fd_log_collector_program_failure(). */
1482 : FD_FN_CONST char const *
1483 144 : fd_executor_instr_strerror( int err ) {
1484 :
1485 144 : switch( err ) {
1486 0 : case FD_EXECUTOR_INSTR_SUCCESS : return ""; // not used
1487 0 : case FD_EXECUTOR_INSTR_ERR_GENERIC_ERR : return "generic instruction error";
1488 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_ARG : return "invalid program argument";
1489 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA : return "invalid instruction data";
1490 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA : return "invalid account data for instruction";
1491 0 : case FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL : return "account data too small for instruction";
1492 0 : case FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS : return "insufficient funds for instruction";
1493 0 : case FD_EXECUTOR_INSTR_ERR_INCORRECT_PROGRAM_ID : return "incorrect program id for instruction";
1494 0 : case FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE : return "missing required signature for instruction";
1495 0 : case FD_EXECUTOR_INSTR_ERR_ACC_ALREADY_INITIALIZED : return "instruction requires an uninitialized account";
1496 0 : case FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT : return "instruction requires an initialized account";
1497 0 : case FD_EXECUTOR_INSTR_ERR_UNBALANCED_INSTR : return "sum of account balances before and after instruction do not match";
1498 0 : case FD_EXECUTOR_INSTR_ERR_MODIFIED_PROGRAM_ID : return "instruction illegally modified the program id of an account";
1499 0 : case FD_EXECUTOR_INSTR_ERR_EXTERNAL_ACCOUNT_LAMPORT_SPEND : return "instruction spent from the balance of an account it does not own";
1500 0 : case FD_EXECUTOR_INSTR_ERR_EXTERNAL_DATA_MODIFIED : return "instruction modified data of an account it does not own";
1501 0 : case FD_EXECUTOR_INSTR_ERR_READONLY_LAMPORT_CHANGE : return "instruction changed the balance of a read-only account";
1502 48 : case FD_EXECUTOR_INSTR_ERR_READONLY_DATA_MODIFIED : return "instruction modified data of a read-only account";
1503 0 : case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_MODIFIED : return "instruction changed executable bit of an account";
1504 0 : case FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS : return "insufficient account keys for instruction";
1505 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";
1506 0 : case FD_EXECUTOR_INSTR_ERR_ACC_BORROW_FAILED : return "instruction tries to borrow reference for an account which is already borrowed";
1507 0 : case FD_EXECUTOR_INSTR_ERR_ACC_BORROW_OUTSTANDING : return "instruction left account with an outstanding borrowed reference";
1508 0 : case FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR : return ""; // custom handling via txn_ctx->err.custom_err
1509 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_ERR : return "program returned invalid error code";
1510 0 : case FD_EXECUTOR_INSTR_ERR_EXECUTABLE_ACCOUNT_NOT_RENT_EXEMPT : return "executable accounts must be rent exempt";
1511 0 : case FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID : return "Unsupported program id";
1512 0 : case FD_EXECUTOR_INSTR_ERR_CALL_DEPTH : return "Cross-program invocation call depth too deep";
1513 0 : case FD_EXECUTOR_INSTR_ERR_MISSING_ACC : return "An account required by the instruction is missing";
1514 0 : case FD_EXECUTOR_INSTR_ERR_REENTRANCY_NOT_ALLOWED : return "Cross-program invocation reentrancy not allowed for this instruction";
1515 0 : case FD_EXECUTOR_INSTR_ERR_MAX_SEED_LENGTH_EXCEEDED : return "Length of the seed is too long for address generation";
1516 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_SEEDS : return "Provided seeds do not result in a valid address";
1517 96 : case FD_EXECUTOR_INSTR_ERR_INVALID_REALLOC : return "Failed to reallocate account data";
1518 0 : case FD_EXECUTOR_INSTR_ERR_COMPUTE_BUDGET_EXCEEDED : return "Computational budget exceeded";
1519 0 : case FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION : return "Cross-program invocation with unauthorized signer or writable account";
1520 0 : case FD_EXECUTOR_INSTR_ERR_PROGRAM_ENVIRONMENT_SETUP_FAILURE : return "Failed to create program execution environment";
1521 0 : case FD_EXECUTOR_INSTR_ERR_PROGRAM_FAILED_TO_COMPLETE : return "Program failed to complete";
1522 0 : case FD_EXECUTOR_INSTR_ERR_ACC_IMMUTABLE : return "Account is immutable";
1523 0 : case FD_EXECUTOR_INSTR_ERR_INCORRECT_AUTHORITY : return "Incorrect authority provided";
1524 0 : case FD_EXECUTOR_INSTR_ERR_BORSH_IO_ERROR : return "Failed to serialize or deserialize account data"; // truncated
1525 0 : case FD_EXECUTOR_INSTR_ERR_ACC_NOT_RENT_EXEMPT : return "An account does not have enough lamports to be rent-exempt";
1526 0 : case FD_EXECUTOR_INSTR_ERR_INVALID_ACC_OWNER : return "Invalid account owner";
1527 0 : case FD_EXECUTOR_INSTR_ERR_ARITHMETIC_OVERFLOW : return "Program arithmetic overflowed";
1528 0 : case FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR : return "Unsupported sysvar";
1529 0 : case FD_EXECUTOR_INSTR_ERR_ILLEGAL_OWNER : return "Provided owner is not allowed";
1530 0 : case FD_EXECUTOR_INSTR_ERR_MAX_ACCS_DATA_ALLOCS_EXCEEDED : return "Accounts data allocations exceeded the maximum allowed per transaction";
1531 0 : case FD_EXECUTOR_INSTR_ERR_MAX_ACCS_EXCEEDED : return "Max accounts exceeded";
1532 0 : case FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED : return "Max instruction trace length exceeded";
1533 0 : case FD_EXECUTOR_INSTR_ERR_BUILTINS_MUST_CONSUME_CUS : return "Builtin programs must consume compute units";
1534 0 : default: break;
1535 144 : }
1536 :
1537 0 : return "";
1538 144 : }
|