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