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