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 "../program/fd_builtin_programs.h"
7 : #include "../sysvar/fd_sysvar_clock.h"
8 : #include "../sysvar/fd_sysvar_epoch_schedule.h"
9 : #include "../sysvar/fd_sysvar_recent_hashes.h"
10 : #include "../sysvar/fd_sysvar_rent.h"
11 : #include "../sysvar/fd_sysvar_slot_hashes.h"
12 : #include "../sysvar/fd_sysvar_stake_history.h"
13 : #include "../../accdb/fd_accdb_impl_v1.h"
14 : #include "../../log_collector/fd_log_collector.h"
15 : #include <assert.h>
16 :
17 : /* Macros to append data to construct a serialized transaction
18 : without exceeding bounds */
19 0 : #define FD_CHECKED_ADD_TO_TXN_DATA( _begin, _cur_data, _to_add, _sz ) __extension__({ \
20 0 : if( FD_UNLIKELY( (*_cur_data)+_sz>_begin+FD_TXN_MTU ) ) return ULONG_MAX; \
21 0 : fd_memcpy( *_cur_data, _to_add, _sz ); \
22 0 : *_cur_data += _sz; \
23 0 : })
24 :
25 0 : #define FD_CHECKED_ADD_CU16_TO_TXN_DATA( _begin, _cur_data, _to_add ) __extension__({ \
26 0 : do { \
27 0 : uchar _buf[3]; \
28 0 : fd_bincode_encode_ctx_t _encode_ctx = { .data = _buf, .dataend = _buf+3 }; \
29 0 : fd_bincode_compact_u16_encode( &_to_add, &_encode_ctx ); \
30 0 : ulong _sz = (ulong) ((uchar *)_encode_ctx.data - _buf ); \
31 0 : FD_CHECKED_ADD_TO_TXN_DATA( _begin, _cur_data, _buf, _sz ); \
32 0 : } while(0); \
33 0 : })
34 :
35 : /* Writes a transaction account to the output Protobuf message's
36 : resulting_state field. out_account_data should be a pointer to
37 : a contiguous region of memory that can hold the account data +
38 : data length. */
39 : static void
40 : fd_solfuzz_pb_txn_ctx_write_account( fd_pubkey_t const * pubkey,
41 : fd_account_meta_t const * meta,
42 : fd_exec_test_resulting_state_t * resulting_state,
43 0 : pb_bytes_array_t * out_account_data ) {
44 0 : fd_exec_test_acct_state_t * out_acct = &resulting_state->acct_states[ resulting_state->acct_states_count++ ];
45 0 : memset( out_acct, 0, sizeof(fd_exec_test_acct_state_t) );
46 :
47 : /* Copy over account content */
48 0 : memcpy( out_acct->address, pubkey, sizeof(fd_pubkey_t) );
49 :
50 0 : out_acct->lamports = meta->lamports;
51 :
52 0 : if( meta->dlen>0UL ) {
53 0 : out_acct->data = out_account_data;
54 0 : out_acct->data->size = (pb_size_t)meta->dlen;
55 0 : fd_memcpy( out_acct->data->bytes, fd_account_data( meta ), meta->dlen );
56 0 : }
57 :
58 0 : out_acct->executable = meta->executable;
59 0 : memcpy( out_acct->owner, meta->owner, sizeof(fd_pubkey_t) );
60 0 : }
61 :
62 : static void
63 0 : fd_solfuzz_txn_ctx_destroy( fd_solfuzz_runner_t * runner ) {
64 0 : fd_accdb_clear( runner->accdb_admin );
65 0 : fd_progcache_clear( runner->progcache_admin );
66 :
67 : /* In order to check for leaks in the workspace, we need to compact the
68 : allocators. Without doing this, empty superblocks may be retained
69 : by the fd_alloc instance, which mean we cannot check for leaks. */
70 0 : fd_alloc_compact( runner->accdb_admin->funk->alloc );
71 0 : fd_alloc_compact( runner->progcache_admin->funk->alloc );
72 0 : }
73 :
74 : /* Creates transaction execution context for a single test case.
75 : Returns a parsed txn descriptor on success and NULL on failure. */
76 : static fd_txn_p_t *
77 : fd_solfuzz_pb_txn_ctx_create( fd_solfuzz_runner_t * runner,
78 0 : fd_exec_test_txn_context_t const * test_ctx ) {
79 0 : fd_accdb_user_t * accdb = runner->accdb;
80 :
81 : /* Default slot */
82 0 : ulong slot = test_ctx->slot_ctx.slot ? test_ctx->slot_ctx.slot : 10; // Arbitrary default > 0
83 :
84 : /* Set up the funk transaction */
85 0 : fd_funk_txn_xid_t xid = { .ul = { slot, runner->bank->data->idx } };
86 0 : fd_funk_txn_xid_t parent_xid; fd_funk_txn_xid_set_root( &parent_xid );
87 0 : fd_accdb_attach_child ( runner->accdb_admin, &parent_xid, &xid );
88 0 : fd_progcache_txn_attach_child( runner->progcache_admin, &parent_xid, &xid );
89 :
90 : /* Set up slot context */
91 0 : fd_banks_clear_bank( runner->banks, runner->bank, 64UL );
92 :
93 : /* Restore feature flags */
94 0 : fd_exec_test_feature_set_t const * feature_set = &test_ctx->epoch_ctx.features;
95 0 : fd_features_t * features_bm = fd_bank_features_modify( runner->bank );
96 0 : if( !fd_solfuzz_pb_restore_features( features_bm, feature_set ) ) {
97 0 : return NULL;
98 0 : }
99 :
100 : /* Set bank variables (defaults obtained from GenesisConfig::default
101 : in Agave) */
102 :
103 0 : fd_bank_slot_set( runner->bank, slot );
104 0 : fd_bank_parent_slot_set( runner->bank, fd_bank_slot_get( runner->bank ) - 1UL );
105 :
106 : /* Initialize builtin accounts */
107 0 : fd_builtin_programs_init( runner->bank, accdb, &xid, NULL );
108 :
109 : /* Load account states into funk (note this is different from the account keys):
110 : Account state = accounts to populate Funk
111 : Account keys = account keys that the transaction needs */
112 0 : for( ulong i = 0; i < test_ctx->account_shared_data_count; i++ ) {
113 : /* Load the accounts into the account manager
114 : Borrowed accounts get reset anyways - we just need to load the account somewhere */
115 0 : fd_solfuzz_pb_load_account( runner->runtime, accdb, &xid, &test_ctx->account_shared_data[i], i );
116 0 : }
117 :
118 : /* Setup Bank manager */
119 0 : fd_fee_rate_governor_t * fee_rate_governor = fd_bank_fee_rate_governor_modify( runner->bank );
120 0 : fee_rate_governor->burn_percent = 50;
121 0 : fee_rate_governor->min_lamports_per_signature = 0;
122 0 : fee_rate_governor->max_lamports_per_signature = 0;
123 0 : fee_rate_governor->target_lamports_per_signature = 10000;
124 0 : fee_rate_governor->target_signatures_per_slot = 20000;
125 :
126 : /* https://github.com/anza-xyz/agave/blob/v3.0.3/runtime/src/bank.rs#L1249-L1251 */
127 0 : fd_runtime_new_fee_rate_governor_derived( runner->bank, fd_bank_parent_signature_cnt_get( runner->bank ) );
128 :
129 0 : fd_bank_ticks_per_slot_set( runner->bank, 64 );
130 :
131 0 : fd_bank_slots_per_year_set( runner->bank, SECONDS_PER_YEAR * (1000000000.0 / (double)6250000) / (double)(fd_bank_ticks_per_slot_get( runner->bank )) );
132 :
133 : /* Ensure the presence of */
134 0 : fd_epoch_schedule_t epoch_schedule_[1];
135 0 : fd_epoch_schedule_t * epoch_schedule = fd_sysvar_epoch_schedule_read( accdb, &xid, epoch_schedule_ );
136 0 : FD_TEST( epoch_schedule );
137 0 : fd_bank_epoch_schedule_set( runner->bank, *epoch_schedule );
138 :
139 0 : fd_rent_t rent[1];
140 0 : FD_TEST( fd_sysvar_rent_read( accdb, &xid, rent ) );
141 0 : fd_bank_rent_set( runner->bank, *rent );
142 :
143 0 : uchar __attribute__((aligned(FD_SLOT_HASHES_GLOBAL_ALIGN))) slot_hashes_mem[ FD_SYSVAR_SLOT_HASHES_FOOTPRINT ];
144 0 : fd_slot_hashes_global_t * slot_hashes = fd_sysvar_slot_hashes_read( accdb, &xid, slot_hashes_mem );
145 0 : FD_TEST( slot_hashes );
146 :
147 0 : fd_stake_history_t stake_history_[1];
148 0 : fd_stake_history_t * stake_history = fd_sysvar_stake_history_read( accdb, &xid, stake_history_ );
149 0 : FD_TEST( stake_history );
150 :
151 0 : fd_sol_sysvar_clock_t clock_[1];
152 0 : fd_sol_sysvar_clock_t const * clock = fd_sysvar_clock_read( accdb, &xid, clock_ );
153 0 : FD_TEST( clock );
154 :
155 : /* Epoch schedule and rent get set from the epoch bank */
156 0 : fd_sysvar_epoch_schedule_init( runner->bank, accdb, &xid, NULL );
157 0 : fd_sysvar_rent_init( runner->bank, accdb, &xid, NULL );
158 :
159 : /* Blockhash queue is given in txn message. We need to populate the following two fields:
160 : - block_hash_queue
161 : - recent_block_hashes */
162 0 : ulong num_blockhashes = test_ctx->blockhash_queue_count;
163 :
164 : /* Blockhash queue init */
165 0 : ulong blockhash_seed; FD_TEST( fd_rng_secure( &blockhash_seed, sizeof(ulong) ) );
166 0 : fd_blockhashes_t * blockhashes = fd_blockhashes_init( fd_bank_block_hash_queue_modify( runner->bank ), blockhash_seed );
167 :
168 : // Save lamports per signature for most recent blockhash, if sysvar cache contains recent block hashes
169 0 : uchar __attribute__((aligned(FD_SYSVAR_RECENT_HASHES_ALIGN))) rbh_mem[FD_SYSVAR_RECENT_HASHES_FOOTPRINT];
170 0 : fd_recent_block_hashes_t const * rbh_sysvar = fd_sysvar_recent_hashes_read( accdb, &xid, rbh_mem );
171 0 : fd_recent_block_hashes_t rbh[1];
172 0 : if( rbh_sysvar ) {
173 0 : rbh->hashes = rbh_sysvar->hashes;
174 0 : }
175 :
176 0 : if( rbh_sysvar && !deq_fd_block_block_hash_entry_t_empty( rbh->hashes ) ) {
177 0 : fd_block_block_hash_entry_t const * last = deq_fd_block_block_hash_entry_t_peek_head_const( rbh->hashes );
178 0 : if( last && last->fee_calculator.lamports_per_signature!=0UL ) {
179 0 : fd_bank_rbh_lamports_per_sig_set( runner->bank, last->fee_calculator.lamports_per_signature );
180 0 : }
181 0 : }
182 :
183 : // Blockhash_queue[end] = last (latest) hash
184 : // Blockhash_queue[0] = genesis hash
185 0 : if( num_blockhashes > 0 ) {
186 0 : fd_hash_t * genesis_hash = fd_bank_genesis_hash_modify( runner->bank );
187 0 : memcpy( genesis_hash->hash, test_ctx->blockhash_queue[0]->bytes, sizeof(fd_hash_t) );
188 :
189 0 : for( ulong i = 0; i < num_blockhashes; ++i ) {
190 0 : fd_hash_t blockhash = FD_LOAD( fd_hash_t, test_ctx->blockhash_queue[i]->bytes );
191 : /* Drop duplicate blockhashes */
192 0 : if( FD_UNLIKELY( fd_blockhash_map_idx_remove( blockhashes->map, &blockhash, ULONG_MAX, blockhashes->d.deque )!=ULONG_MAX ) ) {
193 0 : FD_BASE58_ENCODE_32_BYTES( blockhash.hash, blockhash_b58 );
194 0 : FD_LOG_WARNING(( "Fuzz input has a duplicate blockhash %s at index %lu", blockhash_b58, i ));
195 0 : }
196 : // Recent block hashes cap is 150 (actually 151), while blockhash queue capacity is 300 (actually 301)
197 0 : fd_bank_poh_set( runner->bank, blockhash );
198 0 : fd_sysvar_recent_hashes_update( runner->bank, accdb, &xid, NULL );
199 0 : }
200 0 : } else {
201 : // Add a default empty blockhash and use it as genesis
202 0 : num_blockhashes = 1;
203 0 : *fd_bank_genesis_hash_modify( runner->bank ) = (fd_hash_t){0};
204 0 : fd_bank_poh_set( runner->bank, (fd_hash_t){0} );
205 0 : fd_sysvar_recent_hashes_update( runner->bank, accdb, &xid, NULL );
206 0 : }
207 :
208 : /* Restore sysvars from account context */
209 0 : fd_sysvar_cache_restore_fuzz( runner->bank, runner->accdb, &xid );
210 :
211 : /* Create the raw txn (https://solana.com/docs/core/transactions#transaction-size) */
212 0 : fd_txn_p_t * txn = fd_spad_alloc( runner->spad, alignof(fd_txn_p_t), sizeof(fd_txn_p_t) );
213 0 : ulong msg_sz = fd_solfuzz_pb_txn_serialize( txn->payload, &test_ctx->tx );
214 0 : if( FD_UNLIKELY( msg_sz==ULONG_MAX ) ) {
215 0 : return NULL;
216 0 : }
217 :
218 : /* Set up txn descriptor from raw data */
219 0 : if( FD_UNLIKELY( !fd_txn_parse( txn->payload, msg_sz, TXN( txn ), NULL ) ) ) {
220 0 : return NULL;
221 0 : }
222 :
223 0 : txn->payload_sz = msg_sz;
224 :
225 0 : return txn;
226 0 : }
227 :
228 : ulong
229 : fd_solfuzz_pb_txn_serialize( uchar * txn_raw_begin,
230 0 : fd_exec_test_sanitized_transaction_t const * tx ) {
231 0 : uchar * txn_raw_cur_ptr = txn_raw_begin;
232 :
233 : /* Compact array of signatures (https://solana.com/docs/core/transactions#transaction)
234 : Note that although documentation interchangably refers to the signature cnt as a compact-u16
235 : and a u8, the max signature cnt is capped at 48 (due to txn size limits), so u8 and compact-u16
236 : is represented the same way anyways and can be parsed identically. */
237 : // Note: always create a valid txn with 1+ signatures, add an empty signature if none is provided
238 0 : uchar signature_cnt = fd_uchar_max( 1, (uchar) tx->signatures_count );
239 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &signature_cnt, sizeof(uchar) );
240 0 : for( uchar i = 0; i < signature_cnt; ++i ) {
241 0 : fd_signature_t sig = {0};
242 0 : if( tx->signatures && tx->signatures[i] ) sig = FD_LOAD( fd_signature_t, tx->signatures[i]->bytes );
243 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &sig, FD_TXN_SIGNATURE_SZ );
244 0 : }
245 :
246 : /* Message */
247 : /* For v0 transactions, the highest bit of the num_required_signatures is set, and an extra byte is used for the version.
248 : https://solanacookbook.com/guides/versioned-transactions.html#versioned-transactions-transactionv0
249 :
250 : We will always create a transaction with at least 1 signature, and cap the signature count to 127 to avoid
251 : collisions with the header_b0 tag. */
252 0 : uchar num_required_signatures = fd_uchar_max( 1, fd_uchar_min( 127, (uchar) tx->message.header.num_required_signatures ) );
253 0 : if( !tx->message.is_legacy ) {
254 0 : uchar header_b0 = (uchar) 0x80UL;
255 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &header_b0, sizeof(uchar) );
256 0 : }
257 :
258 : /* Header (3 bytes) (https://solana.com/docs/core/transactions#message-header) */
259 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &num_required_signatures, sizeof(uchar) );
260 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &tx->message.header.num_readonly_signed_accounts, sizeof(uchar) );
261 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &tx->message.header.num_readonly_unsigned_accounts, sizeof(uchar) );
262 :
263 : /* Compact array of account addresses (https://solana.com/docs/core/transactions#compact-array-format) */
264 : // Array length is a compact u16
265 0 : ushort num_acct_keys = (ushort) tx->message.account_keys_count;
266 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, num_acct_keys );
267 0 : for( ushort i = 0; i < num_acct_keys; ++i ) {
268 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, tx->message.account_keys[i]->bytes, sizeof(fd_pubkey_t) );
269 0 : }
270 :
271 : /* Recent blockhash (32 bytes) (https://solana.com/docs/core/transactions#recent-blockhash) */
272 : // Note: add an empty blockhash if none is provided
273 0 : fd_hash_t msg_rbh = {0};
274 0 : if( tx->message.recent_blockhash ) msg_rbh = FD_LOAD( fd_hash_t, tx->message.recent_blockhash->bytes );
275 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &msg_rbh, sizeof(fd_hash_t) );
276 :
277 : /* Compact array of instructions (https://solana.com/docs/core/transactions#array-of-instructions) */
278 : // Instruction count is a compact u16
279 0 : ushort instr_count = (ushort) tx->message.instructions_count;
280 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, instr_count );
281 0 : for( ushort i = 0; i < instr_count; ++i ) {
282 : // Program ID index
283 0 : uchar program_id_index = (uchar) tx->message.instructions[i].program_id_index;
284 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &program_id_index, sizeof(uchar) );
285 :
286 : // Compact array of account addresses
287 0 : ushort acct_count = (ushort) tx->message.instructions[i].accounts_count;
288 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, acct_count );
289 0 : for( ushort j = 0; j < acct_count; ++j ) {
290 0 : uchar account_index = (uchar) tx->message.instructions[i].accounts[j];
291 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &account_index, sizeof(uchar) );
292 0 : }
293 :
294 : // Compact array of 8-bit data
295 0 : pb_bytes_array_t * data = tx->message.instructions[i].data;
296 0 : ushort data_len;
297 0 : if( data ) {
298 0 : data_len = (ushort) data->size;
299 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data_len );
300 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data->bytes, data_len );
301 0 : } else {
302 0 : data_len = 0;
303 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data_len );
304 0 : }
305 0 : }
306 :
307 : /* Address table lookups (N/A for legacy transactions) */
308 0 : ushort addr_table_cnt = 0;
309 0 : if( !tx->message.is_legacy ) {
310 : /* Compact array of address table lookups (https://solanacookbook.com/guides/versioned-transactions.html#compact-array-of-address-table-lookups) */
311 : // NOTE: The diagram is slightly wrong - the account key is a 32 byte pubkey, not a u8
312 0 : addr_table_cnt = (ushort) tx->message.address_table_lookups_count;
313 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, addr_table_cnt );
314 0 : for( ushort i = 0; i < addr_table_cnt; ++i ) {
315 : // Account key
316 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) );
317 :
318 : // Compact array of writable indexes
319 0 : ushort writable_count = (ushort) tx->message.address_table_lookups[i].writable_indexes_count;
320 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, writable_count );
321 0 : for( ushort j = 0; j < writable_count; ++j ) {
322 0 : uchar writable_index = (uchar) tx->message.address_table_lookups[i].writable_indexes[j];
323 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &writable_index, sizeof(uchar) );
324 0 : }
325 :
326 : // Compact array of readonly indexes
327 0 : ushort readonly_count = (ushort) tx->message.address_table_lookups[i].readonly_indexes_count;
328 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, readonly_count );
329 0 : for( ushort j = 0; j < readonly_count; ++j ) {
330 0 : uchar readonly_index = (uchar) tx->message.address_table_lookups[i].readonly_indexes[j];
331 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &readonly_index, sizeof(uchar) );
332 0 : }
333 0 : }
334 0 : }
335 :
336 0 : return (ulong)(txn_raw_cur_ptr - txn_raw_begin);
337 0 : }
338 :
339 : void
340 : fd_solfuzz_txn_ctx_exec( fd_solfuzz_runner_t * runner,
341 : fd_runtime_t * runtime,
342 : fd_txn_in_t const * txn_in,
343 : int * exec_res,
344 0 : fd_txn_out_t * txn_out ) {
345 :
346 0 : txn_out->err.is_committable = 1;
347 :
348 0 : runtime->log.enable_vm_tracing = runner->enable_vm_tracing;
349 0 : uchar * tracing_mem = NULL;
350 0 : if( runner->enable_vm_tracing ) {
351 0 : tracing_mem = fd_spad_alloc_check( runner->spad, FD_RUNTIME_VM_TRACE_STATIC_ALIGN, FD_RUNTIME_VM_TRACE_STATIC_FOOTPRINT * FD_MAX_INSTRUCTION_STACK_DEPTH );
352 0 : }
353 :
354 0 : runtime->accdb = runner->accdb;
355 0 : runtime->progcache = runner->progcache;
356 0 : runtime->status_cache = NULL;
357 0 : runtime->log.tracing_mem = tracing_mem;
358 0 : runtime->log.dumping_mem = NULL;
359 0 : runtime->log.capture_ctx = NULL;
360 :
361 0 : fd_runtime_prepare_and_execute_txn( runtime, runner->bank, txn_in, txn_out );
362 0 : *exec_res = txn_out->err.txn_err;
363 0 : }
364 :
365 : ulong
366 : fd_solfuzz_pb_txn_run( fd_solfuzz_runner_t * runner,
367 : void const * input_,
368 : void ** output_,
369 : void * output_buf,
370 0 : ulong output_bufsz ) {
371 0 : fd_exec_test_txn_context_t const * input = fd_type_pun_const( input_ );
372 0 : fd_exec_test_txn_result_t ** output = fd_type_pun( output_ );
373 :
374 0 : FD_SPAD_FRAME_BEGIN( runner->spad ) {
375 :
376 : /* Setup the transaction context */
377 0 : fd_txn_p_t * txn = fd_solfuzz_pb_txn_ctx_create( runner, input );
378 0 : if( FD_UNLIKELY( txn==NULL ) ) {
379 0 : fd_solfuzz_txn_ctx_destroy( runner );
380 0 : return 0UL;
381 0 : }
382 :
383 : /* Execute the transaction against the runtime */
384 0 : int exec_res = 0;
385 0 : fd_runtime_t * runtime = runner->runtime;
386 0 : fd_txn_in_t * txn_in = fd_spad_alloc( runner->spad, alignof(fd_txn_in_t), sizeof(fd_txn_in_t) );
387 0 : fd_txn_out_t * txn_out = fd_spad_alloc( runner->spad, alignof(fd_txn_out_t), sizeof(fd_txn_out_t) );
388 0 : fd_log_collector_t * log = fd_spad_alloc( runner->spad, alignof(fd_log_collector_t), sizeof(fd_log_collector_t) );
389 0 : runtime->log.log_collector = log;
390 0 : runtime->acc_pool = runner->acc_pool;
391 0 : txn_in->txn = txn;
392 0 : txn_in->bundle.is_bundle = 0;
393 0 : fd_solfuzz_txn_ctx_exec( runner, runtime, txn_in, &exec_res, txn_out );
394 :
395 : /* Start saving txn exec results */
396 0 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
397 0 : ulong output_end = (ulong)output_buf + output_bufsz;
398 :
399 0 : fd_exec_test_txn_result_t * txn_result =
400 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_txn_result_t),
401 0 : sizeof (fd_exec_test_txn_result_t) );
402 0 : if( FD_UNLIKELY( _l > output_end ) ) {
403 0 : abort();
404 0 : }
405 0 : fd_memset( txn_result, 0, sizeof(fd_exec_test_txn_result_t) );
406 :
407 : /* Map the nonce errors into the agave expected ones. */
408 0 : if( FD_UNLIKELY( exec_res==FD_RUNTIME_TXN_ERR_BLOCKHASH_NONCE_ALREADY_ADVANCED ||
409 0 : exec_res==FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_ADVANCE_NONCE_INSTR ||
410 0 : exec_res==FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_WRONG_NONCE )) {
411 0 : exec_res = FD_RUNTIME_TXN_ERR_BLOCKHASH_NOT_FOUND;
412 0 : }
413 :
414 : /* Capture basic results fields */
415 0 : txn_result->executed = txn_out->err.is_committable;
416 0 : txn_result->sanitization_error = !txn_out->err.is_committable;
417 0 : txn_result->has_resulting_state = false;
418 0 : txn_result->resulting_state.acct_states_count = 0;
419 0 : txn_result->is_ok = !exec_res;
420 0 : txn_result->status = (uint32_t) -exec_res;
421 0 : txn_result->instruction_error = 0;
422 0 : txn_result->instruction_error_index = 0;
423 0 : txn_result->custom_error = 0;
424 0 : txn_result->has_fee_details = false;
425 0 : txn_result->loaded_accounts_data_size = txn_out->details.loaded_accounts_data_size;
426 :
427 0 : if( txn_result->sanitization_error ) {
428 : /* Collect fees for transactions that failed to load */
429 0 : if( txn_out->err.is_fees_only ) {
430 0 : txn_result->has_fee_details = true;
431 0 : txn_result->fee_details.prioritization_fee = txn_out->details.priority_fee;
432 0 : txn_result->fee_details.transaction_fee = txn_out->details.execution_fee;
433 0 : }
434 :
435 0 : if( exec_res==FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR ) {
436 0 : txn_result->instruction_error = (uint32_t) -txn_out->err.exec_err;
437 0 : txn_result->instruction_error_index = (uint32_t) txn_out->err.exec_err_idx;
438 0 : if( txn_out->err.exec_err==FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR ) {
439 0 : txn_result->custom_error = txn_out->err.custom_err;
440 0 : }
441 0 : }
442 :
443 0 : ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
444 :
445 0 : txn_out->err.is_committable = 0;
446 0 : fd_runtime_cancel_txn( runner->runtime, txn_out );
447 0 : fd_solfuzz_txn_ctx_destroy( runner );
448 :
449 0 : *output = txn_result;
450 0 : return actual_end - (ulong)output_buf;
451 :
452 0 : } else {
453 : /* Capture the instruction error code */
454 0 : if( exec_res==FD_RUNTIME_TXN_ERR_INSTRUCTION_ERROR ) {
455 0 : fd_txn_t const * txn = TXN( txn_in->txn );
456 0 : int instr_err_idx = txn_out->err.exec_err_idx;
457 0 : int program_id_idx = txn->instr[instr_err_idx].program_id;
458 :
459 0 : txn_result->instruction_error = (uint32_t) -txn_out->err.exec_err;
460 0 : txn_result->instruction_error_index = (uint32_t) instr_err_idx;
461 :
462 : /* If the exec err was a custom instr error and came from a precompile instruction, don't capture the custom error code. */
463 0 : if( txn_out->err.exec_err==FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR &&
464 0 : fd_executor_lookup_native_precompile_program( &txn_out->accounts.keys[ program_id_idx ] )==NULL ) {
465 0 : txn_result->custom_error = txn_out->err.custom_err;
466 0 : }
467 0 : }
468 0 : }
469 :
470 0 : txn_result->has_fee_details = true;
471 0 : txn_result->fee_details.transaction_fee = txn_out->details.execution_fee;
472 0 : txn_result->fee_details.prioritization_fee = txn_out->details.priority_fee;
473 0 : txn_result->executed_units = txn_out->details.compute_budget.compute_unit_limit - txn_out->details.compute_budget.compute_meter;
474 :
475 :
476 : /* Rent is no longer collected */
477 0 : txn_result->rent = 0UL;
478 :
479 0 : if( txn_out->details.return_data.len > 0 ) {
480 0 : txn_result->return_data = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t),
481 0 : PB_BYTES_ARRAY_T_ALLOCSIZE( txn_out->details.return_data.len ) );
482 0 : if( FD_UNLIKELY( _l > output_end ) ) {
483 0 : abort();
484 0 : }
485 :
486 0 : txn_result->return_data->size = (pb_size_t)txn_out->details.return_data.len;
487 0 : fd_memcpy( txn_result->return_data->bytes, txn_out->details.return_data.data, txn_out->details.return_data.len );
488 0 : }
489 :
490 : /* Allocate space for captured accounts */
491 0 : txn_result->has_resulting_state = true;
492 0 : txn_result->resulting_state.acct_states =
493 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_acct_state_t),
494 0 : sizeof (fd_exec_test_acct_state_t) * txn_out->accounts.cnt );
495 0 : if( FD_UNLIKELY( _l > output_end ) ) {
496 0 : abort();
497 0 : }
498 :
499 : /* There are two possible outcomes when a transaction passes
500 : sanitization checks in the runtime:
501 : 1. The transaction fails to load and is not executed (i.e.
502 : load_transaction_accounts fails). We only capture the fee
503 : payer and nonce accounts.
504 : 2. The transaction successfully loads, and is executed. This case
505 : includes both transactions that succeed and transactions that
506 : fail with an instruction error. If the transaction succeeds,
507 : the resulting account states for writable accounts are all
508 : captured. If the transaction failed with an instruction error,
509 : all changes made to the writable accounts up until the
510 : instruction error are captured.
511 : */
512 0 : if( txn_out->err.is_fees_only ) {
513 : /* If the transaction is a fees-only transaction, only capture the
514 : rollback accounts. Note that there is a weird allowed edge
515 : case where the nonce account can be the fee payer, so we add
516 : special casing to make sure we don't capture the fee payer
517 : twice. */
518 0 : if( FD_LIKELY( txn_out->accounts.nonce_idx_in_txn!=FD_FEE_PAYER_TXN_IDX ) ) {
519 0 : fd_pubkey_t * fee_payer_key = &txn_out->accounts.keys[FD_FEE_PAYER_TXN_IDX];
520 0 : fd_account_meta_t * fee_payer_meta = txn_out->accounts.rollback_fee_payer;
521 0 : pb_bytes_array_t * out_account_data = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( fee_payer_meta->dlen ) );
522 0 : if( FD_UNLIKELY( _l>output_end ) ) {
523 0 : abort();
524 0 : }
525 :
526 0 : fd_solfuzz_pb_txn_ctx_write_account( fee_payer_key, fee_payer_meta, &txn_result->resulting_state, out_account_data );
527 0 : }
528 :
529 0 : if( txn_out->accounts.nonce_idx_in_txn!=ULONG_MAX ) {
530 0 : fd_pubkey_t * nonce_key = &txn_out->accounts.keys[txn_out->accounts.nonce_idx_in_txn];
531 0 : fd_account_meta_t * nonce_meta = txn_out->accounts.rollback_nonce;
532 0 : pb_bytes_array_t * out_account_data = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( nonce_meta->dlen ) );
533 0 : if( FD_UNLIKELY( _l>output_end ) ) {
534 0 : abort();
535 0 : }
536 :
537 0 : fd_solfuzz_pb_txn_ctx_write_account( nonce_key, nonce_meta, &txn_result->resulting_state, out_account_data );
538 0 : }
539 0 : } else {
540 : /* Transaction executed, capture fee payer and other writable
541 : accounts */
542 0 : for( ulong j=0UL; j<txn_out->accounts.cnt; j++ ) {
543 0 : fd_pubkey_t * pubkey = &txn_out->accounts.keys[j];
544 0 : fd_account_meta_t * meta = txn_out->accounts.account[j].meta;
545 :
546 0 : if( !( fd_runtime_account_is_writable_idx( txn_in, txn_out, runner->bank, (ushort)j ) || /* Capture writable accounts */
547 0 : j==FD_FEE_PAYER_TXN_IDX ) ) { /* Capture the fee payer account */
548 0 : continue;
549 0 : }
550 :
551 0 : pb_bytes_array_t * out_account_data = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( meta->dlen ) );
552 0 : if( FD_UNLIKELY( _l>output_end ) ) {
553 0 : abort();
554 0 : }
555 :
556 0 : fd_solfuzz_pb_txn_ctx_write_account( pubkey, meta, &txn_result->resulting_state, out_account_data );
557 0 : }
558 0 : }
559 :
560 0 : ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
561 0 : txn_out->err.is_committable = 0;
562 0 : fd_runtime_cancel_txn( runner->runtime, txn_out );
563 0 : fd_solfuzz_txn_ctx_destroy( runner );
564 :
565 0 : *output = txn_result;
566 0 : return actual_end - (ulong)output_buf;
567 0 : } FD_SPAD_FRAME_END;
568 0 : }
|