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