Line data Source code
1 : #include "fd_txn_harness.h"
2 : #include "fd_harness_common.h"
3 :
4 : static void
5 : fd_runtime_fuzz_txn_ctx_destroy( fd_runtime_fuzz_runner_t * runner,
6 0 : fd_exec_slot_ctx_t * slot_ctx ) {
7 0 : if( !slot_ctx ) return; // This shouldn't be false either
8 0 : fd_funk_txn_t * funk_txn = slot_ctx->funk_txn;
9 :
10 0 : fd_funk_txn_cancel( runner->funk, funk_txn, 1 );
11 0 : }
12 :
13 : /* Creates transaction execution context for a single test case. Returns a
14 : a parsed txn descriptor on success and NULL on failure. */
15 : static fd_txn_p_t *
16 : fd_runtime_fuzz_txn_ctx_create( fd_runtime_fuzz_runner_t * runner,
17 : fd_exec_slot_ctx_t * slot_ctx,
18 0 : fd_exec_test_txn_context_t const * test_ctx ) {
19 0 : const uchar empty_bytes[64] = { 0 };
20 0 : fd_funk_t * funk = runner->funk;
21 :
22 : /* Generate unique ID for funk txn */
23 :
24 0 : fd_funk_txn_xid_t xid[1] = {0};
25 0 : xid[0] = fd_funk_generate_xid();
26 :
27 : /* Create temporary funk transaction and spad contexts */
28 :
29 0 : fd_funk_txn_start_write( funk );
30 0 : fd_funk_txn_t * funk_txn = fd_funk_txn_prepare( funk, NULL, xid, 1 );
31 0 : fd_funk_txn_end_write( funk );
32 :
33 : /* Allocate contexts */
34 0 : assert( slot_ctx );
35 :
36 : /* Set up slot context */
37 :
38 0 : slot_ctx->funk_txn = funk_txn;
39 0 : slot_ctx->funk = funk;
40 :
41 0 : slot_ctx->bank = runner->bank;
42 0 : fd_bank_clear_bank( slot_ctx->bank );
43 :
44 : /* Restore feature flags */
45 :
46 0 : fd_exec_test_feature_set_t const * feature_set = &test_ctx->epoch_ctx.features;
47 0 : fd_features_t * features_bm = fd_bank_features_modify( slot_ctx->bank );
48 0 : if( !fd_runtime_fuzz_restore_features( features_bm, feature_set ) ) {
49 0 : return NULL;
50 0 : }
51 :
52 : /* Default slot */
53 0 : ulong slot = test_ctx->slot_ctx.slot ? test_ctx->slot_ctx.slot : 10; // Arbitrary default > 0
54 :
55 : /* Set slot bank variables (defaults obtained from GenesisConfig::default() in Agave) */
56 0 : slot_ctx->slot = slot;
57 0 : slot_ctx->bank->slot = slot;
58 :
59 : /* Initialize builtin accounts */
60 0 : fd_builtin_programs_init( slot_ctx );
61 :
62 : /* Load account states into funk (note this is different from the account keys):
63 : Account state = accounts to populate Funk
64 : Account keys = account keys that the transaction needs */
65 0 : for( ulong i = 0; i < test_ctx->account_shared_data_count; i++ ) {
66 : /* Load the accounts into the account manager
67 : Borrowed accounts get reset anyways - we just need to load the account somewhere */
68 0 : FD_TXN_ACCOUNT_DECL( acc );
69 0 : fd_runtime_fuzz_load_account( acc, funk, funk_txn, &test_ctx->account_shared_data[i], 1 );
70 0 : }
71 :
72 : /* Add accounts to bpf program cache */
73 0 : fd_bpf_scan_and_create_bpf_program_cache_entry( slot_ctx, runner->spad );
74 :
75 : /* Setup Bank manager */
76 :
77 0 : fd_bank_prev_slot_set( slot_ctx->bank, slot_ctx->slot - 1UL );
78 :
79 0 : fd_bank_lamports_per_signature_set( slot_ctx->bank, 5000UL );
80 :
81 0 : fd_bank_prev_lamports_per_signature_set( slot_ctx->bank, 5000UL );
82 :
83 0 : fd_fee_rate_governor_t * fee_rate_governor = fd_bank_fee_rate_governor_modify( slot_ctx->bank );
84 0 : fee_rate_governor->burn_percent = 50;
85 0 : fee_rate_governor->min_lamports_per_signature = 0;
86 0 : fee_rate_governor->max_lamports_per_signature = 0;
87 0 : fee_rate_governor->target_lamports_per_signature = 10000;
88 0 : fee_rate_governor->target_signatures_per_slot = 20000;
89 :
90 0 : fd_bank_ticks_per_slot_set( slot_ctx->bank, 64 );
91 :
92 : /* Set epoch bank variables if not present (defaults obtained from GenesisConfig::default() in Agave) */
93 0 : fd_epoch_schedule_t default_epoch_schedule = {
94 0 : .slots_per_epoch = 432000,
95 0 : .leader_schedule_slot_offset = 432000,
96 0 : .warmup = 1,
97 0 : .first_normal_epoch = 14,
98 0 : .first_normal_slot = 524256
99 0 : };
100 0 : fd_rent_t default_rent = {
101 0 : .lamports_per_uint8_year = 3480,
102 0 : .exemption_threshold = 2.0,
103 0 : .burn_percent = 50
104 0 : };
105 0 : fd_bank_epoch_schedule_set( slot_ctx->bank, default_epoch_schedule );
106 :
107 0 : fd_bank_rent_set( slot_ctx->bank, default_rent );
108 :
109 0 : fd_bank_slots_per_year_set( slot_ctx->bank, SECONDS_PER_YEAR * (1000000000.0 / (double)6250000) / (double)(fd_bank_ticks_per_slot_get( slot_ctx->bank )) );
110 :
111 : // Override default values if provided
112 0 : fd_epoch_schedule_t * epoch_schedule = fd_sysvar_epoch_schedule_read( funk, funk_txn, runner->spad );
113 0 : if( epoch_schedule ) {
114 0 : fd_bank_epoch_schedule_set( slot_ctx->bank, *epoch_schedule );
115 0 : }
116 :
117 0 : fd_rent_t const * rent = fd_sysvar_rent_read( funk, funk_txn, runner->spad );
118 0 : if( rent ) {
119 0 : fd_bank_rent_set( slot_ctx->bank, *rent );
120 0 : }
121 :
122 : /* Provide default slot hashes of size 1 if not provided */
123 0 : fd_slot_hashes_global_t * slot_hashes = fd_sysvar_slot_hashes_read( funk, funk_txn, runner->spad );
124 0 : if( !slot_hashes ) {
125 0 : FD_SPAD_FRAME_BEGIN( runner->spad ) {
126 : /* The offseted gaddr aware types need the memory for the entire
127 : struct to be allocated out of a contiguous memory region. */
128 0 : fd_slot_hash_t * slot_hashes = NULL;
129 0 : void * mem = fd_spad_alloc( runner->spad, FD_SYSVAR_SLOT_HASHES_ALIGN, fd_sysvar_slot_hashes_footprint( 1UL ) );
130 0 : fd_slot_hashes_global_t * default_slot_hashes_global = fd_sysvar_slot_hashes_join( fd_sysvar_slot_hashes_new( mem, 1UL ), &slot_hashes );
131 :
132 0 : fd_slot_hash_t * dummy_elem = deq_fd_slot_hash_t_push_tail_nocopy( slot_hashes );
133 0 : memset( dummy_elem, 0, sizeof(fd_slot_hash_t) );
134 :
135 0 : fd_sysvar_slot_hashes_write( slot_ctx, default_slot_hashes_global );
136 :
137 0 : fd_sysvar_slot_hashes_delete( fd_sysvar_slot_hashes_leave( default_slot_hashes_global, slot_hashes ) );
138 0 : } FD_SPAD_FRAME_END;
139 0 : }
140 :
141 : /* Provide default stake history if not provided */
142 0 : fd_stake_history_t * stake_history = fd_sysvar_stake_history_read( funk, funk_txn, runner->spad );
143 0 : if( !stake_history ) {
144 : // Provide a 0-set default entry
145 0 : fd_epoch_stake_history_entry_pair_t entry = {0};
146 0 : fd_sysvar_stake_history_init( slot_ctx );
147 0 : fd_sysvar_stake_history_update( slot_ctx, &entry, runner->spad );
148 0 : }
149 :
150 : /* Provide default last restart slot sysvar if not provided */
151 0 : FD_TXN_ACCOUNT_DECL( acc );
152 0 : int err = fd_txn_account_init_from_funk_readonly( acc, &fd_sysvar_last_restart_slot_id, funk, funk_txn );
153 0 : if( err==FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) {
154 0 : fd_sysvar_last_restart_slot_init( slot_ctx );
155 0 : }
156 :
157 : /* Provide a default clock if not present */
158 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_clock_read( funk, funk_txn, runner->spad );
159 0 : if( !clock ) {
160 0 : fd_sysvar_clock_init( slot_ctx->bank, slot_ctx->funk, slot_ctx->funk_txn );
161 0 : fd_sysvar_clock_update( slot_ctx->bank, slot_ctx->funk, slot_ctx->funk_txn, runner->spad );
162 0 : }
163 :
164 : /* Epoch schedule and rent get set from the epoch bank */
165 0 : fd_sysvar_epoch_schedule_init( slot_ctx );
166 0 : fd_sysvar_rent_init( slot_ctx );
167 :
168 : /* Set the epoch rewards sysvar if partition epoch rewards feature is enabled
169 :
170 : TODO: The init parameters are not exactly conformant with Agave's epoch rewards sysvar. We should
171 : be calling `fd_begin_partitioned_rewards` with the same parameters as Agave. However,
172 : we just need the `active` field to be conformant due to a single Stake program check.
173 : THIS MAY CHANGE IN THE FUTURE. If there are other parts of transaction execution that use
174 : the epoch rewards sysvar, we may need to update this.
175 : */
176 0 : fd_sysvar_epoch_rewards_t * epoch_rewards = fd_sysvar_epoch_rewards_read( funk, funk_txn, runner->spad );
177 0 : if( !epoch_rewards ) {
178 0 : fd_point_value_t point_value = {0};
179 0 : 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;
180 0 : fd_sysvar_epoch_rewards_init( slot_ctx, 0UL, 2UL, 1UL, point_value, last_hash);
181 0 : }
182 :
183 : /* A NaN rent exemption threshold is U.B. in Solana Labs */
184 0 : rent = fd_sysvar_rent_read( funk, funk_txn, runner->spad );
185 0 : if( ( rent->exemption_threshold != 0.0 &&
186 0 : !fd_dblbits_is_normal( fd_dblbits( rent->exemption_threshold ) ) ) |
187 0 : ( rent->exemption_threshold < 0.0 ) |
188 0 : ( rent->exemption_threshold > 999.0 ) |
189 0 : ( rent->lamports_per_uint8_year > UINT_MAX ) |
190 0 : ( rent->burn_percent > 100 ) ) {
191 0 : return NULL;
192 0 : }
193 :
194 : /* Blockhash queue is given in txn message. We need to populate the following two fields:
195 : - slot_ctx->slot_bank.block_hash_queue
196 : - slot_ctx->slot_bank.recent_block_hashes */
197 0 : ulong num_blockhashes = test_ctx->blockhash_queue_count;
198 :
199 : /* Blockhash queue init */
200 0 : fd_block_hash_queue_global_t * block_hash_queue = fd_bank_block_hash_queue_modify( slot_ctx->bank );
201 0 : uchar * last_hash_mem = (uchar *)fd_ulong_align_up( (ulong)block_hash_queue + sizeof(fd_block_hash_queue_global_t), alignof(fd_hash_t) );
202 0 : uchar * ages_pool_mem = (uchar *)fd_ulong_align_up( (ulong)last_hash_mem + sizeof(fd_hash_t), fd_hash_hash_age_pair_t_map_align() );
203 0 : fd_hash_hash_age_pair_t_mapnode_t * ages_pool = fd_hash_hash_age_pair_t_map_join( fd_hash_hash_age_pair_t_map_new( ages_pool_mem, 400 ) );
204 :
205 0 : block_hash_queue->max_age = FD_BLOCKHASH_QUEUE_MAX_ENTRIES;
206 0 : block_hash_queue->ages_root_offset = 0UL;
207 0 : fd_block_hash_queue_ages_pool_update( block_hash_queue, ages_pool );
208 0 : block_hash_queue->last_hash_index = 0UL;
209 0 : block_hash_queue->last_hash_offset = (ulong)last_hash_mem - (ulong)block_hash_queue;
210 :
211 : // Save lamports per signature for most recent blockhash, if sysvar cache contains recent block hashes
212 0 : fd_recent_block_hashes_global_t const * rbh_global = fd_sysvar_recent_hashes_read( funk, funk_txn, runner->spad );
213 0 : fd_recent_block_hashes_t rbh[1];
214 0 : if( rbh_global ) {
215 0 : rbh->hashes = deq_fd_block_block_hash_entry_t_join( (uchar*)rbh_global + rbh_global->hashes_offset );
216 0 : }
217 :
218 0 : if( rbh_global && !deq_fd_block_block_hash_entry_t_empty( rbh->hashes ) ) {
219 0 : fd_block_block_hash_entry_t const * last = deq_fd_block_block_hash_entry_t_peek_head_const( rbh->hashes );
220 0 : if( last && last->fee_calculator.lamports_per_signature!=0UL ) {
221 0 : fd_bank_lamports_per_signature_set( slot_ctx->bank, last->fee_calculator.lamports_per_signature );
222 0 : fd_bank_prev_lamports_per_signature_set( slot_ctx->bank, last->fee_calculator.lamports_per_signature );
223 0 : }
224 0 : }
225 :
226 : // Blockhash_queue[end] = last (latest) hash
227 : // Blockhash_queue[0] = genesis hash
228 0 : if( num_blockhashes > 0 ) {
229 0 : fd_hash_t * genesis_hash = fd_bank_genesis_hash_modify( slot_ctx->bank );
230 0 : memcpy( genesis_hash->hash, test_ctx->blockhash_queue[0]->bytes, sizeof(fd_hash_t) );
231 :
232 0 : for( ulong i = 0; i < num_blockhashes; ++i ) {
233 : // Recent block hashes cap is 150 (actually 151), while blockhash queue capacity is 300 (actually 301)
234 0 : fd_block_block_hash_entry_t blockhash_entry;
235 0 : memcpy( &blockhash_entry.blockhash, test_ctx->blockhash_queue[i]->bytes, sizeof(fd_hash_t) );
236 0 : fd_bank_poh_set( slot_ctx->bank, blockhash_entry.blockhash );
237 0 : fd_sysvar_recent_hashes_update( slot_ctx, runner->spad );
238 0 : }
239 0 : } else {
240 : // Add a default empty blockhash and use it as genesis
241 0 : num_blockhashes = 1;
242 0 : fd_hash_t * genesis_hash = fd_bank_genesis_hash_modify( slot_ctx->bank );
243 0 : memcpy( genesis_hash->hash, empty_bytes, sizeof(fd_hash_t) );
244 0 : fd_block_block_hash_entry_t blockhash_entry;
245 0 : memcpy( &blockhash_entry.blockhash, empty_bytes, sizeof(fd_hash_t) );
246 0 : fd_bank_poh_set( slot_ctx->bank, blockhash_entry.blockhash );
247 0 : fd_sysvar_recent_hashes_update( slot_ctx, runner->spad );
248 0 : }
249 :
250 : /* Create the raw txn (https://solana.com/docs/core/transactions#transaction-size) */
251 0 : uchar * txn_raw_begin = fd_spad_alloc( runner->spad, alignof(uchar), 1232 );
252 0 : ushort instr_count, addr_table_cnt;
253 0 : ulong msg_sz = fd_runtime_fuzz_serialize_txn( txn_raw_begin, &test_ctx->tx, &instr_count, &addr_table_cnt );
254 0 : if( FD_UNLIKELY( msg_sz==ULONG_MAX ) ) {
255 0 : return NULL;
256 0 : }
257 :
258 : /* Set up txn descriptor from raw data */
259 0 : fd_txn_t * txn_descriptor = (fd_txn_t *) fd_spad_alloc( runner->spad, fd_txn_align(), fd_txn_footprint( instr_count, addr_table_cnt ) );
260 0 : if( FD_UNLIKELY( !fd_txn_parse( txn_raw_begin, msg_sz, txn_descriptor, NULL ) ) ) {
261 0 : return NULL;
262 0 : }
263 :
264 : /* Run txn preparation phases and execution
265 : NOTE: This should be modified accordingly if transaction setup logic changes */
266 0 : fd_txn_p_t * txn = fd_spad_alloc( runner->spad, alignof(fd_txn_p_t), sizeof(fd_txn_p_t) );
267 0 : memcpy( txn->payload, txn_raw_begin, msg_sz );
268 0 : txn->payload_sz = msg_sz;
269 0 : txn->flags = FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
270 0 : memcpy( txn->_, txn_descriptor, fd_txn_footprint( instr_count, addr_table_cnt ) );
271 :
272 0 : return txn;
273 0 : }
274 :
275 : /* Takes in a parsed txn descriptor to be executed against the runtime.
276 : Returns the task info. */
277 : static fd_execute_txn_task_info_t *
278 : fd_runtime_fuzz_txn_ctx_exec( fd_runtime_fuzz_runner_t * runner,
279 : fd_exec_slot_ctx_t * slot_ctx,
280 0 : fd_txn_p_t * txn ) {
281 0 : fd_execute_txn_task_info_t * task_info = fd_spad_alloc( runner->spad, alignof(fd_execute_txn_task_info_t), sizeof(fd_execute_txn_task_info_t) );
282 0 : memset( task_info, 0, sizeof(fd_execute_txn_task_info_t) );
283 0 : task_info->txn = txn;
284 0 : task_info->txn_ctx = fd_spad_alloc( runner->spad, FD_EXEC_TXN_CTX_ALIGN, FD_EXEC_TXN_CTX_FOOTPRINT );
285 :
286 0 : fd_tpool_t tpool[1];
287 0 : tpool->worker_cnt = 1;
288 0 : tpool->worker_max = 1;
289 :
290 0 : fd_runtime_prepare_txns_start( slot_ctx, task_info, txn, 1UL, runner->spad );
291 :
292 : /* Setup the spad for account allocation */
293 0 : task_info->txn_ctx->spad = runner->spad;
294 0 : task_info->txn_ctx->spad_wksp = fd_wksp_containing( runner->spad );
295 :
296 0 : fd_runtime_pre_execute_check( task_info, 0 );
297 :
298 0 : if( task_info->txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS ) {
299 0 : task_info->txn->flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
300 0 : task_info->exec_res = fd_execute_txn( task_info );
301 0 : }
302 :
303 0 : fd_bank_execution_fees_set( slot_ctx->bank, fd_bank_execution_fees_get( slot_ctx->bank ) + task_info->txn_ctx->execution_fee );
304 :
305 0 : fd_bank_priority_fees_set( slot_ctx->bank, fd_bank_priority_fees_get( slot_ctx->bank ) + task_info->txn_ctx->priority_fee );
306 :
307 0 : return task_info;
308 0 : }
309 :
310 : ulong
311 : fd_runtime_fuzz_serialize_txn( uchar * txn_raw_begin,
312 : fd_exec_test_sanitized_transaction_t const * tx,
313 : ushort * out_instr_cnt,
314 0 : ushort * out_addr_table_cnt ) {
315 0 : const uchar empty_bytes[64] = { 0 };
316 0 : uchar * txn_raw_cur_ptr = txn_raw_begin;
317 :
318 : /* Compact array of signatures (https://solana.com/docs/core/transactions#transaction)
319 : Note that although documentation interchangably refers to the signature cnt as a compact-u16
320 : and a u8, the max signature cnt is capped at 48 (due to txn size limits), so u8 and compact-u16
321 : is represented the same way anyways and can be parsed identically. */
322 : // Note: always create a valid txn with 1+ signatures, add an empty signature if none is provided
323 0 : uchar signature_cnt = fd_uchar_max( 1, (uchar) tx->signatures_count );
324 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &signature_cnt, sizeof(uchar) );
325 0 : for( uchar i = 0; i < signature_cnt; ++i ) {
326 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, tx->signatures && tx->signatures[i] ? tx->signatures[i]->bytes : empty_bytes, FD_TXN_SIGNATURE_SZ );
327 0 : }
328 :
329 : /* Message */
330 : /* For v0 transactions, the highest bit of the num_required_signatures is set, and an extra byte is used for the version.
331 : https://solanacookbook.com/guides/versioned-transactions.html#versioned-transactions-transactionv0
332 :
333 : We will always create a transaction with at least 1 signature, and cap the signature count to 127 to avoid
334 : collisions with the header_b0 tag. */
335 0 : uchar num_required_signatures = fd_uchar_max( 1, fd_uchar_min( 127, (uchar) tx->message.header.num_required_signatures ) );
336 0 : if( !tx->message.is_legacy ) {
337 0 : uchar header_b0 = (uchar) 0x80UL;
338 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &header_b0, sizeof(uchar) );
339 0 : }
340 :
341 : /* Header (3 bytes) (https://solana.com/docs/core/transactions#message-header) */
342 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &num_required_signatures, sizeof(uchar) );
343 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &tx->message.header.num_readonly_signed_accounts, sizeof(uchar) );
344 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &tx->message.header.num_readonly_unsigned_accounts, sizeof(uchar) );
345 :
346 : /* Compact array of account addresses (https://solana.com/docs/core/transactions#compact-array-format) */
347 : // Array length is a compact u16
348 0 : ushort num_acct_keys = (ushort) tx->message.account_keys_count;
349 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, num_acct_keys );
350 0 : for( ushort i = 0; i < num_acct_keys; ++i ) {
351 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, tx->message.account_keys[i]->bytes, sizeof(fd_pubkey_t) );
352 0 : }
353 :
354 : /* Recent blockhash (32 bytes) (https://solana.com/docs/core/transactions#recent-blockhash) */
355 : // Note: add an empty blockhash if none is provided
356 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, tx->message.recent_blockhash ? tx->message.recent_blockhash->bytes : empty_bytes, sizeof(fd_hash_t) );
357 :
358 : /* Compact array of instructions (https://solana.com/docs/core/transactions#array-of-instructions) */
359 : // Instruction count is a compact u16
360 0 : ushort instr_count = (ushort) tx->message.instructions_count;
361 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, instr_count );
362 0 : for( ushort i = 0; i < instr_count; ++i ) {
363 : // Program ID index
364 0 : uchar program_id_index = (uchar) tx->message.instructions[i].program_id_index;
365 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &program_id_index, sizeof(uchar) );
366 :
367 : // Compact array of account addresses
368 0 : ushort acct_count = (ushort) tx->message.instructions[i].accounts_count;
369 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, acct_count );
370 0 : for( ushort j = 0; j < acct_count; ++j ) {
371 0 : uchar account_index = (uchar) tx->message.instructions[i].accounts[j];
372 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &account_index, sizeof(uchar) );
373 0 : }
374 :
375 : // Compact array of 8-bit data
376 0 : pb_bytes_array_t * data = tx->message.instructions[i].data;
377 0 : ushort data_len;
378 0 : if( data ) {
379 0 : data_len = (ushort) data->size;
380 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data_len );
381 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data->bytes, data_len );
382 0 : } else {
383 0 : data_len = 0;
384 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data_len );
385 0 : }
386 0 : }
387 :
388 : /* Address table lookups (N/A for legacy transactions) */
389 0 : ushort addr_table_cnt = 0;
390 0 : if( !tx->message.is_legacy ) {
391 : /* Compact array of address table lookups (https://solanacookbook.com/guides/versioned-transactions.html#compact-array-of-address-table-lookups) */
392 : // NOTE: The diagram is slightly wrong - the account key is a 32 byte pubkey, not a u8
393 0 : addr_table_cnt = (ushort) tx->message.address_table_lookups_count;
394 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, addr_table_cnt );
395 0 : for( ushort i = 0; i < addr_table_cnt; ++i ) {
396 : // Account key
397 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, tx->message.address_table_lookups[i].account_key, sizeof(fd_pubkey_t) );
398 :
399 : // Compact array of writable indexes
400 0 : ushort writable_count = (ushort) tx->message.address_table_lookups[i].writable_indexes_count;
401 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, writable_count );
402 0 : for( ushort j = 0; j < writable_count; ++j ) {
403 0 : uchar writable_index = (uchar) tx->message.address_table_lookups[i].writable_indexes[j];
404 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &writable_index, sizeof(uchar) );
405 0 : }
406 :
407 : // Compact array of readonly indexes
408 0 : ushort readonly_count = (ushort) tx->message.address_table_lookups[i].readonly_indexes_count;
409 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, readonly_count );
410 0 : for( ushort j = 0; j < readonly_count; ++j ) {
411 0 : uchar readonly_index = (uchar) tx->message.address_table_lookups[i].readonly_indexes[j];
412 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &readonly_index, sizeof(uchar) );
413 0 : }
414 0 : }
415 0 : }
416 :
417 0 : *out_instr_cnt = instr_count;
418 0 : *out_addr_table_cnt = addr_table_cnt;
419 0 : return (ulong)(txn_raw_cur_ptr - txn_raw_begin);
420 0 : }
421 :
422 : ulong
423 : fd_runtime_fuzz_txn_run( fd_runtime_fuzz_runner_t * runner,
424 : void const * input_,
425 : void ** output_,
426 : void * output_buf,
427 0 : ulong output_bufsz ) {
428 0 : fd_exec_test_txn_context_t const * input = fd_type_pun_const( input_ );
429 0 : fd_exec_test_txn_result_t ** output = fd_type_pun( output_ );
430 :
431 0 : FD_SPAD_FRAME_BEGIN( runner->spad ) {
432 :
433 : /* Initialize memory */
434 0 : uchar * slot_ctx_mem = fd_spad_alloc( runner->spad, FD_EXEC_SLOT_CTX_ALIGN, FD_EXEC_SLOT_CTX_FOOTPRINT );
435 0 : fd_exec_slot_ctx_t * slot_ctx = fd_exec_slot_ctx_join( fd_exec_slot_ctx_new( slot_ctx_mem ) );
436 :
437 : /* Setup the transaction context */
438 0 : fd_txn_p_t * txn = fd_runtime_fuzz_txn_ctx_create( runner, slot_ctx, input );
439 0 : if( txn==NULL ) {
440 0 : fd_runtime_fuzz_txn_ctx_destroy( runner, slot_ctx );
441 0 : return 0;
442 0 : }
443 :
444 : /* Execute the transaction against the runtime */
445 0 : fd_execute_txn_task_info_t * task_info = fd_runtime_fuzz_txn_ctx_exec( runner, slot_ctx, txn );
446 0 : fd_exec_txn_ctx_t * txn_ctx = task_info->txn_ctx;
447 :
448 0 : int exec_res = task_info->exec_res;
449 :
450 : /* Start saving txn exec results */
451 0 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
452 0 : ulong output_end = (ulong)output_buf + output_bufsz;
453 :
454 0 : fd_exec_test_txn_result_t * txn_result =
455 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_txn_result_t),
456 0 : sizeof (fd_exec_test_txn_result_t) );
457 0 : if( FD_UNLIKELY( _l > output_end ) ) {
458 0 : abort();
459 0 : }
460 0 : fd_memset( txn_result, 0, sizeof(fd_exec_test_txn_result_t) );
461 :
462 : /* Capture basic results fields */
463 0 : txn_result->executed = task_info->txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
464 0 : txn_result->sanitization_error = !( task_info->txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS );
465 0 : txn_result->has_resulting_state = false;
466 0 : txn_result->resulting_state.acct_states_count = 0;
467 0 : txn_result->is_ok = !exec_res;
468 0 : txn_result->status = (uint32_t) -exec_res;
469 0 : txn_result->instruction_error = 0;
470 0 : txn_result->instruction_error_index = 0;
471 0 : txn_result->custom_error = 0;
472 0 : txn_result->executed_units = txn_ctx->compute_unit_limit - txn_ctx->compute_meter;
473 0 : txn_result->has_fee_details = false;
474 0 : txn_result->loaded_accounts_data_size = task_info->txn_ctx->loaded_accounts_data_size;
475 :
476 0 : if( txn_result->sanitization_error ) {
477 : /* Collect fees for transactions that failed to load */
478 0 : if( task_info->txn->flags & FD_TXN_P_FLAGS_FEES_ONLY ) {
479 0 : txn_result->has_fee_details = true;
480 0 : txn_result->fee_details.prioritization_fee = task_info->txn_ctx->priority_fee;
481 0 : txn_result->fee_details.transaction_fee = task_info->txn_ctx->execution_fee;
482 0 : }
483 :
484 0 : if( exec_res==FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR ) {
485 : /* If exec_res was an instruction error and we have a sanitization error, it was a precompile error */
486 0 : txn_result->instruction_error = (uint32_t) -txn_ctx->exec_err;
487 0 : txn_result->instruction_error_index = (uint32_t) txn_ctx->instr_err_idx;
488 :
489 : /*
490 : 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.
491 : For now, only precompiles throw custom error codes, so we can ignore all custom error codes thrown in the sanitization phase. If this changes,
492 : this logic will have to be revisited.
493 :
494 : if( task_info->txn_ctx->exec_err == FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR ) {
495 : txn_result->custom_error = txn_ctx->custom_err;
496 : }
497 : */
498 0 : }
499 :
500 0 : ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
501 0 : fd_runtime_fuzz_txn_ctx_destroy( runner, slot_ctx );
502 :
503 0 : *output = txn_result;
504 0 : return actual_end - (ulong)output_buf;
505 :
506 0 : } else {
507 : /* Capture the instruction error code */
508 0 : if( exec_res==FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR ) {
509 0 : int instr_err_idx = txn_ctx->instr_err_idx;
510 0 : int program_id_idx = txn_ctx->instr_infos[instr_err_idx].program_id;
511 :
512 0 : txn_result->instruction_error = (uint32_t) -txn_ctx->exec_err;
513 0 : txn_result->instruction_error_index = (uint32_t) instr_err_idx;
514 :
515 : /* If the exec err was a custom instr error and came from a precompile instruction, don't capture the custom error code. */
516 0 : if( txn_ctx->exec_err==FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR &&
517 0 : fd_executor_lookup_native_precompile_program( &txn_ctx->accounts[ program_id_idx ] )==NULL ) {
518 0 : txn_result->custom_error = txn_ctx->custom_err;
519 0 : }
520 0 : }
521 0 : }
522 :
523 0 : txn_result->has_fee_details = true;
524 0 : txn_result->fee_details.transaction_fee = task_info->txn_ctx->execution_fee;
525 0 : txn_result->fee_details.prioritization_fee = task_info->txn_ctx->priority_fee;
526 :
527 : /* Rent is only collected on successfully loaded transactions */
528 0 : txn_result->rent = txn_ctx->collected_rent;
529 :
530 0 : if( txn_ctx->return_data.len > 0 ) {
531 0 : txn_result->return_data = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t),
532 0 : PB_BYTES_ARRAY_T_ALLOCSIZE( txn_ctx->return_data.len ) );
533 0 : if( FD_UNLIKELY( _l > output_end ) ) {
534 0 : abort();
535 0 : }
536 :
537 0 : txn_result->return_data->size = (pb_size_t)txn_ctx->return_data.len;
538 0 : fd_memcpy( txn_result->return_data->bytes, txn_ctx->return_data.data, txn_ctx->return_data.len );
539 0 : }
540 :
541 : /* Allocate space for captured accounts */
542 0 : ulong modified_acct_cnt = txn_ctx->accounts_cnt;
543 :
544 0 : txn_result->has_resulting_state = true;
545 0 : txn_result->resulting_state.acct_states =
546 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_acct_state_t),
547 0 : sizeof (fd_exec_test_acct_state_t) * modified_acct_cnt );
548 0 : if( FD_UNLIKELY( _l > output_end ) ) {
549 0 : abort();
550 0 : }
551 :
552 : /* If the transaction is a fees-only transaction, we have to create rollback accounts to iterate over and save. */
553 0 : fd_txn_account_t * accounts_to_save = txn_ctx->accounts;
554 0 : ulong accounts_cnt = txn_ctx->accounts_cnt;
555 0 : if( task_info->txn->flags & FD_TXN_P_FLAGS_FEES_ONLY ) {
556 0 : accounts_to_save = fd_spad_alloc( runner->spad, alignof(fd_txn_account_t), sizeof(fd_txn_account_t) * 2 );
557 0 : accounts_cnt = 0UL;
558 :
559 0 : if( FD_LIKELY( txn_ctx->nonce_account_idx_in_txn!=FD_FEE_PAYER_TXN_IDX ) ) {
560 0 : accounts_to_save[accounts_cnt++] = *txn_ctx->rollback_fee_payer_account;
561 0 : }
562 :
563 0 : if( txn_ctx->nonce_account_idx_in_txn!=ULONG_MAX ) {
564 0 : accounts_to_save[accounts_cnt++] = *txn_ctx->rollback_nonce_account;
565 0 : }
566 0 : }
567 :
568 : /* Capture borrowed accounts */
569 0 : for( ulong j=0UL; j<accounts_cnt; j++ ) {
570 0 : fd_txn_account_t * acc = &accounts_to_save[j];
571 :
572 0 : if( !( fd_exec_txn_ctx_account_is_writable_idx( txn_ctx, (ushort)j ) || j==FD_FEE_PAYER_TXN_IDX ) ) continue;
573 0 : assert( acc->vt->is_mutable( acc ) );
574 :
575 0 : ulong modified_idx = txn_result->resulting_state.acct_states_count;
576 0 : assert( modified_idx < modified_acct_cnt );
577 :
578 0 : fd_exec_test_acct_state_t * out_acct = &txn_result->resulting_state.acct_states[ modified_idx ];
579 0 : memset( out_acct, 0, sizeof(fd_exec_test_acct_state_t) );
580 : /* Copy over account content */
581 :
582 0 : memcpy( out_acct->address, acc->pubkey, sizeof(fd_pubkey_t) );
583 :
584 0 : out_acct->lamports = acc->vt->get_lamports( acc );
585 :
586 0 : if( acc->vt->get_data_len( acc ) > 0 ) {
587 0 : out_acct->data =
588 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t),
589 0 : PB_BYTES_ARRAY_T_ALLOCSIZE( acc->vt->get_data_len( acc ) ) );
590 0 : if( FD_UNLIKELY( _l > output_end ) ) {
591 0 : abort();
592 0 : }
593 0 : out_acct->data->size = (pb_size_t)acc->vt->get_data_len( acc );
594 0 : fd_memcpy( out_acct->data->bytes, acc->vt->get_data( acc ), acc->vt->get_data_len( acc ) );
595 0 : }
596 :
597 0 : out_acct->executable = acc->vt->is_executable( acc );
598 0 : out_acct->rent_epoch = acc->vt->get_rent_epoch( acc );
599 0 : memcpy( out_acct->owner, acc->vt->get_owner( acc ), sizeof(fd_pubkey_t) );
600 :
601 0 : txn_result->resulting_state.acct_states_count++;
602 0 : }
603 :
604 0 : ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
605 0 : fd_runtime_fuzz_txn_ctx_destroy( runner, slot_ctx );
606 :
607 0 : *output = txn_result;
608 0 : return actual_end - (ulong)output_buf;
609 0 : } FD_SPAD_FRAME_END;
610 0 : }
|