Line data Source code
1 :
2 : #include "generated/invoke.pb.h"
3 : #undef FD_SCRATCH_USE_HANDHOLDING
4 : #define FD_SCRATCH_USE_HANDHOLDING 1
5 : #include "fd_exec_instr_test.h"
6 : #include "../fd_acc_mgr.h"
7 : #include "../fd_account.h"
8 : #include "../fd_executor.h"
9 : #include "../fd_runtime.h"
10 : #include "../program/fd_bpf_loader_program.h"
11 : #include "../program/fd_bpf_program_util.h"
12 : #include "../program/fd_builtin_programs.h"
13 : #include "../context/fd_exec_epoch_ctx.h"
14 : #include "../context/fd_exec_slot_ctx.h"
15 : #include "../context/fd_exec_txn_ctx.h"
16 : #include "../sysvar/fd_sysvar_recent_hashes.h"
17 : #include "../sysvar/fd_sysvar_last_restart_slot.h"
18 : #include "../sysvar/fd_sysvar_slot_hashes.h"
19 : #include "../sysvar/fd_sysvar_stake_history.h"
20 : #include "../sysvar/fd_sysvar_epoch_rewards.h"
21 : #include "../../../funk/fd_funk.h"
22 : #include "../../../util/bits/fd_float.h"
23 : #include "../../../ballet/sbpf/fd_sbpf_loader.h"
24 : #include "../../../ballet/elf/fd_elf.h"
25 : #include "../../vm/fd_vm.h"
26 : #include <assert.h>
27 : #include "../sysvar/fd_sysvar_cache.h"
28 : #include "../sysvar/fd_sysvar_epoch_schedule.h"
29 : #include "../sysvar/fd_sysvar_clock.h"
30 : #include "../../../ballet/pack/fd_pack.h"
31 : #include "fd_vm_test.h"
32 : #include "../../vm/test_vm_util.h"
33 :
34 : #pragma GCC diagnostic ignored "-Wformat-extra-args"
35 :
36 : /* LOGFMT_REPORT is the log prefix for instruction processing tests */
37 :
38 : #define LOGFMT_REPORT "%s"
39 : static FD_TL char _report_prefix[100] = {0};
40 :
41 : #define REPORTV( level, fmt, ... ) \
42 0 : FD_LOG_##level(( LOGFMT_REPORT fmt, _report_prefix, __VA_ARGS__ ))
43 :
44 0 : #define REPORT( level, fmt ) REPORTV( level, fmt, 0 )
45 :
46 : #define REPORT_ACCTV( level, addr, fmt, ... ) \
47 : do { \
48 : char _acct_log_private_addr[ FD_BASE58_ENCODED_32_SZ ]; \
49 : void const * _acct_log_private_addr_ptr = (addr); \
50 : fd_acct_addr_cstr( _acct_log_private_addr, _acct_log_private_addr_ptr ); \
51 : REPORTV( level, " account %s: " fmt, _acct_log_private_addr, __VA_ARGS__ ); \
52 : } while(0);
53 :
54 : #define REPORT_ACCT( level, addr, fmt ) REPORT_ACCTV( level, addr, fmt, 0 )
55 :
56 : /* Define routine to sort accounts to support query-by-pubkey via
57 : binary search. */
58 :
59 : #define SORT_NAME sort_pubkey_p
60 : #define SORT_KEY_T void const *
61 : #define SORT_BEFORE(a,b) ( memcmp( (a), (b), sizeof(fd_pubkey_t) )<0 )
62 : #include "../../../util/tmpl/fd_sort.c"
63 : #include "../../vm/fd_vm_base.h"
64 :
65 : struct __attribute__((aligned(32UL))) fd_exec_instr_test_runner_private {
66 : fd_funk_t * funk;
67 : fd_spad_t * spad;
68 : };
69 :
70 : ulong
71 3676050 : fd_exec_instr_test_runner_align( void ) {
72 3676050 : return alignof(fd_exec_instr_test_runner_t);
73 3676050 : }
74 :
75 : ulong
76 408450 : fd_exec_instr_test_runner_footprint( void ) {
77 408450 : ulong l = FD_LAYOUT_INIT;
78 408450 : l = FD_LAYOUT_APPEND( l, fd_exec_instr_test_runner_align(), sizeof(fd_exec_instr_test_runner_t) );
79 408450 : l = FD_LAYOUT_APPEND( l, fd_funk_align(), fd_funk_footprint() );
80 408450 : return FD_LAYOUT_FINI( l, fd_exec_instr_test_runner_align() );
81 408450 : }
82 :
83 : fd_exec_instr_test_runner_t *
84 : fd_exec_instr_test_runner_new( void * mem,
85 : void * spad_mem,
86 408450 : ulong wksp_tag ) {
87 408450 : FD_SCRATCH_ALLOC_INIT( l, mem );
88 408450 : void * runner_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_exec_instr_test_runner_align(), sizeof(fd_exec_instr_test_runner_t) );
89 408450 : void * funk_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_funk_align(), fd_funk_footprint() );
90 408450 : FD_SCRATCH_ALLOC_FINI( l, fd_exec_instr_test_runner_align() );
91 :
92 408450 : ulong txn_max = 4+fd_tile_cnt();
93 408450 : ulong rec_max = 1024UL;
94 408450 : fd_funk_t * funk = fd_funk_join( fd_funk_new( funk_mem, wksp_tag, (ulong)fd_tickcount(), txn_max, rec_max ) );
95 408450 : if( FD_UNLIKELY( !funk ) ) {
96 0 : FD_LOG_WARNING(( "fd_funk_new() failed" ));
97 0 : return NULL;
98 0 : }
99 :
100 408450 : fd_exec_instr_test_runner_t * runner = runner_mem;
101 408450 : runner->funk = funk;
102 :
103 : /* Create spad */
104 408450 : runner->spad = fd_spad_join( fd_spad_new( spad_mem, FD_RUNTIME_TRANSACTION_EXECUTION_FOOTPRINT_FUZZ ) );
105 408450 : fd_spad_push_debug( runner->spad );
106 408450 : return runner;
107 408450 : }
108 :
109 : void *
110 408450 : fd_exec_instr_test_runner_delete( fd_exec_instr_test_runner_t * runner ) {
111 408450 : if( FD_UNLIKELY( !runner ) ) return NULL;
112 408450 : fd_funk_delete( fd_funk_leave( runner->funk ) );
113 408450 : runner->funk = NULL;
114 408450 : if( FD_UNLIKELY( fd_spad_verify( runner->spad ) ) ) {
115 0 : FD_LOG_ERR(( "fd_spad_verify() failed" ));
116 0 : }
117 408450 : fd_spad_pop_debug( runner->spad );
118 408450 : if( FD_UNLIKELY( fd_spad_frame_used( runner->spad )!=0 ) ) {
119 0 : FD_LOG_ERR(( "stray spad frame frame_used=%lu", fd_spad_frame_used( runner->spad ) ));
120 0 : }
121 408450 : runner->spad = NULL;
122 408450 : return runner;
123 408450 : }
124 :
125 : fd_spad_t *
126 333126 : fd_exec_instr_test_runner_get_spad( fd_exec_instr_test_runner_t * runner ) {
127 333126 : return runner->spad;
128 333126 : }
129 :
130 : static int
131 12741 : fd_double_is_normal( double dbl ) {
132 12741 : ulong x = fd_dblbits( dbl );
133 12741 : int is_denorm =
134 12741 : ( fd_dblbits_bexp( x ) == 0 ) &
135 12741 : ( fd_dblbits_mant( x ) != 0 );
136 12741 : int is_inf =
137 12741 : ( fd_dblbits_bexp( x ) == 2047 ) &
138 12741 : ( fd_dblbits_mant( x ) == 0 );
139 12741 : int is_nan =
140 12741 : ( fd_dblbits_bexp( x ) == 2047 ) &
141 12741 : ( fd_dblbits_mant( x ) != 0 );
142 12741 : return !( is_denorm | is_inf | is_nan );
143 12741 : }
144 :
145 : static int
146 : _load_account( fd_borrowed_account_t * acc,
147 : fd_acc_mgr_t * acc_mgr,
148 : fd_funk_txn_t * funk_txn,
149 607302 : fd_exec_test_acct_state_t const * state ) {
150 607302 : fd_borrowed_account_init( acc );
151 607302 : ulong size = 0UL;
152 607302 : if( state->data ) size = state->data->size;
153 :
154 607302 : fd_pubkey_t pubkey[1]; memcpy( pubkey, state->address, sizeof(fd_pubkey_t) );
155 :
156 : /* Account must not yet exist */
157 607302 : if( FD_UNLIKELY( fd_acc_mgr_view_raw( acc_mgr, funk_txn, pubkey, NULL, NULL, NULL) ) )
158 0 : return 0;
159 :
160 607302 : assert( acc_mgr->funk );
161 0 : assert( acc_mgr->funk->magic == FD_FUNK_MAGIC );
162 0 : fd_funk_start_write( acc_mgr->funk );
163 607302 : int err = fd_acc_mgr_modify( /* acc_mgr */ acc_mgr,
164 607302 : /* txn */ funk_txn,
165 607302 : /* pubkey */ pubkey,
166 607302 : /* do_create */ 1,
167 607302 : /* min_data_sz */ size,
168 607302 : acc );
169 607302 : assert( err==FD_ACC_MGR_SUCCESS );
170 607302 : if( state->data ) fd_memcpy( acc->data, state->data->bytes, size );
171 :
172 607302 : acc->starting_lamports = state->lamports;
173 607302 : acc->starting_dlen = size;
174 607302 : acc->meta->info.lamports = state->lamports;
175 607302 : acc->meta->info.executable = state->executable;
176 607302 : acc->meta->info.rent_epoch = state->rent_epoch;
177 607302 : acc->meta->dlen = size;
178 607302 : memcpy( acc->meta->info.owner, state->owner, sizeof(fd_pubkey_t) );
179 :
180 : /* make the account read-only by default */
181 607302 : acc->meta = NULL;
182 607302 : acc->data = NULL;
183 607302 : acc->rec = NULL;
184 607302 : fd_funk_end_write( acc_mgr->funk );
185 :
186 607302 : return 1;
187 607302 : }
188 :
189 : static int
190 : _load_txn_account( fd_borrowed_account_t * acc,
191 : fd_acc_mgr_t * acc_mgr,
192 : fd_funk_txn_t * funk_txn,
193 49704 : fd_exec_test_acct_state_t const * state ) {
194 : // In the Agave transaction fuzzing harness, accounts with 0 lamports are not saved in the accounts db.
195 : // When they are fetched for transactions, the fields of the account are 0-set.
196 49704 : fd_exec_test_acct_state_t account_state_to_save = FD_EXEC_TEST_ACCT_STATE_INIT_ZERO;
197 49704 : memcpy( account_state_to_save.address, state->address, sizeof(fd_pubkey_t) );
198 :
199 : // Restore the account state if it has lamports
200 49704 : if( state->lamports ) {
201 49197 : account_state_to_save = *state;
202 49197 : }
203 :
204 49704 : return _load_account( acc, acc_mgr, funk_txn, &account_state_to_save );
205 49704 : }
206 :
207 : static int
208 : _restore_feature_flags( fd_exec_epoch_ctx_t * epoch_ctx,
209 407859 : fd_exec_test_feature_set_t const * feature_set ) {
210 407859 : fd_features_disable_all( &epoch_ctx->features );
211 12421401 : for( ulong j=0UL; j < feature_set->features_count; j++ ) {
212 12013542 : ulong prefix = feature_set->features[j];
213 12013542 : fd_feature_id_t const * id = fd_feature_id_query( prefix );
214 12013542 : if( FD_UNLIKELY( !id ) ) {
215 0 : FD_LOG_WARNING(( "unsupported feature ID 0x%016lx", prefix ));
216 0 : return 0;
217 0 : }
218 : /* Enabled since genesis */
219 12013542 : fd_features_set( &epoch_ctx->features, id, 0UL );
220 12013542 : }
221 407859 : return 1;
222 407859 : }
223 :
224 : int
225 : fd_exec_test_instr_context_create( fd_exec_instr_test_runner_t * runner,
226 : fd_exec_instr_ctx_t * ctx,
227 : fd_exec_test_instr_context_t const * test_ctx,
228 395118 : bool is_syscall ) {
229 395118 : memset( ctx, 0, sizeof(fd_exec_instr_ctx_t) );
230 :
231 395118 : fd_funk_t * funk = runner->funk;
232 :
233 : /* Generate unique ID for funk txn */
234 :
235 395118 : fd_funk_txn_xid_t xid[1] = {0};
236 395118 : xid[0] = fd_funk_generate_xid();
237 :
238 : /* Create temporary funk transaction and scratch contexts */
239 :
240 395118 : fd_funk_start_write( funk );
241 395118 : fd_funk_txn_t * funk_txn = fd_funk_txn_prepare( funk, NULL, xid, 1 );
242 395118 : fd_funk_end_write( funk );
243 395118 : fd_scratch_push();
244 :
245 395118 : ulong vote_acct_max = MAX_TX_ACCOUNT_LOCKS;
246 :
247 : /* Allocate contexts */
248 395118 : uchar * epoch_ctx_mem = fd_scratch_alloc( fd_exec_epoch_ctx_align(), fd_exec_epoch_ctx_footprint( vote_acct_max ) );
249 395118 : uchar * slot_ctx_mem = fd_scratch_alloc( FD_EXEC_SLOT_CTX_ALIGN, FD_EXEC_SLOT_CTX_FOOTPRINT );
250 395118 : uchar * txn_ctx_mem = fd_scratch_alloc( FD_EXEC_TXN_CTX_ALIGN, FD_EXEC_TXN_CTX_FOOTPRINT );
251 :
252 395118 : fd_exec_epoch_ctx_t * epoch_ctx = fd_exec_epoch_ctx_join( fd_exec_epoch_ctx_new( epoch_ctx_mem, vote_acct_max ) );
253 395118 : fd_exec_slot_ctx_t * slot_ctx = fd_exec_slot_ctx_join ( fd_exec_slot_ctx_new ( slot_ctx_mem, fd_spad_virtual( runner->spad ) ) );
254 395118 : fd_exec_txn_ctx_t * txn_ctx = fd_exec_txn_ctx_join ( fd_exec_txn_ctx_new ( txn_ctx_mem ) );
255 :
256 395118 : assert( epoch_ctx );
257 0 : assert( slot_ctx );
258 :
259 0 : ctx->slot_ctx = slot_ctx;
260 395118 : ctx->txn_ctx = txn_ctx;
261 :
262 : /* Set up epoch context. Defaults obtained from GenesisConfig::Default() */
263 395118 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( epoch_ctx );
264 395118 : epoch_bank->rent.lamports_per_uint8_year = 3480;
265 395118 : epoch_bank->rent.exemption_threshold = 2;
266 395118 : epoch_bank->rent.burn_percent = 50;
267 :
268 : /* Create account manager */
269 :
270 395118 : fd_acc_mgr_t * acc_mgr = fd_acc_mgr_new( fd_scratch_alloc( FD_ACC_MGR_ALIGN, FD_ACC_MGR_FOOTPRINT ), funk );
271 395118 : assert( acc_mgr );
272 :
273 : /* Set up slot context */
274 :
275 0 : slot_ctx->epoch_ctx = epoch_ctx;
276 395118 : slot_ctx->funk_txn = funk_txn;
277 395118 : slot_ctx->acc_mgr = acc_mgr;
278 :
279 : /* Restore feature flags */
280 :
281 395118 : fd_exec_test_feature_set_t const * feature_set = &test_ctx->epoch_context.features;
282 395118 : if( !_restore_feature_flags( epoch_ctx, feature_set ) ) {
283 0 : return 0;
284 0 : }
285 :
286 : /* TODO: Restore slot_bank */
287 :
288 395118 : fd_slot_bank_new( &slot_ctx->slot_bank );
289 395118 : fd_block_block_hash_entry_t * recent_block_hashes = deq_fd_block_block_hash_entry_t_alloc( slot_ctx->valloc, FD_SYSVAR_RECENT_HASHES_CAP );
290 395118 : slot_ctx->slot_bank.recent_block_hashes.hashes = recent_block_hashes;
291 395118 : fd_block_block_hash_entry_t * recent_block_hash = deq_fd_block_block_hash_entry_t_push_tail_nocopy( recent_block_hashes );
292 395118 : fd_memset( recent_block_hash, 0, sizeof(fd_block_block_hash_entry_t) );
293 :
294 : /* Set up txn context */
295 395118 : fd_exec_txn_ctx_from_exec_slot_ctx( slot_ctx, txn_ctx );
296 395118 : fd_exec_txn_ctx_setup_basic( txn_ctx );
297 :
298 395118 : txn_ctx->funk_txn = funk_txn;
299 395118 : txn_ctx->compute_unit_limit = test_ctx->cu_avail;
300 395118 : txn_ctx->compute_meter = test_ctx->cu_avail;
301 395118 : txn_ctx->vote_accounts_pool = NULL;
302 395118 : txn_ctx->spad = runner->spad;
303 395118 : txn_ctx->has_program_id = 1;
304 :
305 : /* Set up instruction context */
306 :
307 395118 : fd_instr_info_t * info = fd_valloc_malloc( fd_scratch_virtual(), 8UL, sizeof(fd_instr_info_t) );
308 395118 : assert( info );
309 0 : memset( info, 0, sizeof(fd_instr_info_t) );
310 :
311 395118 : if( test_ctx->data ) {
312 60390 : info->data_sz = (ushort)test_ctx->data->size;
313 60390 : info->data = test_ctx->data->bytes;
314 60390 : }
315 :
316 395118 : memcpy( info->program_id_pubkey.uc, test_ctx->program_id, sizeof(fd_pubkey_t) );
317 :
318 : /* Prepare borrowed account table (correctly handles aliasing) */
319 :
320 395118 : if( FD_UNLIKELY( test_ctx->accounts_count > MAX_TX_ACCOUNT_LOCKS ) ) {
321 0 : REPORT( NOTICE, "too many accounts" );
322 0 : return 0;
323 0 : }
324 :
325 : /* Load accounts into database */
326 :
327 395118 : assert( acc_mgr->funk );
328 :
329 0 : fd_borrowed_account_t * borrowed_accts = txn_ctx->borrowed_accounts;
330 395118 : fd_memset( borrowed_accts, 0, test_ctx->accounts_count * sizeof(fd_borrowed_account_t) );
331 395118 : txn_ctx->accounts_cnt = test_ctx->accounts_count;
332 :
333 952716 : for( ulong j=0UL; j < test_ctx->accounts_count; j++ ) {
334 557598 : memcpy( &(txn_ctx->accounts[j]), test_ctx->accounts[j].address, sizeof(fd_pubkey_t) );
335 557598 : if( !_load_account( &borrowed_accts[j], acc_mgr, funk_txn, &test_ctx->accounts[j] ) ) {
336 0 : return 0;
337 0 : }
338 :
339 557598 : if( borrowed_accts[j].const_meta ) {
340 557598 : uchar * data = fd_spad_alloc_debug( txn_ctx->spad, FD_ACCOUNT_REC_ALIGN, FD_ACC_TOT_SZ_MAX );
341 557598 : ulong dlen = borrowed_accts[j].const_meta->dlen;
342 557598 : fd_memcpy( data, borrowed_accts[j].const_meta, sizeof(fd_account_meta_t)+dlen );
343 557598 : borrowed_accts[j].const_meta = (fd_account_meta_t*)data;
344 557598 : borrowed_accts[j].const_data = data + sizeof(fd_account_meta_t);
345 557598 : }
346 557598 : }
347 :
348 : /* Load in executable accounts */
349 952716 : for( ulong i = 0; i < txn_ctx->accounts_cnt; i++ ) {
350 557598 : if ( memcmp( borrowed_accts[i].const_meta->info.owner, fd_solana_bpf_loader_deprecated_program_id.key, sizeof(fd_pubkey_t) ) != 0
351 557598 : && memcmp( borrowed_accts[i].const_meta->info.owner, fd_solana_bpf_loader_program_id.key, sizeof(fd_pubkey_t) ) != 0
352 557598 : && memcmp( borrowed_accts[i].const_meta->info.owner, fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) != 0
353 557598 : && memcmp( borrowed_accts[i].const_meta->info.owner, fd_solana_bpf_loader_v4_program_id.key, sizeof(fd_pubkey_t) ) != 0
354 557598 : ) {
355 442377 : continue;
356 442377 : }
357 :
358 115221 : fd_account_meta_t const * meta = borrowed_accts[i].const_meta ? borrowed_accts[i].const_meta : borrowed_accts[i].meta;
359 115221 : if (meta == NULL) {
360 0 : static const fd_account_meta_t sentinel = { .magic = FD_ACCOUNT_META_MAGIC };
361 0 : borrowed_accts[i].const_meta = &sentinel;
362 0 : borrowed_accts[i].starting_lamports = 0UL;
363 0 : borrowed_accts[i].starting_dlen = 0UL;
364 0 : continue;
365 0 : }
366 :
367 115221 : if( meta->info.executable ) {
368 102117 : FD_BORROWED_ACCOUNT_DECL(owner_borrowed_account);
369 102117 : int err = fd_acc_mgr_view( txn_ctx->acc_mgr, txn_ctx->funk_txn, (fd_pubkey_t *)meta->info.owner, owner_borrowed_account );
370 102117 : if( FD_UNLIKELY( err ) ) {
371 71676 : borrowed_accts[i].starting_owner_dlen = 0;
372 71676 : } else {
373 30441 : borrowed_accts[i].starting_owner_dlen = owner_borrowed_account->const_meta->dlen;
374 30441 : }
375 102117 : }
376 :
377 115221 : if ( FD_UNLIKELY( 0 == memcmp(meta->info.owner, fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t)) ) ) {
378 52776 : fd_bpf_upgradeable_loader_state_t program_loader_state = {0};
379 52776 : int err = 0;
380 52776 : if( FD_UNLIKELY( !read_bpf_upgradeable_loader_state_for_program( txn_ctx, (uchar) i, &program_loader_state, &err ) ) ) {
381 33456 : continue;
382 33456 : }
383 :
384 19320 : fd_pubkey_t * programdata_acc = &program_loader_state.inner.program.programdata_address;
385 19320 : fd_borrowed_account_t * executable_account = fd_borrowed_account_init( &txn_ctx->executable_accounts[txn_ctx->executable_cnt] );
386 19320 : fd_acc_mgr_view(txn_ctx->acc_mgr, txn_ctx->funk_txn, programdata_acc, executable_account);
387 19320 : txn_ctx->executable_cnt++;
388 19320 : }
389 115221 : }
390 :
391 : /* Add accounts to bpf program cache */
392 395118 : fd_funk_start_write( acc_mgr->funk );
393 395118 : fd_bpf_scan_and_create_bpf_program_cache_entry( slot_ctx, funk_txn );
394 395118 : fd_funk_end_write( acc_mgr->funk );
395 :
396 : /* Restore sysvar cache */
397 395118 : fd_sysvar_cache_restore( slot_ctx->sysvar_cache, acc_mgr, funk_txn );
398 :
399 : /* Fill missing sysvar cache values with defaults */
400 : /* We create mock accounts for each of the sysvars and hardcode the data fields before loading it into the account manager */
401 : /* We use Agave sysvar defaults for data field values */
402 :
403 : /* Clock */
404 : // https://github.com/firedancer-io/solfuzz-agave/blob/agave-v2.0/src/lib.rs#L466-L474
405 395118 : if( !slot_ctx->sysvar_cache->has_clock ) {
406 363654 : slot_ctx->sysvar_cache->has_clock = 1;
407 363654 : fd_sol_sysvar_clock_t sysvar_clock = {
408 363654 : .slot = 10,
409 363654 : .epoch_start_timestamp = 0,
410 363654 : .epoch = 0,
411 363654 : .leader_schedule_epoch = 0,
412 363654 : .unix_timestamp = 0
413 363654 : };
414 363654 : memcpy( slot_ctx->sysvar_cache->val_clock, &sysvar_clock, sizeof(fd_sol_sysvar_clock_t) );
415 363654 : }
416 :
417 : /* Epoch schedule */
418 : // https://github.com/firedancer-io/solfuzz-agave/blob/agave-v2.0/src/lib.rs#L476-L483
419 395118 : if ( !slot_ctx->sysvar_cache->has_epoch_schedule ) {
420 383508 : slot_ctx->sysvar_cache->has_epoch_schedule = 1;
421 383508 : fd_epoch_schedule_t sysvar_epoch_schedule = {
422 383508 : .slots_per_epoch = 432000,
423 383508 : .leader_schedule_slot_offset = 432000,
424 383508 : .warmup = 1,
425 383508 : .first_normal_epoch = 14,
426 383508 : .first_normal_slot = 524256
427 383508 : };
428 383508 : memcpy( slot_ctx->sysvar_cache->val_epoch_schedule, &sysvar_epoch_schedule, sizeof(fd_epoch_schedule_t) );
429 383508 : }
430 :
431 : /* Rent */
432 : // https://github.com/firedancer-io/solfuzz-agave/blob/agave-v2.0/src/lib.rs#L487-L500
433 395118 : if ( !slot_ctx->sysvar_cache->has_rent ) {
434 378627 : slot_ctx->sysvar_cache->has_rent = 1;
435 378627 : fd_rent_t sysvar_rent = {
436 378627 : .lamports_per_uint8_year = 3480,
437 378627 : .exemption_threshold = 2.0,
438 378627 : .burn_percent = 50
439 378627 : };
440 378627 : memcpy( slot_ctx->sysvar_cache->val_rent, &sysvar_rent, sizeof(fd_rent_t) );
441 378627 : }
442 :
443 395118 : if ( !slot_ctx->sysvar_cache->has_last_restart_slot ) {
444 391797 : slot_ctx->sysvar_cache->has_last_restart_slot = 1;
445 :
446 391797 : fd_sol_sysvar_last_restart_slot_t restart = {.slot = 5000};
447 :
448 391797 : memcpy( slot_ctx->sysvar_cache->val_last_restart_slot, &restart, sizeof(fd_sol_sysvar_last_restart_slot_t) );
449 391797 : }
450 :
451 : /* Set slot bank variables */
452 395118 : slot_ctx->slot_bank.slot = fd_sysvar_cache_clock( slot_ctx->sysvar_cache )->slot;
453 :
454 : /* Handle undefined behavior if sysvars are malicious (!!!) */
455 :
456 : /* Override epoch bank rent setting */
457 395118 : fd_rent_t const * rent = fd_sysvar_cache_rent( slot_ctx->sysvar_cache );
458 395118 : if( rent ) {
459 395118 : epoch_bank->rent = *rent;
460 395118 : }
461 :
462 : /* Override most recent blockhash if given */
463 395118 : fd_recent_block_hashes_t const * rbh = fd_sysvar_cache_recent_block_hashes( slot_ctx->sysvar_cache );
464 395118 : if( rbh && !deq_fd_block_block_hash_entry_t_empty( rbh->hashes ) ) {
465 4896 : fd_block_block_hash_entry_t const * last = deq_fd_block_block_hash_entry_t_peek_tail_const( rbh->hashes );
466 4896 : if( last ) {
467 4896 : *recent_block_hash = *last;
468 4896 : slot_ctx->slot_bank.lamports_per_signature = last->fee_calculator.lamports_per_signature;
469 4896 : slot_ctx->prev_lamports_per_signature = last->fee_calculator.lamports_per_signature;
470 4896 : }
471 4896 : }
472 :
473 : /* Load instruction accounts */
474 :
475 395118 : if( FD_UNLIKELY( test_ctx->instr_accounts_count > MAX_TX_ACCOUNT_LOCKS ) ) {
476 0 : REPORT( NOTICE, "too many instruction accounts" );
477 0 : return 0;
478 0 : }
479 :
480 395118 : uchar acc_idx_seen[256] = {0};
481 820356 : for( ulong j=0UL; j < test_ctx->instr_accounts_count; j++ ) {
482 425238 : uint index = test_ctx->instr_accounts[j].index;
483 425238 : if( index >= test_ctx->accounts_count ) {
484 0 : REPORTV( NOTICE, " instruction account index out of range (%u > %u)", index, test_ctx->instr_accounts_count );
485 0 : return 0;
486 0 : }
487 :
488 425238 : fd_borrowed_account_t * acc = &borrowed_accts[ index ];
489 425238 : uint flags = 0;
490 425238 : flags |= test_ctx->instr_accounts[j].is_writable ? FD_INSTR_ACCT_FLAGS_IS_WRITABLE : 0;
491 425238 : flags |= test_ctx->instr_accounts[j].is_signer ? FD_INSTR_ACCT_FLAGS_IS_SIGNER : 0;
492 :
493 425238 : info->borrowed_accounts[j] = acc;
494 425238 : info->acct_flags [j] = (uchar)flags;
495 425238 : memcpy( info->acct_pubkeys[j].uc, acc->pubkey, sizeof(fd_pubkey_t) );
496 425238 : info->acct_txn_idxs[j] = (uchar) index;
497 :
498 425238 : if( test_ctx->instr_accounts[j].is_writable ) {
499 214833 : acc->meta = (void *)acc->const_meta;
500 214833 : acc->data = (void *)acc->const_data;
501 214833 : acc->rec = (void *)acc->const_rec;
502 214833 : }
503 :
504 425238 : if (acc_idx_seen[index]) {
505 19275 : info->is_duplicate[j] = 1;
506 19275 : }
507 425238 : acc_idx_seen[index] = 1;
508 425238 : }
509 395118 : info->acct_cnt = (uchar)test_ctx->instr_accounts_count;
510 :
511 : // FIXME: Specifically for CPI syscalls, flag guard this?
512 395118 : fd_instr_info_sum_account_lamports( info, &info->starting_lamports_h, &info->starting_lamports_l );
513 :
514 : /* The remaining checks enforce that the program is in the accounts list. */
515 395118 : bool found_program_id = false;
516 778725 : for( uint i = 0; i < test_ctx->accounts_count; i++ ) {
517 447771 : if( 0 == memcmp( test_ctx->accounts[i].address, test_ctx->program_id, sizeof(fd_pubkey_t) ) ) {
518 64164 : info->program_id = (uchar) i;
519 64164 : found_program_id = true;
520 64164 : break;
521 64164 : }
522 447771 : }
523 :
524 : /* Early returning only happens in instruction execution. */
525 395118 : if( !is_syscall && !found_program_id ) {
526 0 : FD_LOG_NOTICE(( " Unable to find program_id in accounts" ));
527 0 : return 0;
528 0 : }
529 :
530 395118 : ctx->epoch_ctx = epoch_ctx;
531 395118 : ctx->funk_txn = funk_txn;
532 395118 : ctx->acc_mgr = acc_mgr;
533 395118 : ctx->instr = info;
534 :
535 395118 : fd_log_collector_init( &ctx->txn_ctx->log_collector, 1 );
536 395118 : fd_base58_encode_32( ctx->instr->program_id_pubkey.uc, NULL, ctx->program_id_base58 );
537 :
538 395118 : return 1;
539 395118 : }
540 :
541 : static void
542 262311 : _add_to_data(uchar ** data, void const * to_add, ulong size) {
543 6072951 : while( size-- ) {
544 5810640 : **data = *(uchar *)to_add;
545 5810640 : (*data)++;
546 5810640 : to_add = (uchar *)to_add + 1;
547 5810640 : }
548 262311 : }
549 :
550 : static void
551 89790 : _add_compact_u16(uchar ** data, ushort to_add) {
552 89790 : fd_bincode_encode_ctx_t encode_ctx = { .data = *data, .dataend = *data + 3 }; // Up to 3 bytes
553 89790 : fd_bincode_compact_u16_encode( &to_add, &encode_ctx );
554 89790 : *data = (uchar *) encode_ctx.data;
555 89790 : }
556 :
557 : static fd_execute_txn_task_info_t *
558 : _txn_context_create_and_exec( fd_exec_instr_test_runner_t * runner,
559 : fd_exec_slot_ctx_t * slot_ctx,
560 12741 : fd_exec_test_txn_context_t const * test_ctx ) {
561 12741 : uchar empty_bytes[64] = { 0 };
562 12741 : fd_funk_t * funk = runner->funk;
563 :
564 : /* Generate unique ID for funk txn */
565 :
566 12741 : fd_funk_txn_xid_t xid[1] = {0};
567 12741 : xid[0] = fd_funk_generate_xid();
568 :
569 : /* Create temporary funk transaction and scratch contexts */
570 :
571 12741 : fd_funk_start_write( runner->funk );
572 12741 : fd_funk_txn_t * funk_txn = fd_funk_txn_prepare( funk, NULL, xid, 1 );
573 12741 : fd_funk_end_write( runner->funk );
574 :
575 12741 : ulong vote_acct_max = MAX_TX_ACCOUNT_LOCKS;
576 :
577 : /* Allocate contexts */
578 12741 : uchar * epoch_ctx_mem = fd_scratch_alloc( fd_exec_epoch_ctx_align(), fd_exec_epoch_ctx_footprint( vote_acct_max ) );
579 12741 : fd_exec_epoch_ctx_t * epoch_ctx = fd_exec_epoch_ctx_join( fd_exec_epoch_ctx_new( epoch_ctx_mem, vote_acct_max ) );
580 :
581 12741 : assert( epoch_ctx );
582 0 : assert( slot_ctx );
583 :
584 : /* Set up epoch context */
585 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( epoch_ctx );
586 :
587 : /* Create account manager */
588 12741 : fd_acc_mgr_t * acc_mgr = fd_acc_mgr_new( fd_scratch_alloc( FD_ACC_MGR_ALIGN, FD_ACC_MGR_FOOTPRINT ), funk );
589 12741 : assert( acc_mgr );
590 :
591 : /* Set up slot context */
592 :
593 0 : slot_ctx->epoch_ctx = epoch_ctx;
594 12741 : slot_ctx->funk_txn = funk_txn;
595 12741 : slot_ctx->acc_mgr = acc_mgr;
596 :
597 : /* Restore feature flags */
598 :
599 12741 : fd_exec_test_feature_set_t const * feature_set = &test_ctx->epoch_ctx.features;
600 12741 : if( !_restore_feature_flags( epoch_ctx, feature_set ) ) {
601 0 : return NULL;
602 0 : }
603 :
604 : /* Restore slot bank */
605 12741 : fd_slot_bank_new( &slot_ctx->slot_bank );
606 :
607 : /* Initialize builtin accounts */
608 12741 : fd_funk_start_write( runner->funk );
609 12741 : fd_builtin_programs_init( slot_ctx );
610 12741 : fd_funk_end_write( runner->funk );
611 :
612 : /* Load account states into funk (note this is different from the account keys):
613 : Account state = accounts to populate Funk
614 : Account keys = account keys that the transaction needs */
615 62445 : for( ulong i = 0; i < test_ctx->tx.message.account_shared_data_count; i++ ) {
616 : /* Load the accounts into the account manager
617 : Borrowed accounts get reset anyways - we just need to load the account somewhere */
618 49704 : FD_BORROWED_ACCOUNT_DECL(acc);
619 49704 : _load_txn_account( acc, acc_mgr, funk_txn, &test_ctx->tx.message.account_shared_data[i] );
620 49704 : }
621 :
622 : /* Restore sysvar cache */
623 12741 : fd_sysvar_cache_restore( slot_ctx->sysvar_cache, acc_mgr, funk_txn );
624 :
625 : /* Add accounts to bpf program cache */
626 12741 : fd_funk_start_write( runner->funk );
627 12741 : fd_bpf_scan_and_create_bpf_program_cache_entry( slot_ctx, funk_txn );
628 :
629 : /* Default slot */
630 12741 : ulong slot = test_ctx->slot_ctx.slot ? test_ctx->slot_ctx.slot : 10; // Arbitrary default > 0
631 :
632 : /* Set slot bank variables (defaults obtained from GenesisConfig::default() in Agave) */
633 12741 : slot_ctx->slot_bank.slot = slot;
634 12741 : slot_ctx->slot_bank.prev_slot = slot_ctx->slot_bank.slot - 1; // Can underflow, but its fine since it will correctly be ULONG_MAX
635 12741 : slot_ctx->slot_bank.fee_rate_governor.burn_percent = 50;
636 12741 : slot_ctx->slot_bank.fee_rate_governor.min_lamports_per_signature = 0;
637 12741 : slot_ctx->slot_bank.fee_rate_governor.max_lamports_per_signature = 0;
638 12741 : slot_ctx->slot_bank.fee_rate_governor.target_lamports_per_signature = 10000;
639 12741 : slot_ctx->slot_bank.fee_rate_governor.target_signatures_per_slot = 20000;
640 12741 : slot_ctx->slot_bank.lamports_per_signature = 5000;
641 12741 : slot_ctx->prev_lamports_per_signature = 5000;
642 :
643 : /* Set epoch bank variables if not present (defaults obtained from GenesisConfig::default() in Agave) */
644 12741 : fd_epoch_schedule_t default_epoch_schedule = {
645 12741 : .slots_per_epoch = 432000,
646 12741 : .leader_schedule_slot_offset = 432000,
647 12741 : .warmup = 1,
648 12741 : .first_normal_epoch = 14,
649 12741 : .first_normal_slot = 524256
650 12741 : };
651 12741 : fd_rent_t default_rent = {
652 12741 : .lamports_per_uint8_year = 3480,
653 12741 : .exemption_threshold = 2.0,
654 12741 : .burn_percent = 50
655 12741 : };
656 12741 : epoch_bank->epoch_schedule = default_epoch_schedule;
657 12741 : epoch_bank->rent_epoch_schedule = default_epoch_schedule;
658 12741 : epoch_bank->rent = default_rent;
659 12741 : epoch_bank->ticks_per_slot = 64;
660 12741 : epoch_bank->slots_per_year = SECONDS_PER_YEAR * (1000000000.0 / (double)6250000) / (double)epoch_bank->ticks_per_slot;
661 :
662 : // Override default values if provided
663 12741 : if( slot_ctx->sysvar_cache->has_epoch_schedule ) {
664 192 : epoch_bank->epoch_schedule = *slot_ctx->sysvar_cache->val_epoch_schedule;
665 192 : epoch_bank->rent_epoch_schedule = *slot_ctx->sysvar_cache->val_epoch_schedule;
666 192 : }
667 :
668 12741 : if( slot_ctx->sysvar_cache->has_rent ) {
669 753 : epoch_bank->rent = *slot_ctx->sysvar_cache->val_rent;
670 753 : }
671 :
672 : /* Provide default slot hashes of size 1 if not provided */
673 12741 : if( !slot_ctx->sysvar_cache->has_slot_hashes ) {
674 11718 : fd_slot_hash_t * slot_hashes = deq_fd_slot_hash_t_alloc( fd_scratch_virtual(), 1 );
675 11718 : fd_slot_hash_t * dummy_elem = deq_fd_slot_hash_t_push_tail_nocopy( slot_hashes );
676 11718 : memset( dummy_elem, 0, sizeof(fd_slot_hash_t) );
677 11718 : fd_slot_hashes_t default_slot_hashes = { .hashes = slot_hashes };
678 11718 : fd_sysvar_slot_hashes_init( slot_ctx, &default_slot_hashes );
679 11718 : }
680 :
681 : /* Provide default stake history if not provided */
682 12741 : if( !slot_ctx->sysvar_cache->has_stake_history ) {
683 : // Provide a 0-set default entry
684 12276 : fd_stake_history_entry_t entry = {0};
685 12276 : fd_sysvar_stake_history_init( slot_ctx );
686 12276 : fd_sysvar_stake_history_update( slot_ctx, &entry );
687 12276 : }
688 :
689 : /* Provide default last restart slot sysvar if not provided */
690 12741 : if( !slot_ctx->sysvar_cache->has_last_restart_slot ) {
691 12729 : fd_sysvar_last_restart_slot_init( slot_ctx );
692 12729 : }
693 :
694 : /* Provide a default clock if not present */
695 12741 : if( !slot_ctx->sysvar_cache->has_clock ) {
696 9924 : fd_sysvar_clock_init( slot_ctx );
697 9924 : fd_sysvar_clock_update( slot_ctx );
698 9924 : }
699 :
700 : /* Epoch schedule and rent get set from the epoch bank */
701 12741 : fd_sysvar_epoch_schedule_init( slot_ctx );
702 12741 : fd_sysvar_rent_init( slot_ctx );
703 :
704 : /* Set the epoch rewards sysvar if partition epoch rewards feature is enabled
705 :
706 : TODO: The init parameters are not exactly conformant with Agave's epoch rewards sysvar. We should
707 : be calling `fd_begin_partitioned_rewards` with the same parameters as Agave. However,
708 : we just need the `active` field to be conformant due to a single Stake program check.
709 : THIS MAY CHANGE IN THE FUTURE. If there are other parts of transaction execution that use
710 : the epoch rewards sysvar, we may need to update this.
711 : */
712 12741 : if ( (
713 12741 : FD_FEATURE_ACTIVE( slot_ctx, enable_partitioned_epoch_reward ) ||
714 12741 : FD_FEATURE_ACTIVE( slot_ctx, partitioned_epoch_rewards_superfeature )
715 12741 : ) && !slot_ctx->sysvar_cache->has_epoch_rewards ) {
716 1845 : fd_point_value_t point_value = {0};
717 1845 : fd_hash_t const * last_hash = test_ctx->blockhash_queue_count > 0 ? (fd_hash_t const *)test_ctx->blockhash_queue[0]->bytes : (fd_hash_t const *)empty_bytes;
718 1845 : fd_sysvar_epoch_rewards_init( slot_ctx, 0UL, 0UL, 2UL, 1UL, point_value, last_hash);
719 1845 : }
720 :
721 : /* Restore sysvar cache (again, since we may need to provide default sysvars) */
722 12741 : fd_sysvar_cache_restore( slot_ctx->sysvar_cache, acc_mgr, funk_txn );
723 :
724 : /* A NaN rent exemption threshold is U.B. in Solana Labs */
725 12741 : fd_rent_t const * rent = fd_sysvar_cache_rent( slot_ctx->sysvar_cache );
726 12741 : if( ( !fd_double_is_normal( rent->exemption_threshold ) ) |
727 12741 : ( rent->exemption_threshold < 0.0 ) |
728 12741 : ( rent->exemption_threshold > 999.0 ) |
729 12741 : ( rent->lamports_per_uint8_year > UINT_MAX ) |
730 12741 : ( rent->burn_percent > 100 ) )
731 0 : return NULL;
732 :
733 : /* Blockhash queue is given in txn message. We need to populate the following three:
734 : - slot_ctx->slot_bank.block_hash_queue (TODO: Does more than just the last_hash need to be populated?)
735 : - sysvar_cache_recent_block_hashes
736 : - slot_ctx->slot_bank.recent_block_hashes */
737 12741 : ulong num_blockhashes = test_ctx->blockhash_queue_count;
738 :
739 : /* Recent blockhashes init */
740 12741 : fd_block_block_hash_entry_t * recent_block_hashes = deq_fd_block_block_hash_entry_t_alloc( slot_ctx->valloc, FD_SYSVAR_RECENT_HASHES_CAP );
741 :
742 : /* Blockhash queue init */
743 12741 : slot_ctx->slot_bank.block_hash_queue.max_age = FD_BLOCKHASH_QUEUE_MAX_ENTRIES;
744 12741 : slot_ctx->slot_bank.block_hash_queue.ages_root = NULL;
745 12741 : slot_ctx->slot_bank.block_hash_queue.ages_pool = fd_hash_hash_age_pair_t_map_alloc( slot_ctx->valloc, 400 );
746 12741 : slot_ctx->slot_bank.block_hash_queue.last_hash = fd_valloc_malloc( slot_ctx->valloc, FD_HASH_ALIGN, FD_HASH_FOOTPRINT );
747 :
748 : // Save lamports per signature for most recent blockhash, if sysvar cache contains recent block hashes
749 12741 : fd_recent_block_hashes_t const * rbh = fd_sysvar_cache_recent_block_hashes( slot_ctx->sysvar_cache );
750 12741 : if( rbh && !deq_fd_block_block_hash_entry_t_empty( rbh->hashes ) ) {
751 1050 : fd_block_block_hash_entry_t const * last = deq_fd_block_block_hash_entry_t_peek_head_const( rbh->hashes );
752 1050 : if( last ) {
753 1050 : slot_ctx->slot_bank.lamports_per_signature = last->fee_calculator.lamports_per_signature;
754 1050 : slot_ctx->prev_lamports_per_signature = last->fee_calculator.lamports_per_signature;
755 1050 : }
756 1050 : }
757 :
758 : // Clear and reset recent block hashes sysvar
759 12741 : slot_ctx->sysvar_cache->has_recent_block_hashes = 1;
760 12741 : slot_ctx->sysvar_cache->val_recent_block_hashes->hashes = recent_block_hashes;
761 12741 : slot_ctx->slot_bank.recent_block_hashes.hashes = recent_block_hashes;
762 :
763 : // Blockhash_queue[end] = last (latest) hash
764 : // Blockhash_queue[0] = genesis hash
765 12741 : if( num_blockhashes > 0 ) {
766 8319 : memcpy( &epoch_bank->genesis_hash, test_ctx->blockhash_queue[0]->bytes, sizeof(fd_hash_t) );
767 :
768 876558 : for( ulong i = 0; i < num_blockhashes; ++i ) {
769 : // Recent block hashes cap is 150 (actually 151), while blockhash queue capacity is 300 (actually 301)
770 868239 : fd_block_block_hash_entry_t blockhash_entry;
771 868239 : memcpy( &blockhash_entry.blockhash, test_ctx->blockhash_queue[i]->bytes, sizeof(fd_hash_t) );
772 868239 : slot_ctx->slot_bank.poh = blockhash_entry.blockhash;
773 868239 : fd_sysvar_recent_hashes_update( slot_ctx );
774 868239 : }
775 8319 : } else {
776 : // Add a default empty blockhash and use it as genesis
777 4422 : num_blockhashes = 1;
778 4422 : memcpy( &epoch_bank->genesis_hash, empty_bytes, sizeof(fd_hash_t) );
779 4422 : fd_block_block_hash_entry_t blockhash_entry;
780 4422 : memcpy( &blockhash_entry.blockhash, empty_bytes, sizeof(fd_hash_t) );
781 4422 : slot_ctx->slot_bank.poh = blockhash_entry.blockhash;
782 4422 : fd_sysvar_recent_hashes_update( slot_ctx );
783 4422 : }
784 12741 : fd_funk_end_write( runner->funk );
785 :
786 : /* Create the raw txn (https://solana.com/docs/core/transactions#transaction-size) */
787 12741 : uchar * txn_raw_begin = fd_scratch_alloc( alignof(uchar), 10000 ); // max txn size is 1232 but we allocate extra for safety
788 12741 : uchar * txn_raw_cur_ptr = txn_raw_begin;
789 :
790 : /* Compact array of signatures (https://solana.com/docs/core/transactions#transaction)
791 : Note that although documentation interchangably refers to the signature cnt as a compact-u16
792 : and a u8, the max signature cnt is capped at 48 (due to txn size limits), so u8 and compact-u16
793 : is represented the same way anyways and can be parsed identically. */
794 : // Note: always create a valid txn with 1+ signatures, add an empty signature if none is provided
795 12741 : uchar signature_cnt = fd_uchar_max( 1, (uchar) test_ctx->tx.signatures_count );
796 12741 : _add_to_data( &txn_raw_cur_ptr, &signature_cnt, sizeof(uchar) );
797 43740 : for( uchar i = 0; i < signature_cnt; ++i ) {
798 30999 : _add_to_data( &txn_raw_cur_ptr, test_ctx->tx.signatures && test_ctx->tx.signatures[i] ? test_ctx->tx.signatures[i]->bytes : empty_bytes, FD_TXN_SIGNATURE_SZ );
799 30999 : }
800 :
801 : /* Message */
802 : /* For v0 transactions, the highest bit of the num_required_signatures is set, and an extra byte is used for the version.
803 : https://solanacookbook.com/guides/versioned-transactions.html#versioned-transactions-transactionv0
804 :
805 : We will always create a transaction with at least 1 signature, and cap the signature count to 127 to avoid
806 : collisions with the header_b0 tag. */
807 12741 : uchar num_required_signatures = fd_uchar_max( 1, fd_uchar_min( 127, (uchar) test_ctx->tx.message.header.num_required_signatures ) );
808 12741 : if( !test_ctx->tx.message.is_legacy ) {
809 12228 : uchar header_b0 = (uchar) 0x80UL;
810 12228 : _add_to_data( &txn_raw_cur_ptr, &header_b0, sizeof(uchar) );
811 12228 : }
812 :
813 : /* Header (3 bytes) (https://solana.com/docs/core/transactions#message-header) */
814 12741 : _add_to_data( &txn_raw_cur_ptr, &num_required_signatures, sizeof(uchar) );
815 12741 : _add_to_data( &txn_raw_cur_ptr, &test_ctx->tx.message.header.num_readonly_signed_accounts, sizeof(uchar) );
816 12741 : _add_to_data( &txn_raw_cur_ptr, &test_ctx->tx.message.header.num_readonly_unsigned_accounts, sizeof(uchar) );
817 :
818 : /* Compact array of account addresses (https://solana.com/docs/core/transactions#compact-array-format) */
819 : // Array length is a compact u16
820 12741 : ushort num_acct_keys = (ushort) test_ctx->tx.message.account_keys_count;
821 12741 : _add_compact_u16( &txn_raw_cur_ptr, num_acct_keys );
822 59679 : for( ushort i = 0; i < num_acct_keys; ++i ) {
823 46938 : _add_to_data( &txn_raw_cur_ptr, test_ctx->tx.message.account_keys[i]->bytes, sizeof(fd_pubkey_t) );
824 46938 : }
825 :
826 : /* Recent blockhash (32 bytes) (https://solana.com/docs/core/transactions#recent-blockhash) */
827 : // Note: add an empty blockhash if none is provided
828 12741 : _add_to_data( &txn_raw_cur_ptr, test_ctx->tx.message.recent_blockhash ? test_ctx->tx.message.recent_blockhash->bytes : empty_bytes, sizeof(fd_hash_t) );
829 :
830 : /* Compact array of instructions (https://solana.com/docs/core/transactions#array-of-instructions) */
831 : // Instruction count is a compact u16
832 12741 : ushort instr_count = (ushort) test_ctx->tx.message.instructions_count;
833 12741 : _add_compact_u16( &txn_raw_cur_ptr, instr_count );
834 30825 : for( ushort i = 0; i < instr_count; ++i ) {
835 : // Program ID index
836 18084 : uchar program_id_index = (uchar) test_ctx->tx.message.instructions[i].program_id_index;
837 18084 : _add_to_data( &txn_raw_cur_ptr, &program_id_index, sizeof(uchar) );
838 :
839 : // Compact array of account addresses
840 18084 : ushort acct_count = (ushort) test_ctx->tx.message.instructions[i].accounts_count;
841 18084 : _add_compact_u16( &txn_raw_cur_ptr, acct_count );
842 65100 : for( ushort j = 0; j < acct_count; ++j ) {
843 47016 : uchar account_index = (uchar) test_ctx->tx.message.instructions[i].accounts[j];
844 47016 : _add_to_data( &txn_raw_cur_ptr, &account_index, sizeof(uchar) );
845 47016 : }
846 :
847 : // Compact array of 8-bit data
848 18084 : pb_bytes_array_t * data = test_ctx->tx.message.instructions[i].data;
849 18084 : if( data ) {
850 14907 : ushort data_len = (ushort) data->size;
851 14907 : _add_compact_u16( &txn_raw_cur_ptr, data_len );
852 14907 : _add_to_data( &txn_raw_cur_ptr, data->bytes, data_len );
853 14907 : } else {
854 3177 : _add_compact_u16( &txn_raw_cur_ptr, 0 );
855 3177 : }
856 18084 : }
857 :
858 : /* Address table lookups (N/A for legacy transactions) */
859 12741 : ushort addr_table_cnt = 0;
860 12741 : if( !test_ctx->tx.message.is_legacy ) {
861 : /* Compact array of address table lookups (https://solanacookbook.com/guides/versioned-transactions.html#compact-array-of-address-table-lookups) */
862 : // NOTE: The diagram is slightly wrong - the account key is a 32 byte pubkey, not a u8
863 12228 : addr_table_cnt = (ushort) test_ctx->tx.message.address_table_lookups_count;
864 12228 : _add_compact_u16( &txn_raw_cur_ptr, addr_table_cnt );
865 20184 : for( ushort i = 0; i < addr_table_cnt; ++i ) {
866 : // Account key
867 7956 : _add_to_data( &txn_raw_cur_ptr, test_ctx->tx.message.address_table_lookups[i].account_key, sizeof(fd_pubkey_t) );
868 :
869 : // Compact array of writable indexes
870 7956 : ushort writable_count = (ushort) test_ctx->tx.message.address_table_lookups[i].writable_indexes_count;
871 7956 : _add_compact_u16( &txn_raw_cur_ptr, writable_count );
872 20151 : for( ushort j = 0; j < writable_count; ++j ) {
873 12195 : uchar writable_index = (uchar) test_ctx->tx.message.address_table_lookups[i].writable_indexes[j];
874 12195 : _add_to_data( &txn_raw_cur_ptr, &writable_index, sizeof(uchar) );
875 12195 : }
876 :
877 : // Compact array of readonly indexes
878 7956 : ushort readonly_count = (ushort) test_ctx->tx.message.address_table_lookups[i].readonly_indexes_count;
879 7956 : _add_compact_u16( &txn_raw_cur_ptr, readonly_count );
880 16239 : for( ushort j = 0; j < readonly_count; ++j ) {
881 8283 : uchar readonly_index = (uchar) test_ctx->tx.message.address_table_lookups[i].readonly_indexes[j];
882 8283 : _add_to_data( &txn_raw_cur_ptr, &readonly_index, sizeof(uchar) );
883 8283 : }
884 7956 : }
885 12228 : }
886 :
887 : /* Set up txn descriptor from raw data */
888 12741 : fd_txn_t * txn_descriptor = (fd_txn_t *) fd_scratch_alloc( fd_txn_align(), fd_txn_footprint( instr_count, addr_table_cnt ) );
889 12741 : ushort txn_raw_sz = (ushort) (txn_raw_cur_ptr - txn_raw_begin);
890 12741 : if( !fd_txn_parse( txn_raw_begin, txn_raw_sz, txn_descriptor, NULL ) ) {
891 0 : FD_LOG_WARNING(("could not parse txn descriptor"));
892 0 : return NULL;
893 0 : }
894 :
895 : /* Run txn preparation phases and execution
896 : NOTE: This should be modified accordingly if transaction setup logic changes */
897 12741 : fd_txn_p_t * txn = fd_scratch_alloc( alignof(fd_txn_p_t), sizeof(fd_txn_p_t) );
898 12741 : memcpy( txn->payload, txn_raw_begin, txn_raw_sz );
899 12741 : txn->payload_sz = (ulong) txn_raw_sz;
900 12741 : txn->flags = FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
901 12741 : memcpy( txn->_, txn_descriptor, fd_txn_footprint( instr_count, addr_table_cnt ) );
902 :
903 12741 : fd_execute_txn_task_info_t * task_info = fd_scratch_alloc( alignof(fd_execute_txn_task_info_t), sizeof(fd_execute_txn_task_info_t) );
904 12741 : memset( task_info, 0, sizeof(fd_execute_txn_task_info_t) );
905 12741 : task_info->txn = txn;
906 :
907 12741 : fd_tpool_t tpool[1];
908 12741 : tpool->worker_cnt = 1;
909 12741 : tpool->worker_max = 1;
910 :
911 12741 : fd_runtime_prepare_txns_start( slot_ctx, task_info, txn, 1UL );
912 :
913 : /* Setup the spad for account allocation */
914 12741 : task_info->txn_ctx->spad = runner->spad;
915 :
916 12741 : fd_runtime_pre_execute_check( task_info );
917 :
918 12741 : if( !task_info->exec_res ) {
919 5190 : task_info->txn->flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
920 5190 : task_info->exec_res = fd_execute_txn( task_info->txn_ctx );
921 5190 : }
922 :
923 12741 : slot_ctx->slot_bank.collected_execution_fees += task_info->txn_ctx->execution_fee;
924 12741 : slot_ctx->slot_bank.collected_priority_fees += task_info->txn_ctx->priority_fee;
925 12741 : slot_ctx->slot_bank.collected_rent += task_info->txn_ctx->collected_rent;
926 12741 : return task_info;
927 12741 : }
928 :
929 : void
930 : fd_exec_test_instr_context_destroy( fd_exec_instr_test_runner_t * runner,
931 395118 : fd_exec_instr_ctx_t * ctx ) {
932 395118 : if( !ctx ) return;
933 395118 : fd_exec_slot_ctx_t * slot_ctx = (fd_exec_slot_ctx_t *)ctx->slot_ctx;
934 395118 : if( !slot_ctx ) return;
935 395118 : fd_acc_mgr_t * acc_mgr = slot_ctx->acc_mgr;
936 395118 : fd_funk_txn_t * funk_txn = slot_ctx->funk_txn;
937 :
938 395118 : fd_exec_slot_ctx_free( slot_ctx );
939 395118 : fd_acc_mgr_delete( acc_mgr );
940 395118 : fd_scratch_pop();
941 :
942 395118 : fd_funk_start_write( runner->funk );
943 395118 : fd_funk_txn_cancel( runner->funk, funk_txn, 1 );
944 395118 : fd_funk_end_write( runner->funk );
945 :
946 395118 : ctx->slot_ctx = NULL;
947 395118 : }
948 :
949 : static void
950 : _txn_context_destroy( fd_exec_instr_test_runner_t * runner,
951 12741 : fd_exec_slot_ctx_t * slot_ctx ) {
952 12741 : if( !slot_ctx ) return; // This shouldn't be false either
953 12741 : fd_acc_mgr_t * acc_mgr = slot_ctx->acc_mgr;
954 12741 : fd_funk_txn_t * funk_txn = slot_ctx->funk_txn;
955 :
956 12741 : fd_exec_slot_ctx_free( slot_ctx );
957 12741 : fd_acc_mgr_delete( acc_mgr );
958 :
959 12741 : fd_funk_start_write( runner->funk );
960 12741 : fd_funk_txn_cancel( runner->funk, funk_txn, 1 );
961 12741 : fd_funk_end_write( runner->funk );
962 12741 : }
963 :
964 : static fd_sbpf_syscalls_t *
965 : lookup_syscall_func( fd_sbpf_syscalls_t *syscalls,
966 : const char *syscall_name,
967 12711 : size_t len) {
968 12711 : ulong i;
969 :
970 12711 : if (!syscall_name) return NULL;
971 :
972 492615 : for (i = 0; i < fd_sbpf_syscalls_slot_cnt(); ++i) {
973 492615 : if (!fd_sbpf_syscalls_key_inval(syscalls[i].key) && syscalls[i].name && strlen(syscalls[i].name) == len) {
974 19116 : if (!memcmp(syscalls[i].name, syscall_name, len)) {
975 12711 : return syscalls + i;
976 12711 : }
977 19116 : }
978 492615 : }
979 :
980 0 : return NULL;
981 12711 : }
982 :
983 : ulong
984 : fd_exec_instr_test_run( fd_exec_instr_test_runner_t * runner,
985 : void const * input_,
986 : void ** output_,
987 : void * output_buf,
988 61992 : ulong output_bufsz ) {
989 61992 : fd_exec_test_instr_context_t const * input = fd_type_pun_const( input_ );
990 61992 : fd_exec_test_instr_effects_t ** output = fd_type_pun( output_ );
991 :
992 : /* Convert the Protobuf inputs to a fd_exec context */
993 61992 : fd_exec_instr_ctx_t ctx[1];
994 61992 : if( !fd_exec_test_instr_context_create( runner, ctx, input, false ) ) {
995 0 : fd_exec_test_instr_context_destroy( runner, ctx );
996 0 : return 0UL;
997 0 : }
998 :
999 61992 : fd_instr_info_t * instr = (fd_instr_info_t *) ctx->instr;
1000 :
1001 : /* Execute the test */
1002 61992 : int exec_result = fd_execute_instr(ctx->txn_ctx, instr);
1003 :
1004 : /* Allocate space to capture outputs */
1005 :
1006 61992 : ulong output_end = (ulong)output_buf + output_bufsz;
1007 61992 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
1008 :
1009 61992 : fd_exec_test_instr_effects_t * effects =
1010 61992 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_instr_effects_t),
1011 61992 : sizeof (fd_exec_test_instr_effects_t) );
1012 61992 : if( FD_UNLIKELY( _l > output_end ) ) {
1013 0 : fd_exec_test_instr_context_destroy( runner, ctx );
1014 0 : return 0UL;
1015 0 : }
1016 61992 : fd_memset( effects, 0, sizeof(fd_exec_test_instr_effects_t) );
1017 :
1018 : /* Capture error code */
1019 :
1020 61992 : effects->result = -exec_result;
1021 61992 : effects->cu_avail = ctx->txn_ctx->compute_meter;
1022 :
1023 61992 : if( exec_result == FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR ) {
1024 5502 : effects->custom_err = ctx->txn_ctx->custom_err;
1025 5502 : }
1026 :
1027 : /* Allocate space for captured accounts */
1028 61992 : ulong modified_acct_cnt = ctx->txn_ctx->accounts_cnt;
1029 :
1030 61992 : fd_exec_test_acct_state_t * modified_accts =
1031 61992 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_acct_state_t),
1032 61992 : sizeof (fd_exec_test_acct_state_t) * modified_acct_cnt );
1033 61992 : if( FD_UNLIKELY( _l > output_end ) ) {
1034 0 : fd_exec_test_instr_context_destroy( runner, ctx );
1035 0 : return 0;
1036 0 : }
1037 61992 : effects->modified_accounts = modified_accts;
1038 61992 : effects->modified_accounts_count = 0UL;
1039 :
1040 : /* Capture borrowed accounts */
1041 :
1042 585501 : for( ulong j=0UL; j < ctx->txn_ctx->accounts_cnt; j++ ) {
1043 523509 : fd_borrowed_account_t * acc = &ctx->txn_ctx->borrowed_accounts[j];
1044 523509 : if( !acc->const_meta ) {
1045 0 : continue;
1046 0 : }
1047 :
1048 523509 : ulong modified_idx = effects->modified_accounts_count;
1049 523509 : assert( modified_idx < modified_acct_cnt );
1050 :
1051 0 : fd_exec_test_acct_state_t * out_acct = &effects->modified_accounts[ modified_idx ];
1052 523509 : memset( out_acct, 0, sizeof(fd_exec_test_acct_state_t) );
1053 : /* Copy over account content */
1054 :
1055 523509 : memcpy( out_acct->address, acc->pubkey, sizeof(fd_pubkey_t) );
1056 523509 : out_acct->lamports = acc->const_meta->info.lamports;
1057 523509 : if( acc->const_meta->dlen>0UL ) {
1058 414762 : out_acct->data =
1059 414762 : FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t),
1060 414762 : PB_BYTES_ARRAY_T_ALLOCSIZE( acc->const_meta->dlen ) );
1061 414762 : if( FD_UNLIKELY( _l > output_end ) ) {
1062 0 : fd_exec_test_instr_context_destroy( runner, ctx );
1063 0 : return 0UL;
1064 0 : }
1065 414762 : out_acct->data->size = (pb_size_t)acc->const_meta->dlen;
1066 414762 : fd_memcpy( out_acct->data->bytes, acc->const_data, acc->const_meta->dlen );
1067 414762 : }
1068 :
1069 523509 : out_acct->executable = acc->const_meta->info.executable;
1070 523509 : out_acct->rent_epoch = acc->const_meta->info.rent_epoch;
1071 523509 : memcpy( out_acct->owner, acc->const_meta->info.owner, sizeof(fd_pubkey_t) );
1072 :
1073 523509 : effects->modified_accounts_count++;
1074 523509 : }
1075 :
1076 : /* Capture return data */
1077 61992 : fd_txn_return_data_t * return_data = &ctx->txn_ctx->return_data;
1078 61992 : if( return_data->len>0UL ) {
1079 2208 : effects->return_data = FD_SCRATCH_ALLOC_APPEND(l, alignof(pb_bytes_array_t),
1080 2208 : PB_BYTES_ARRAY_T_ALLOCSIZE( return_data->len ) );
1081 2208 : if( FD_UNLIKELY( _l > output_end ) ) {
1082 0 : fd_exec_test_instr_context_destroy( runner, ctx );
1083 0 : return 0UL;
1084 0 : }
1085 2208 : effects->return_data->size = (pb_size_t)return_data->len;
1086 2208 : fd_memcpy( effects->return_data->bytes, return_data->data, return_data->len );
1087 2208 : }
1088 :
1089 61992 : ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
1090 61992 : fd_exec_test_instr_context_destroy( runner, ctx );
1091 :
1092 61992 : *output = effects;
1093 61992 : return actual_end - (ulong)output_buf;
1094 61992 : }
1095 :
1096 : ulong
1097 : fd_exec_txn_test_run( fd_exec_instr_test_runner_t * runner, // Runner only contains funk instance, so we can borrow instr test runner
1098 : void const * input_,
1099 : void ** output_,
1100 : void * output_buf,
1101 12741 : ulong output_bufsz ) {
1102 12741 : fd_exec_test_txn_context_t const * input = fd_type_pun_const( input_ );
1103 12741 : fd_exec_test_txn_result_t ** output = fd_type_pun( output_ );
1104 :
1105 12741 : FD_SCRATCH_SCOPE_BEGIN {
1106 : /* Initialize memory */
1107 12741 : uchar * slot_ctx_mem = fd_scratch_alloc( FD_EXEC_SLOT_CTX_ALIGN, FD_EXEC_SLOT_CTX_FOOTPRINT );
1108 12741 : fd_exec_slot_ctx_t * slot_ctx = fd_exec_slot_ctx_join ( fd_exec_slot_ctx_new ( slot_ctx_mem, fd_spad_virtual( runner->spad ) ) );
1109 :
1110 : /* Create and exec transaction */
1111 12741 : fd_execute_txn_task_info_t * task_info = _txn_context_create_and_exec( runner, slot_ctx, input );
1112 12741 : if( task_info == NULL ) {
1113 0 : _txn_context_destroy( runner, slot_ctx );
1114 0 : return 0UL;
1115 0 : }
1116 12741 : fd_exec_txn_ctx_t * txn_ctx = task_info->txn_ctx;
1117 :
1118 12741 : int exec_res = task_info->exec_res;
1119 :
1120 : /* Start saving txn exec results */
1121 12741 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
1122 12741 : ulong output_end = (ulong)output_buf + output_bufsz;
1123 :
1124 12741 : fd_exec_test_txn_result_t * txn_result =
1125 12741 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_txn_result_t),
1126 12741 : sizeof (fd_exec_test_txn_result_t) );
1127 12741 : if( FD_UNLIKELY( _l > output_end ) ) {
1128 0 : abort();
1129 0 : }
1130 12741 : fd_memset( txn_result, 0, sizeof(fd_exec_test_txn_result_t) );
1131 :
1132 : /* Capture basic results fields */
1133 12741 : txn_result->executed = task_info->txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
1134 12741 : txn_result->sanitization_error = !( task_info->txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS );
1135 12741 : txn_result->has_resulting_state = false;
1136 12741 : txn_result->resulting_state.acct_states_count = 0;
1137 12741 : txn_result->is_ok = !exec_res;
1138 12741 : txn_result->status = (uint32_t) -exec_res;
1139 12741 : txn_result->instruction_error = 0;
1140 12741 : txn_result->instruction_error_index = 0;
1141 12741 : txn_result->custom_error = 0;
1142 12741 : txn_result->executed_units = txn_ctx->compute_unit_limit - txn_ctx->compute_meter;
1143 12741 : txn_result->has_fee_details = false;
1144 :
1145 12741 : if( txn_result->sanitization_error ) {
1146 : /* If exec_res was an instruction error, capture the error number and idx */
1147 7551 : if( exec_res == FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR ) {
1148 4194 : txn_result->instruction_error = (uint32_t) -task_info->txn_ctx->exec_err;
1149 4194 : txn_result->instruction_error_index = (uint32_t) task_info->txn_ctx->instr_err_idx;
1150 :
1151 : /*
1152 : TODO: precompile error codes are not conformant, so we're ignoring custom error codes for them for now. This should be revisited in the future.
1153 : For now, only precompiles throw custom error codes, so we can ignore all custom error codes thrown in the sanitization phase. If this changes,
1154 : this logic will have to be revisited.
1155 :
1156 : if( task_info->txn_ctx->exec_err == FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR ) {
1157 : txn_result->custom_error = txn_ctx->custom_err;
1158 : }
1159 : */
1160 4194 : }
1161 7551 : ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
1162 7551 : _txn_context_destroy( runner, slot_ctx );
1163 :
1164 7551 : *output = txn_result;
1165 7551 : return actual_end - (ulong)output_buf;
1166 7551 : }
1167 :
1168 5190 : txn_result->has_fee_details = true;
1169 5190 : txn_result->fee_details.transaction_fee = slot_ctx->slot_bank.collected_execution_fees;
1170 5190 : txn_result->fee_details.prioritization_fee = slot_ctx->slot_bank.collected_priority_fees;
1171 :
1172 : /* Rent is only collected on successfully loaded transactions */
1173 5190 : txn_result->rent = txn_ctx->collected_rent;
1174 :
1175 : /* At this point, the transaction has executed */
1176 5190 : if( exec_res ) {
1177 : /* Instruction error index must be set for the txn error to be an instruction error */
1178 3486 : if( txn_ctx->instr_err_idx != INT32_MAX ) {
1179 3480 : txn_result->status = (uint32_t) -FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR;
1180 3480 : txn_result->instruction_error = (uint32_t) -exec_res;
1181 3480 : txn_result->instruction_error_index = (uint32_t) txn_ctx->instr_err_idx;
1182 3480 : if( exec_res == FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR ) {
1183 675 : txn_result->custom_error = txn_ctx->custom_err;
1184 675 : }
1185 3480 : } else {
1186 6 : txn_result->status = (uint32_t) -exec_res;
1187 6 : }
1188 3486 : }
1189 :
1190 5190 : if( txn_ctx->return_data.len > 0 ) {
1191 39 : txn_result->return_data = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t),
1192 39 : PB_BYTES_ARRAY_T_ALLOCSIZE( txn_ctx->return_data.len ) );
1193 39 : if( FD_UNLIKELY( _l > output_end ) ) {
1194 0 : abort();
1195 0 : }
1196 :
1197 39 : txn_result->return_data->size = (pb_size_t)txn_ctx->return_data.len;
1198 39 : fd_memcpy( txn_result->return_data->bytes, txn_ctx->return_data.data, txn_ctx->return_data.len );
1199 39 : }
1200 :
1201 : /* Allocate space for captured accounts */
1202 5190 : ulong modified_acct_cnt = txn_ctx->accounts_cnt;
1203 :
1204 5190 : txn_result->has_resulting_state = true;
1205 5190 : txn_result->resulting_state.acct_states =
1206 5190 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_acct_state_t),
1207 5190 : sizeof (fd_exec_test_acct_state_t) * modified_acct_cnt );
1208 5190 : if( FD_UNLIKELY( _l > output_end ) ) {
1209 0 : abort();
1210 0 : }
1211 :
1212 : /* Capture borrowed accounts */
1213 34035 : for( ulong j=0UL; j < txn_ctx->accounts_cnt; j++ ) {
1214 28845 : fd_borrowed_account_t * acc = &txn_ctx->borrowed_accounts[j];
1215 28845 : if( !( fd_txn_account_is_writable_idx( txn_ctx, (int)j ) || j==FD_FEE_PAYER_TXN_IDX ) ) continue;
1216 13326 : assert( acc->meta );
1217 :
1218 0 : ulong modified_idx = txn_result->resulting_state.acct_states_count;
1219 13326 : assert( modified_idx < modified_acct_cnt );
1220 :
1221 0 : fd_exec_test_acct_state_t * out_acct = &txn_result->resulting_state.acct_states[ modified_idx ];
1222 13326 : memset( out_acct, 0, sizeof(fd_exec_test_acct_state_t) );
1223 : /* Copy over account content */
1224 :
1225 13326 : memcpy( out_acct->address, acc->pubkey, sizeof(fd_pubkey_t) );
1226 :
1227 13326 : out_acct->lamports = acc->const_meta->info.lamports;
1228 :
1229 13326 : if( acc->const_meta->dlen > 0 ) {
1230 4773 : out_acct->data =
1231 4773 : FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t),
1232 4773 : PB_BYTES_ARRAY_T_ALLOCSIZE( acc->const_meta->dlen ) );
1233 4773 : if( FD_UNLIKELY( _l > output_end ) ) {
1234 0 : abort();
1235 0 : }
1236 4773 : out_acct->data->size = (pb_size_t)acc->const_meta->dlen;
1237 4773 : fd_memcpy( out_acct->data->bytes, acc->const_data, acc->const_meta->dlen );
1238 4773 : }
1239 :
1240 13326 : out_acct->executable = acc->const_meta->info.executable;
1241 13326 : out_acct->rent_epoch = acc->const_meta->info.rent_epoch;
1242 13326 : memcpy( out_acct->owner, acc->const_meta->info.owner, sizeof(fd_pubkey_t) );
1243 :
1244 13326 : txn_result->resulting_state.acct_states_count++;
1245 13326 : }
1246 :
1247 5190 : ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
1248 5190 : _txn_context_destroy( runner, slot_ctx );
1249 :
1250 5190 : *output = txn_result;
1251 5190 : return actual_end - (ulong)output_buf;
1252 12741 : } FD_SCRATCH_SCOPE_END;
1253 12741 : }
1254 :
1255 :
1256 : ulong
1257 : fd_sbpf_program_load_test_run( FD_PARAM_UNUSED fd_exec_instr_test_runner_t * runner,
1258 : void const * input_,
1259 : void ** output_,
1260 : void * output_buf,
1261 591 : ulong output_bufsz ) {
1262 591 : fd_exec_test_elf_loader_ctx_t const * input = fd_type_pun_const( input_ );
1263 591 : fd_exec_test_elf_loader_effects_t ** output = fd_type_pun( output_ );
1264 :
1265 591 : fd_sbpf_elf_info_t info;
1266 591 : fd_valloc_t valloc = fd_scratch_virtual();
1267 :
1268 591 : if ( FD_UNLIKELY( !input->has_elf || !input->elf.data ) ){
1269 0 : return 0UL;
1270 0 : }
1271 :
1272 591 : ulong elf_sz = input->elf_sz;
1273 591 : void const * _bin;
1274 :
1275 : /* elf_sz will be passed as arguments to elf loader functions.
1276 : pb decoder allocates memory for elf.data based on its actual size,
1277 : not elf_sz !.
1278 : If elf_sz is larger than the size of actual elf data, this may result
1279 : in out-of-bounds accesses which will upset ASAN (however intentional).
1280 : So in this case we just copy the data into a memory region of elf_sz bytes
1281 :
1282 : ! The decoupling of elf_sz and the actual binary size is intentional to test
1283 : underflow/overflow behavior */
1284 591 : if ( elf_sz > input->elf.data->size ){
1285 0 : void * tmp = fd_valloc_malloc( valloc, 1UL, elf_sz );
1286 0 : if ( FD_UNLIKELY( !tmp ) ){
1287 0 : return 0UL;
1288 0 : }
1289 0 : fd_memcpy( tmp, input->elf.data->bytes, input->elf.data->size );
1290 0 : _bin = tmp;
1291 591 : } else {
1292 591 : _bin = input->elf.data->bytes;
1293 591 : }
1294 :
1295 : // Allocate space for captured effects
1296 591 : ulong output_end = (ulong)output_buf + output_bufsz;
1297 591 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
1298 :
1299 591 : fd_exec_test_elf_loader_effects_t * elf_effects =
1300 591 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_elf_loader_effects_t),
1301 591 : sizeof (fd_exec_test_elf_loader_effects_t) );
1302 591 : if( FD_UNLIKELY( _l > output_end ) ) {
1303 : /* return 0 on fuzz-specific failures */
1304 0 : return 0UL;
1305 0 : }
1306 591 : fd_memset( elf_effects, 0, sizeof(fd_exec_test_elf_loader_effects_t) );
1307 :
1308 : /* wrap the loader code in do-while(0) block so that we can exit
1309 : immediately if execution fails at any point */
1310 :
1311 591 : do{
1312 :
1313 591 : if( FD_UNLIKELY( !fd_sbpf_elf_peek( &info, _bin, elf_sz, input->deploy_checks, FD_SBPF_V0, FD_SBPF_V3 ) ) ) {
1314 : /* return incomplete effects on execution failures */
1315 0 : break;
1316 0 : }
1317 :
1318 591 : void* rodata = fd_valloc_malloc( valloc, FD_SBPF_PROG_RODATA_ALIGN, info.rodata_footprint );
1319 591 : FD_TEST( rodata );
1320 :
1321 591 : fd_sbpf_program_t * prog = fd_sbpf_program_new( fd_valloc_malloc( valloc, fd_sbpf_program_align(), fd_sbpf_program_footprint( &info ) ), &info, rodata );
1322 591 : FD_TEST( prog );
1323 :
1324 591 : fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_new( fd_valloc_malloc( valloc, fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() ));
1325 591 : FD_TEST( syscalls );
1326 :
1327 591 : fd_vm_syscall_register_all( syscalls, 0 );
1328 :
1329 591 : int res = fd_sbpf_program_load( prog, _bin, elf_sz, syscalls, input->deploy_checks );
1330 591 : if( FD_UNLIKELY( res ) ) {
1331 3 : break;
1332 3 : }
1333 :
1334 588 : fd_memset( elf_effects, 0, sizeof(fd_exec_test_elf_loader_effects_t) );
1335 588 : elf_effects->rodata_sz = prog->rodata_sz;
1336 :
1337 : // Load rodata section
1338 588 : elf_effects->rodata = FD_SCRATCH_ALLOC_APPEND(l, 8UL, PB_BYTES_ARRAY_T_ALLOCSIZE( prog->rodata_sz ));
1339 588 : if( FD_UNLIKELY( _l > output_end ) ) {
1340 0 : return 0UL;
1341 0 : }
1342 588 : elf_effects->rodata->size = (pb_size_t) prog->rodata_sz;
1343 588 : fd_memcpy( &(elf_effects->rodata->bytes), prog->rodata, prog->rodata_sz );
1344 :
1345 588 : elf_effects->text_cnt = prog->text_cnt;
1346 588 : elf_effects->text_off = prog->text_off;
1347 :
1348 588 : elf_effects->entry_pc = prog->entry_pc;
1349 :
1350 :
1351 588 : pb_size_t calldests_sz = (pb_size_t) fd_sbpf_calldests_cnt( prog->calldests);
1352 588 : elf_effects->calldests_count = calldests_sz;
1353 588 : elf_effects->calldests = FD_SCRATCH_ALLOC_APPEND(l, 8UL, calldests_sz * sizeof(uint64_t));
1354 588 : if( FD_UNLIKELY( _l > output_end ) ) {
1355 0 : return 0UL;
1356 0 : }
1357 :
1358 588 : ulong i = 0;
1359 161052 : for(ulong target_pc = fd_sbpf_calldests_const_iter_init(prog->calldests); !fd_sbpf_calldests_const_iter_done(target_pc);
1360 160464 : target_pc = fd_sbpf_calldests_const_iter_next(prog->calldests, target_pc)) {
1361 160464 : elf_effects->calldests[i] = target_pc;
1362 160464 : ++i;
1363 160464 : }
1364 588 : } while(0);
1365 :
1366 591 : ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
1367 :
1368 591 : *output = elf_effects;
1369 591 : return actual_end - (ulong) output_buf;
1370 591 : }
1371 :
1372 : static fd_exec_test_instr_effects_t const * cpi_exec_effects = NULL;
1373 :
1374 : ulong
1375 : fd_exec_vm_syscall_test_run( fd_exec_instr_test_runner_t * runner,
1376 : void const * input_,
1377 : void ** output_,
1378 : void * output_buf,
1379 12711 : ulong output_bufsz ) {
1380 12711 : fd_exec_test_syscall_context_t const * input = fd_type_pun_const( input_ );
1381 12711 : fd_exec_test_syscall_effects_t ** output = fd_type_pun( output_ );
1382 :
1383 : /* Create execution context */
1384 12711 : const fd_exec_test_instr_context_t * input_instr_ctx = &input->instr_ctx;
1385 12711 : fd_exec_instr_ctx_t ctx[1];
1386 : // Skip extra checks for non-CPI syscalls
1387 12711 : int is_cpi = !strncmp( (const char *)input->syscall_invocation.function_name.bytes, "sol_invoke_signed", 17 );
1388 12711 : int skip_extra_checks = !is_cpi;
1389 :
1390 12711 : if( !fd_exec_test_instr_context_create( runner, ctx, input_instr_ctx, skip_extra_checks ) )
1391 0 : goto error;
1392 12711 : fd_valloc_t valloc = fd_scratch_virtual();
1393 12711 : fd_spad_t * spad = fd_exec_instr_test_runner_get_spad( runner );
1394 :
1395 12711 : if (is_cpi) {
1396 0 : ctx->txn_ctx->instr_info_cnt = 1;
1397 0 : }
1398 :
1399 12711 : ctx->txn_ctx->instr_trace[0].instr_info = (fd_instr_info_t *)ctx->instr;
1400 12711 : ctx->txn_ctx->instr_trace[0].stack_height = 1;
1401 :
1402 12711 : if (is_cpi) {
1403 0 : ctx->txn_ctx->instr_info_cnt = 1;
1404 0 : }
1405 :
1406 12711 : ctx->txn_ctx->instr_trace[0].instr_info = (fd_instr_info_t *)ctx->instr;
1407 12711 : ctx->txn_ctx->instr_trace[0].stack_height = 1;
1408 :
1409 : /* Capture outputs */
1410 12711 : ulong output_end = (ulong)output_buf + output_bufsz;
1411 12711 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
1412 12711 : fd_exec_test_syscall_effects_t * effects =
1413 12711 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_syscall_effects_t),
1414 12711 : sizeof (fd_exec_test_syscall_effects_t) );
1415 12711 : if( FD_UNLIKELY( _l > output_end ) ) {
1416 0 : goto error;
1417 0 : }
1418 :
1419 12711 : if (input->vm_ctx.return_data.program_id && input->vm_ctx.return_data.program_id->size == sizeof(fd_pubkey_t)) {
1420 2214 : fd_memcpy( ctx->txn_ctx->return_data.program_id.uc, input->vm_ctx.return_data.program_id->bytes, sizeof(fd_pubkey_t) );
1421 2214 : ctx->txn_ctx->return_data.len = input->vm_ctx.return_data.data->size;
1422 2214 : fd_memcpy( ctx->txn_ctx->return_data.data, input->vm_ctx.return_data.data->bytes, ctx->txn_ctx->return_data.len );
1423 2214 : }
1424 :
1425 12711 : *effects = (fd_exec_test_syscall_effects_t) FD_EXEC_TEST_SYSCALL_EFFECTS_INIT_ZERO;
1426 :
1427 : /* Set up the VM instance */
1428 12711 : fd_sha256_t _sha[1];
1429 12711 : fd_sha256_t * sha = fd_sha256_join( fd_sha256_new( _sha ) );
1430 12711 : fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_new( fd_valloc_malloc( valloc, fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() ) );
1431 12711 : fd_vm_syscall_register_all( syscalls, 0 );
1432 :
1433 : /* Pull out the memory regions */
1434 12711 : if( !input->has_vm_ctx ) {
1435 0 : goto error;
1436 0 : }
1437 12711 : if( input->has_exec_effects ){
1438 675 : cpi_exec_effects = &input->exec_effects;
1439 675 : }
1440 :
1441 12711 : ulong rodata_sz = input->vm_ctx.rodata ? input->vm_ctx.rodata->size : 0UL;
1442 12711 : uchar * rodata = fd_valloc_malloc( valloc, 8UL, rodata_sz );
1443 12711 : if ( input->vm_ctx.rodata != NULL ) {
1444 2214 : fd_memcpy( rodata, input->vm_ctx.rodata->bytes, rodata_sz );
1445 2214 : }
1446 :
1447 : /* Load input data regions */
1448 12711 : fd_vm_input_region_t * input_regions = NULL;
1449 12711 : uint input_regions_count = 0U;
1450 12711 : if( !!(input->vm_ctx.input_data_regions_count) ) {
1451 2214 : input_regions = fd_spad_alloc_debug( spad, alignof(fd_vm_input_region_t), sizeof(fd_vm_input_region_t) * input->vm_ctx.input_data_regions_count );
1452 2214 : input_regions_count = fd_setup_vm_input_regions( input_regions, input->vm_ctx.input_data_regions, input->vm_ctx.input_data_regions_count, spad );
1453 2214 : if ( !input_regions_count ) {
1454 0 : goto error;
1455 0 : }
1456 2214 : }
1457 :
1458 12711 : if( input->vm_ctx.heap_max > FD_VM_HEAP_MAX ) {
1459 0 : goto error;
1460 0 : }
1461 :
1462 12711 : fd_vm_t * vm = fd_vm_join( fd_vm_new( fd_valloc_malloc( valloc, fd_vm_align(), fd_vm_footprint() ) ) );
1463 12711 : if ( !vm ) {
1464 0 : goto error;
1465 0 : }
1466 :
1467 : /* If the program ID account owner is the v1 BPF loader, then alignment is disabled (controlled by
1468 : the `is_deprecated` flag) */
1469 12711 : uchar program_id_idx = ctx->instr->program_id;
1470 12711 : uchar is_deprecated = ( program_id_idx < ctx->txn_ctx->accounts_cnt ) &&
1471 12711 : ( !memcmp( ctx->txn_ctx->borrowed_accounts[program_id_idx].const_meta->info.owner, fd_solana_bpf_loader_deprecated_program_id.key, sizeof(fd_pubkey_t) ) );
1472 :
1473 12711 : fd_vm_init(
1474 12711 : vm,
1475 12711 : ctx,
1476 12711 : input->vm_ctx.heap_max,
1477 12711 : ctx->txn_ctx->compute_meter,
1478 12711 : rodata,
1479 12711 : rodata_sz,
1480 12711 : NULL, // TODO
1481 12711 : 0, // TODO
1482 12711 : 0, // TODO
1483 12711 : 0, // TODO, text_sz
1484 12711 : 0, // TODO
1485 12711 : NULL, // TODO
1486 12711 : TEST_VM_DEFAULT_SBPF_VERSION,
1487 12711 : syscalls,
1488 12711 : NULL, // TODO
1489 12711 : sha,
1490 12711 : input_regions,
1491 12711 : input_regions_count,
1492 12711 : NULL,
1493 12711 : is_deprecated,
1494 12711 : FD_FEATURE_ACTIVE( ctx->slot_ctx, bpf_account_data_direct_mapping ) );
1495 :
1496 : // Override some execution state values from the syscall fuzzer input
1497 : // This is so we can test if the syscall mutates any of these erroneously
1498 12711 : vm->reg[0] = input->vm_ctx.r0;
1499 12711 : vm->reg[1] = input->vm_ctx.r1;
1500 12711 : vm->reg[2] = input->vm_ctx.r2;
1501 12711 : vm->reg[3] = input->vm_ctx.r3;
1502 12711 : vm->reg[4] = input->vm_ctx.r4;
1503 12711 : vm->reg[5] = input->vm_ctx.r5;
1504 12711 : vm->reg[6] = input->vm_ctx.r6;
1505 12711 : vm->reg[7] = input->vm_ctx.r7;
1506 12711 : vm->reg[8] = input->vm_ctx.r8;
1507 12711 : vm->reg[9] = input->vm_ctx.r9;
1508 12711 : vm->reg[10] = input->vm_ctx.r10;
1509 12711 : vm->reg[11] = input->vm_ctx.r11;
1510 :
1511 : // Override initial part of the heap, if specified the syscall fuzzer input
1512 12711 : if( input->syscall_invocation.heap_prefix ) {
1513 12669 : fd_memcpy( vm->heap, input->syscall_invocation.heap_prefix->bytes,
1514 12669 : fd_ulong_min(input->syscall_invocation.heap_prefix->size, vm->heap_max) );
1515 12669 : }
1516 :
1517 : // Override initial part of the stack, if specified the syscall fuzzer input
1518 12711 : if( input->syscall_invocation.stack_prefix ) {
1519 2214 : fd_memcpy( vm->stack, input->syscall_invocation.stack_prefix->bytes,
1520 2214 : fd_ulong_min(input->syscall_invocation.stack_prefix->size, FD_VM_STACK_MAX) );
1521 2214 : }
1522 :
1523 : // Propogate the acc_regions_meta to the vm
1524 12711 : vm->acc_region_metas = fd_valloc_malloc( valloc, alignof(fd_vm_acc_region_meta_t), sizeof(fd_vm_acc_region_meta_t) * input->vm_ctx.input_data_regions_count );
1525 12711 : fd_setup_vm_acc_region_metas( vm->acc_region_metas, vm, vm->instr_ctx );
1526 :
1527 : // Look up the syscall to execute
1528 12711 : char * syscall_name = (char *)input->syscall_invocation.function_name.bytes;
1529 12711 : fd_sbpf_syscalls_t const * syscall = lookup_syscall_func(syscalls, syscall_name, input->syscall_invocation.function_name.size);
1530 12711 : if( !syscall ) {
1531 0 : goto error;
1532 0 : }
1533 :
1534 : /* Actually invoke the syscall */
1535 12711 : int stack_push_err = fd_instr_stack_push( ctx->txn_ctx, (fd_instr_info_t *)ctx->instr );
1536 12711 : if( FD_UNLIKELY( stack_push_err ) ) {
1537 0 : FD_LOG_WARNING(( "instr stack push err" ));
1538 0 : goto error;
1539 0 : }
1540 12711 : int syscall_err = syscall->func( vm, vm->reg[1], vm->reg[2], vm->reg[3], vm->reg[4], vm->reg[5], &vm->reg[0] );
1541 12711 : int stack_pop_err = fd_instr_stack_pop( ctx->txn_ctx, ctx->instr );
1542 12711 : if( FD_UNLIKELY( stack_pop_err ) ) {
1543 0 : FD_LOG_WARNING(( "instr stack pop err" ));
1544 0 : goto error;
1545 0 : }
1546 12711 : if( syscall_err ) {
1547 : /* In the CPI syscall, certain checks are performed out of order between Firedancer and Agave's
1548 : implementation. Certain checks in FD (whose error codes mapped below)
1549 : do not have a (sequentially) equivalent one in Agave. Thus, it doesn't make sense
1550 : to declare a mismatch if Firedancer fails such a check when Agave doesn't, as long
1551 : as both end up error'ing out at some point. We also have other metrics (namely CU count)
1552 : to rely on. */
1553 :
1554 : /* Certain pre-flight checks are not performed in Agave. These manifest as
1555 : access violations in Agave. The agave_access_violation_mask bitset sets
1556 : the error codes that are expected to be access violations in Agave. */
1557 1935 : if( is_cpi &&
1558 1935 : ( syscall_err == FD_VM_SYSCALL_ERR_TOO_MANY_SIGNERS ||
1559 0 : syscall_err == FD_VM_SYSCALL_ERR_INSTRUCTION_TOO_LARGE ||
1560 0 : syscall_err == FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED ||
1561 0 : syscall_err == FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNT_INFOS_EXCEEDED ) ) {
1562 :
1563 : /* FD performs pre-flight checks that manifest as access violations in Agave */
1564 0 : vm->instr_ctx->txn_ctx->exec_err = FD_VM_ERR_EBPF_ACCESS_VIOLATION;
1565 0 : vm->instr_ctx->txn_ctx->exec_err_kind = FD_EXECUTOR_ERR_KIND_EBPF;
1566 0 : }
1567 :
1568 1935 : fd_log_collector_program_failure( vm->instr_ctx );
1569 1935 : }
1570 :
1571 : /* Capture the effects */
1572 12711 : int exec_err = vm->instr_ctx->txn_ctx->exec_err;
1573 12711 : effects->error = 0;
1574 12711 : if( syscall_err ) {
1575 1935 : if( exec_err==0 ) {
1576 0 : FD_LOG_WARNING(( "TODO: syscall returns error, but exec_err not set. this is probably missing a log." ));
1577 0 : effects->error = -1;
1578 1935 : } else {
1579 1935 : effects->error = (exec_err <= 0) ? -exec_err : -1;
1580 :
1581 : /* Map error kind, equivalent to:
1582 : effects->error_kind = (fd_exec_test_err_kind_t)(vm->instr_ctx->txn_ctx->exec_err_kind + 1); */
1583 1935 : switch (vm->instr_ctx->txn_ctx->exec_err_kind) {
1584 1206 : case FD_EXECUTOR_ERR_KIND_EBPF:
1585 1206 : effects->error_kind = FD_EXEC_TEST_ERR_KIND_EBPF;
1586 1206 : break;
1587 237 : case FD_EXECUTOR_ERR_KIND_SYSCALL:
1588 237 : effects->error_kind = FD_EXEC_TEST_ERR_KIND_SYSCALL;
1589 237 : break;
1590 492 : case FD_EXECUTOR_ERR_KIND_INSTR:
1591 492 : effects->error_kind = FD_EXEC_TEST_ERR_KIND_INSTRUCTION;
1592 492 : break;
1593 0 : default:
1594 0 : effects->error_kind = FD_EXEC_TEST_ERR_KIND_UNSPECIFIED;
1595 0 : break;
1596 1935 : }
1597 1935 : }
1598 1935 : }
1599 12711 : effects->r0 = syscall_err ? 0 : vm->reg[0]; // Save only on success
1600 12711 : effects->cu_avail = (ulong)vm->cu;
1601 :
1602 12711 : if( vm->heap_max ) {
1603 12663 : effects->heap = FD_SCRATCH_ALLOC_APPEND(
1604 12663 : l, alignof(uint), PB_BYTES_ARRAY_T_ALLOCSIZE( vm->heap_max ) );
1605 12663 : if( FD_UNLIKELY( _l > output_end ) ) {
1606 0 : goto error;
1607 0 : }
1608 12663 : effects->heap->size = (uint)vm->heap_max;
1609 12663 : fd_memcpy( effects->heap->bytes, vm->heap, vm->heap_max );
1610 12663 : } else {
1611 48 : effects->heap = NULL;
1612 48 : }
1613 :
1614 12711 : effects->stack = FD_SCRATCH_ALLOC_APPEND(
1615 12711 : l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( FD_VM_STACK_MAX ) );
1616 12711 : if( FD_UNLIKELY( _l > output_end ) ) {
1617 0 : goto error;
1618 0 : }
1619 12711 : effects->stack->size = (uint)FD_VM_STACK_MAX;
1620 12711 : fd_memcpy( effects->stack->bytes, vm->stack, FD_VM_STACK_MAX );
1621 :
1622 12711 : if( vm->rodata_sz ) {
1623 2214 : effects->rodata = FD_SCRATCH_ALLOC_APPEND(
1624 2214 : l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( rodata_sz ) );
1625 2214 : if( FD_UNLIKELY( _l > output_end ) ) {
1626 0 : goto error;
1627 0 : }
1628 2214 : effects->rodata->size = (uint)rodata_sz;
1629 2214 : fd_memcpy( effects->rodata->bytes, vm->rodata, rodata_sz );
1630 10497 : } else {
1631 10497 : effects->rodata = NULL;
1632 10497 : }
1633 :
1634 12711 : effects->frame_count = vm->frame_cnt;
1635 :
1636 12711 : fd_log_collector_t * log = &vm->instr_ctx->txn_ctx->log_collector;
1637 : /* Only collect log on valid errors (i.e., != -1). Follows
1638 : https://github.com/firedancer-io/solfuzz-agave/blob/99758d3c4f3a342d56e2906936458d82326ae9a8/src/utils/err_map.rs#L148 */
1639 12711 : if( effects->error != -1 && log->buf_sz ) {
1640 2004 : effects->log = FD_SCRATCH_ALLOC_APPEND(
1641 2004 : l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( log->buf_sz ) );
1642 2004 : if( FD_UNLIKELY( _l > output_end ) ) {
1643 0 : goto error;
1644 0 : }
1645 2004 : effects->log->size = (uint)fd_log_collector_debug_sprintf( log, (char *)effects->log->bytes, 0 );
1646 10707 : } else {
1647 10707 : effects->log = NULL;
1648 10707 : }
1649 :
1650 : /* Capture input regions */
1651 12711 : effects->inputdata = NULL; /* Deprecated, using input_data_regions instead */
1652 12711 : ulong tmp_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
1653 12711 : ulong input_regions_size = load_from_vm_input_regions( vm->input_mem_regions,
1654 12711 : vm->input_mem_regions_cnt,
1655 12711 : &effects->input_data_regions,
1656 12711 : &effects->input_data_regions_count,
1657 12711 : (void *)tmp_end,
1658 12711 : fd_ulong_sat_sub( output_end, tmp_end ) );
1659 :
1660 12711 : if( !!vm->input_mem_regions_cnt && !effects->input_data_regions ) {
1661 0 : goto error;
1662 0 : }
1663 :
1664 : /* Return the effects */
1665 12711 : ulong actual_end = tmp_end + input_regions_size;
1666 12711 : fd_exec_test_instr_context_destroy( runner, ctx );
1667 12711 : cpi_exec_effects = NULL;
1668 :
1669 12711 : *output = effects;
1670 12711 : return actual_end - (ulong)output_buf;
1671 :
1672 0 : error:
1673 0 : fd_exec_test_instr_context_destroy( runner, ctx );
1674 0 : cpi_exec_effects = NULL;
1675 0 : return 0;
1676 12711 : }
1677 :
1678 : /* Stubs fd_execute_instr for binaries compiled with
1679 : `-Xlinker --wrap=fd_execute_instr` */
1680 : int
1681 : __wrap_fd_execute_instr( fd_exec_txn_ctx_t * txn_ctx,
1682 : fd_instr_info_t * instr_info )
1683 0 : {
1684 0 : static const pb_byte_t zero_blk[32] = {0};
1685 :
1686 0 : if( cpi_exec_effects == NULL ) {
1687 0 : FD_LOG_WARNING(( "fd_execute_instr is disabled" ));
1688 0 : return FD_EXECUTOR_INSTR_SUCCESS;
1689 0 : }
1690 :
1691 : // Iterate through instruction accounts
1692 0 : for( ushort i = 0UL; i < instr_info->acct_cnt; ++i ) {
1693 0 : uchar idx_in_txn = instr_info->acct_txn_idxs[i];
1694 0 : fd_pubkey_t * acct_pubkey = &instr_info->acct_pubkeys[i];
1695 :
1696 0 : fd_borrowed_account_t * acct = NULL;
1697 : /* Find (first) account in cpi_exec_effects->modified_accounts that matches pubkey */
1698 0 : for( uint j = 0UL; j < cpi_exec_effects->modified_accounts_count; ++j ) {
1699 0 : fd_exec_test_acct_state_t * acct_state = &cpi_exec_effects->modified_accounts[j];
1700 0 : if( memcmp( acct_state->address, acct_pubkey, sizeof(fd_pubkey_t) ) != 0 ) continue;
1701 :
1702 : /* Fetch borrowed account */
1703 : /* First check if account is read-only.
1704 : TODO: Once direct mapping is enabled we _technically_ don't need
1705 : this check */
1706 :
1707 0 : if( fd_txn_borrowed_account_view_idx( txn_ctx, idx_in_txn, &acct ) ) {
1708 0 : break;
1709 0 : }
1710 0 : if( acct->meta == NULL ){
1711 0 : break;
1712 0 : }
1713 :
1714 : /* Now borrow mutably (with resize) */
1715 0 : int err = fd_txn_borrowed_account_modify_idx( txn_ctx,
1716 0 : idx_in_txn,
1717 : /* Do not reallocate if data is not going to be modified */
1718 0 : acct_state->data ? acct_state->data->size : 0UL,
1719 0 : &acct );
1720 0 : if( err ) break;
1721 :
1722 : /* Update account state */
1723 0 : acct->meta->info.lamports = acct_state->lamports;
1724 0 : acct->meta->info.executable = acct_state->executable;
1725 0 : acct->meta->info.rent_epoch = acct_state->rent_epoch;
1726 :
1727 : /* TODO: use lower level API (i.e., fd_borrowed_account_resize) to avoid memcpy here */
1728 0 : if( acct_state->data ){
1729 0 : fd_memcpy( acct->data, acct_state->data->bytes, acct_state->data->size );
1730 0 : acct->meta->dlen = acct_state->data->size;
1731 0 : }
1732 :
1733 : /* Follow solfuzz-agave, which skips if pubkey is malformed */
1734 0 : if( memcmp( acct_state->owner, zero_blk, sizeof(fd_pubkey_t) ) != 0 ) {
1735 0 : fd_memcpy( acct->meta->info.owner, acct_state->owner, sizeof(fd_pubkey_t) );
1736 0 : }
1737 :
1738 0 : break;
1739 0 : }
1740 0 : }
1741 0 : return FD_EXECUTOR_INSTR_SUCCESS;
1742 0 : }
|