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