Line data Source code
1 : #include "fd_solfuzz.h"
2 : #include "fd_solfuzz_private.h"
3 : #include "fd_txn_harness.h"
4 : #include "fd_dump_pb.h"
5 : #include "../fd_runtime.h"
6 : #include "../sysvar/fd_sysvar_epoch_schedule.h"
7 : #include "../sysvar/fd_sysvar_rent.h"
8 : #include "../../accdb/fd_accdb_admin_v1.h"
9 : #include "../../accdb/fd_accdb_impl_v1.h"
10 : #include "../../progcache/fd_progcache_admin.h"
11 : #include "../../log_collector/fd_log_collector.h"
12 : #include "../fd_system_ids.h"
13 :
14 : /* Macros to append data to construct a serialized transaction
15 : without exceeding bounds */
16 0 : #define FD_CHECKED_ADD_TO_TXN_DATA( _begin, _cur_data, _to_add, _sz ) __extension__({ \
17 0 : if( FD_UNLIKELY( (*_cur_data)+_sz>_begin+FD_TXN_MTU ) ) return ULONG_MAX; \
18 0 : fd_memcpy( *_cur_data, _to_add, _sz ); \
19 0 : *_cur_data += _sz; \
20 0 : })
21 :
22 0 : #define FD_CHECKED_ADD_CU16_TO_TXN_DATA( _begin, _cur_data, _to_add ) __extension__({ \
23 0 : do { \
24 0 : uchar _buf[3]; \
25 0 : fd_bincode_encode_ctx_t _encode_ctx = { .data = _buf, .dataend = _buf+3 }; \
26 0 : fd_bincode_compact_u16_encode( &_to_add, &_encode_ctx ); \
27 0 : ulong _sz = (ulong) ((uchar *)_encode_ctx.data - _buf ); \
28 0 : FD_CHECKED_ADD_TO_TXN_DATA( _begin, _cur_data, _buf, _sz ); \
29 0 : } while(0); \
30 0 : })
31 :
32 : static void
33 0 : fd_solfuzz_txn_ctx_destroy( fd_solfuzz_runner_t * runner ) {
34 0 : fd_accdb_v1_clear( runner->accdb_admin );
35 0 : fd_progcache_clear( runner->progcache->join );
36 :
37 : /* In order to check for leaks in the workspace, we need to compact the
38 : allocators. Without doing this, empty superblocks may be retained
39 : by the fd_alloc instance, which mean we cannot check for leaks. */
40 0 : fd_alloc_compact( fd_accdb_user_v1_funk( runner->accdb )->alloc );
41 0 : fd_alloc_compact( runner->progcache->join->alloc );
42 0 : }
43 :
44 : /* Creates transaction execution context for a single test case.
45 : Returns a parsed txn descriptor on success and NULL on failure. */
46 : static fd_txn_p_t *
47 : fd_solfuzz_pb_txn_ctx_create( fd_solfuzz_runner_t * runner,
48 0 : fd_exec_test_txn_context_t const * test_ctx ) {
49 0 : fd_accdb_user_t * accdb = runner->accdb;
50 :
51 : /* Set up the funk transaction */
52 0 : ulong slot = fd_solfuzz_pb_get_slot( test_ctx->account_shared_data, test_ctx->account_shared_data_count );
53 0 : fd_funk_txn_xid_t xid = { .ul = { slot, runner->bank->idx } };
54 0 : fd_funk_txn_xid_t parent_xid; fd_funk_txn_xid_set_root( &parent_xid );
55 0 : fd_accdb_attach_child ( runner->accdb_admin, &parent_xid, &xid );
56 0 : fd_progcache_attach_child( runner->progcache->join, &parent_xid, &xid );
57 :
58 : /* Initialize bank from input txn bank */
59 0 : fd_banks_clear_bank( runner->banks, runner->bank, 64UL );
60 0 : FD_TEST( test_ctx->has_bank );
61 0 : fd_exec_test_txn_bank_t const * txn_bank = &test_ctx->bank;
62 :
63 : /* Slot*/
64 0 : runner->bank->f.slot = slot;
65 :
66 : /* Blockhash queue */
67 0 : fd_solfuzz_pb_restore_blockhash_queue( runner->bank, txn_bank->blockhash_queue, txn_bank->blockhash_queue_count );
68 :
69 : /* RBH lamports per signature. In the Agave harness this is set inside
70 : the fee rate governor itself. */
71 0 : runner->bank->f.rbh_lamports_per_sig = txn_bank->rbh_lamports_per_signature;
72 :
73 : /* Fee rate governor */
74 0 : FD_TEST( txn_bank->has_fee_rate_governor );
75 0 : fd_solfuzz_pb_restore_fee_rate_governor( runner->bank, &txn_bank->fee_rate_governor );
76 :
77 : /* Parent slot */
78 0 : runner->bank->f.parent_slot = slot-1UL;
79 :
80 : /* Total epoch stake */
81 0 : runner->bank->f.total_epoch_stake = txn_bank->total_epoch_stake;
82 :
83 : /* Epoch schedule */
84 0 : FD_TEST( txn_bank->has_epoch_schedule );
85 0 : fd_solfuzz_pb_restore_epoch_schedule( runner->bank, &txn_bank->epoch_schedule );
86 :
87 : /* Features */
88 0 : FD_TEST( txn_bank->has_features );
89 0 : fd_exec_test_feature_set_t const * feature_set = &txn_bank->features;
90 0 : fd_features_t * features_bm = &runner->bank->f.features;
91 0 : FD_TEST( fd_solfuzz_pb_restore_features( features_bm, feature_set ) );
92 :
93 : /* Epoch */
94 0 : ulong epoch = fd_slot_to_epoch( &runner->bank->f.epoch_schedule, slot, NULL );
95 0 : runner->bank->f.epoch = epoch;
96 :
97 : /* Load account states into funk (note this is different from the account keys):
98 : Account state = accounts to populate Funk
99 : Account keys = account keys that the transaction needs */
100 0 : for( ulong i = 0; i < test_ctx->account_shared_data_count; i++ ) {
101 : /* Load the accounts into the account manager
102 : Borrowed accounts get reset anyways - we just need to load the account somewhere */
103 0 : fd_solfuzz_pb_load_account( runner->runtime, accdb, &xid, &test_ctx->account_shared_data[i], i );
104 0 : }
105 :
106 0 : runner->bank->f.ticks_per_slot = 64;
107 0 : runner->bank->f.slots_per_year = SECONDS_PER_YEAR * (1000000000.0 / (double)6250000) / (double)(runner->bank->f.ticks_per_slot);
108 :
109 : /* Restore sysvars from account context */
110 0 : fd_sysvar_cache_restore_fuzz( runner->bank, runner->accdb, &xid );
111 :
112 : /* Rent */
113 0 : FD_TEST( fd_sysvar_cache_rent_read( &runner->bank->f.sysvar_cache, &runner->bank->f.rent ) );
114 :
115 : /* Create the raw txn (https://solana.com/docs/core/transactions#transaction-size) */
116 0 : fd_txn_p_t * txn = fd_spad_alloc( runner->spad, alignof(fd_txn_p_t), sizeof(fd_txn_p_t) );
117 0 : ulong msg_sz = fd_solfuzz_pb_txn_serialize( txn->payload, &test_ctx->tx );
118 0 : if( FD_UNLIKELY( msg_sz==ULONG_MAX ) ) {
119 0 : return NULL;
120 0 : }
121 :
122 : /* Set up txn descriptor from raw data */
123 0 : if( FD_UNLIKELY( !fd_txn_parse( txn->payload, msg_sz, TXN( txn ), NULL ) ) ) {
124 0 : return NULL;
125 0 : }
126 :
127 0 : txn->payload_sz = msg_sz;
128 :
129 0 : return txn;
130 0 : }
131 :
132 : ulong
133 : fd_solfuzz_pb_txn_serialize( uchar * txn_raw_begin,
134 0 : fd_exec_test_sanitized_transaction_t const * tx ) {
135 0 : uchar * txn_raw_cur_ptr = txn_raw_begin;
136 :
137 : /* Compact array of signatures (https://solana.com/docs/core/transactions#transaction)
138 : Note that although documentation interchangably refers to the signature cnt as a compact-u16
139 : and a u8, the max signature cnt is capped at 48 (due to txn size limits), so u8 and compact-u16
140 : is represented the same way anyways and can be parsed identically. */
141 : // Note: always create a valid txn with 1+ signatures, add an empty signature if none is provided
142 0 : uchar signature_cnt = fd_uchar_max( 1, (uchar) tx->signatures_count );
143 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &signature_cnt, sizeof(uchar) );
144 0 : for( uchar i = 0; i < signature_cnt; ++i ) {
145 0 : fd_signature_t sig = {0};
146 0 : if( tx->signatures && tx->signatures[i] ) sig = FD_LOAD( fd_signature_t, tx->signatures[i]->bytes );
147 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &sig, FD_TXN_SIGNATURE_SZ );
148 0 : }
149 :
150 : /* Message */
151 : /* For v0 transactions, the highest bit of the num_required_signatures is set, and an extra byte is used for the version.
152 : https://solanacookbook.com/guides/versioned-transactions.html#versioned-transactions-transactionv0
153 :
154 : We will always create a transaction with at least 1 signature, and cap the signature count to 127 to avoid
155 : collisions with the header_b0 tag. */
156 0 : uchar num_required_signatures = fd_uchar_max( 1, fd_uchar_min( 127, (uchar) tx->message.header.num_required_signatures ) );
157 0 : if( !tx->message.is_legacy ) {
158 0 : uchar header_b0 = (uchar) 0x80UL;
159 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &header_b0, sizeof(uchar) );
160 0 : }
161 :
162 : /* Header (3 bytes) (https://solana.com/docs/core/transactions#message-header) */
163 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &num_required_signatures, sizeof(uchar) );
164 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &tx->message.header.num_readonly_signed_accounts, sizeof(uchar) );
165 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &tx->message.header.num_readonly_unsigned_accounts, sizeof(uchar) );
166 :
167 : /* Compact array of account addresses (https://solana.com/docs/core/transactions#compact-array-format) */
168 : // Array length is a compact u16
169 0 : ushort num_acct_keys = (ushort) tx->message.account_keys_count;
170 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, num_acct_keys );
171 0 : for( ushort i = 0; i < num_acct_keys; ++i ) {
172 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, tx->message.account_keys[i]->bytes, sizeof(fd_pubkey_t) );
173 0 : }
174 :
175 : /* Recent blockhash (32 bytes) (https://solana.com/docs/core/transactions#recent-blockhash) */
176 : // Note: add an empty blockhash if none is provided
177 0 : fd_hash_t msg_rbh = {0};
178 0 : if( tx->message.recent_blockhash ) msg_rbh = FD_LOAD( fd_hash_t, tx->message.recent_blockhash->bytes );
179 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &msg_rbh, sizeof(fd_hash_t) );
180 :
181 : /* Compact array of instructions (https://solana.com/docs/core/transactions#array-of-instructions) */
182 : // Instruction count is a compact u16
183 0 : ushort instr_count = (ushort) tx->message.instructions_count;
184 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, instr_count );
185 0 : for( ushort i = 0; i < instr_count; ++i ) {
186 : // Program ID index
187 0 : uchar program_id_index = (uchar) tx->message.instructions[i].program_id_index;
188 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &program_id_index, sizeof(uchar) );
189 :
190 : // Compact array of account addresses
191 0 : ushort acct_count = (ushort) tx->message.instructions[i].accounts_count;
192 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, acct_count );
193 0 : for( ushort j = 0; j < acct_count; ++j ) {
194 0 : uchar account_index = (uchar) tx->message.instructions[i].accounts[j];
195 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &account_index, sizeof(uchar) );
196 0 : }
197 :
198 : // Compact array of 8-bit data
199 0 : pb_bytes_array_t * data = tx->message.instructions[i].data;
200 0 : ushort data_len;
201 0 : if( data ) {
202 0 : data_len = (ushort) data->size;
203 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data_len );
204 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data->bytes, data_len );
205 0 : } else {
206 0 : data_len = 0;
207 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data_len );
208 0 : }
209 0 : }
210 :
211 : /* Address table lookups (N/A for legacy transactions) */
212 0 : ushort addr_table_cnt = 0;
213 0 : if( !tx->message.is_legacy ) {
214 : /* Compact array of address table lookups (https://solanacookbook.com/guides/versioned-transactions.html#compact-array-of-address-table-lookups) */
215 : // NOTE: The diagram is slightly wrong - the account key is a 32 byte pubkey, not a u8
216 0 : addr_table_cnt = (ushort) tx->message.address_table_lookups_count;
217 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, addr_table_cnt );
218 0 : for( ushort i = 0; i < addr_table_cnt; ++i ) {
219 : // Account key
220 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) );
221 :
222 : // Compact array of writable indexes
223 0 : ushort writable_count = (ushort) tx->message.address_table_lookups[i].writable_indexes_count;
224 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, writable_count );
225 0 : for( ushort j = 0; j < writable_count; ++j ) {
226 0 : uchar writable_index = (uchar) tx->message.address_table_lookups[i].writable_indexes[j];
227 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &writable_index, sizeof(uchar) );
228 0 : }
229 :
230 : // Compact array of readonly indexes
231 0 : ushort readonly_count = (ushort) tx->message.address_table_lookups[i].readonly_indexes_count;
232 0 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, readonly_count );
233 0 : for( ushort j = 0; j < readonly_count; ++j ) {
234 0 : uchar readonly_index = (uchar) tx->message.address_table_lookups[i].readonly_indexes[j];
235 0 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &readonly_index, sizeof(uchar) );
236 0 : }
237 0 : }
238 0 : }
239 :
240 0 : return (ulong)(txn_raw_cur_ptr - txn_raw_begin);
241 0 : }
242 :
243 : void
244 : fd_solfuzz_txn_ctx_exec( fd_solfuzz_runner_t * runner,
245 : fd_runtime_t * runtime,
246 : fd_txn_in_t const * txn_in,
247 : int * exec_res,
248 0 : fd_txn_out_t * txn_out ) {
249 :
250 0 : txn_out->err.is_committable = 1;
251 :
252 0 : runtime->log.enable_vm_tracing = runner->enable_vm_tracing;
253 0 : uchar * tracing_mem = NULL;
254 0 : if( runner->enable_vm_tracing ) {
255 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 );
256 0 : }
257 :
258 0 : runtime->accdb = runner->accdb;
259 0 : runtime->progcache = runner->progcache;
260 0 : runtime->status_cache = NULL;
261 0 : runtime->log.tracing_mem = tracing_mem;
262 0 : runtime->log.dumping_mem = NULL;
263 0 : runtime->log.capture_ctx = NULL;
264 0 : runtime->log.dump_proto_ctx = NULL;
265 0 : runtime->log.txn_dump_ctx = NULL;
266 0 : runtime->fuzz.enabled = 1;
267 :
268 0 : fd_runtime_prepare_and_execute_txn( runtime, runner->bank, txn_in, txn_out );
269 0 : *exec_res = txn_out->err.txn_err;
270 0 : }
271 :
272 : ulong
273 : fd_solfuzz_pb_txn_run( fd_solfuzz_runner_t * runner,
274 : void const * input_,
275 : void ** output_,
276 : void * output_buf,
277 0 : ulong output_bufsz ) {
278 0 : fd_exec_test_txn_context_t const * input = fd_type_pun_const( input_ );
279 0 : fd_exec_test_txn_result_t ** output = fd_type_pun( output_ );
280 :
281 0 : FD_SPAD_FRAME_BEGIN( runner->spad ) {
282 :
283 : /* Setup the transaction context */
284 0 : fd_txn_p_t * txn = fd_solfuzz_pb_txn_ctx_create( runner, input );
285 0 : if( FD_UNLIKELY( txn==NULL ) ) {
286 0 : fd_solfuzz_txn_ctx_destroy( runner );
287 0 : return 0UL;
288 0 : }
289 :
290 : /* Execute the transaction against the runtime */
291 0 : int exec_res = 0;
292 0 : fd_runtime_t * runtime = runner->runtime;
293 0 : fd_txn_in_t * txn_in = fd_spad_alloc( runner->spad, alignof(fd_txn_in_t), sizeof(fd_txn_in_t) );
294 0 : fd_txn_out_t * txn_out = fd_spad_alloc( runner->spad, alignof(fd_txn_out_t), sizeof(fd_txn_out_t) );
295 0 : fd_log_collector_t * log = fd_spad_alloc( runner->spad, alignof(fd_log_collector_t), sizeof(fd_log_collector_t) );
296 0 : runtime->log.log_collector = log;
297 0 : runtime->acc_pool = runner->acc_pool;
298 0 : txn_in->txn = txn;
299 0 : txn_in->bundle.is_bundle = 0;
300 0 : fd_solfuzz_txn_ctx_exec( runner, runtime, txn_in, &exec_res, txn_out );
301 :
302 : /* Build result directly into the caller-owned output_buf */
303 0 : fd_exec_test_txn_result_t * txn_result = NULL;
304 0 : ulong result_sz = create_txn_result_protobuf_from_txn(
305 0 : &txn_result,
306 0 : output_buf,
307 0 : output_bufsz,
308 0 : txn_in,
309 0 : txn_out,
310 0 : exec_res
311 0 : );
312 :
313 0 : txn_out->err.is_committable = 0;
314 0 : fd_runtime_cancel_txn( runner->runtime, txn_out );
315 0 : fd_solfuzz_txn_ctx_destroy( runner );
316 :
317 0 : *output = txn_result;
318 0 : return result_sz;
319 0 : } FD_SPAD_FRAME_END;
320 0 : }
|