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