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 "../../accdb/fd_accdb_admin_v1.h"
8 : #include "../../accdb/fd_accdb_impl_v1.h"
9 : #include "../../progcache/fd_progcache_admin.h"
10 : #include "../../log_collector/fd_log_collector.h"
11 : #include "../fd_system_ids.h"
12 :
13 : /* Macros to append data to construct a serialized transaction
14 : without exceeding bounds */
15 152188 : #define FD_CHECKED_ADD_TO_TXN_DATA( _begin, _cur_data, _to_add, _sz ) __extension__({ \
16 152188 : if( FD_UNLIKELY( (*_cur_data)+_sz>_begin+FD_TXN_MTU ) ) return ULONG_MAX; \
17 152188 : fd_memcpy( *_cur_data, _to_add, _sz ); \
18 152188 : *_cur_data += _sz; \
19 152188 : })
20 :
21 38463 : #define FD_CHECKED_ADD_CU16_TO_TXN_DATA( _begin, _cur_data, _to_add ) __extension__({ \
22 38463 : do { \
23 38463 : uchar _buf[3]; \
24 38463 : fd_bincode_encode_ctx_t _encode_ctx = { .data = _buf, .dataend = _buf+3 }; \
25 38463 : fd_bincode_compact_u16_encode( &_to_add, &_encode_ctx ); \
26 38463 : ulong _sz = (ulong) ((uchar *)_encode_ctx.data - _buf ); \
27 38463 : FD_CHECKED_ADD_TO_TXN_DATA( _begin, _cur_data, _buf, _sz ); \
28 38463 : } while(0); \
29 38463 : })
30 :
31 : static void
32 5244 : fd_solfuzz_txn_ctx_destroy( fd_solfuzz_runner_t * runner ) {
33 5244 : fd_accdb_v1_clear( runner->accdb_admin );
34 5244 : fd_progcache_clear( runner->progcache->join );
35 :
36 : /* In order to check for leaks in the workspace, we need to compact the
37 : allocators. Without doing this, empty superblocks may be retained
38 : by the fd_alloc instance, which mean we cannot check for leaks. */
39 5244 : fd_alloc_compact( fd_accdb_user_v1_funk( runner->accdb )->alloc );
40 5244 : fd_alloc_compact( runner->progcache->join->alloc );
41 5244 : }
42 :
43 : /* Creates transaction execution context for a single test case.
44 : Returns a parsed txn descriptor on success and NULL on failure. */
45 : static fd_txn_p_t *
46 : fd_solfuzz_pb_txn_ctx_create( fd_solfuzz_runner_t * runner,
47 5230 : fd_exec_test_txn_context_t const * test_ctx ) {
48 5230 : fd_accdb_user_t * accdb = runner->accdb;
49 :
50 : /* Set up the funk transaction */
51 5230 : ulong slot = fd_solfuzz_pb_get_slot( test_ctx->account_shared_data, test_ctx->account_shared_data_count );
52 5230 : fd_funk_txn_xid_t xid = { .ul = { slot, runner->bank->data->idx } };
53 5230 : fd_funk_txn_xid_t parent_xid; fd_funk_txn_xid_set_root( &parent_xid );
54 5230 : fd_accdb_attach_child ( runner->accdb_admin, &parent_xid, &xid );
55 5230 : fd_progcache_txn_attach_child( runner->progcache->join, &parent_xid, &xid );
56 :
57 : /* Initialize bank from input txn bank */
58 5230 : fd_banks_clear_bank( runner->banks, runner->bank, 64UL );
59 5230 : FD_TEST( test_ctx->has_bank );
60 5230 : fd_exec_test_txn_bank_t const * txn_bank = &test_ctx->bank;
61 :
62 : /* Slot*/
63 5230 : fd_bank_slot_set( runner->bank, slot );
64 :
65 : /* Blockhash queue */
66 5230 : fd_solfuzz_pb_restore_blockhash_queue( runner->bank, txn_bank->blockhash_queue, txn_bank->blockhash_queue_count );
67 :
68 : /* RBH lamports per signature. In the Agave harness this is set inside
69 : the fee rate governor itself. */
70 5230 : fd_bank_rbh_lamports_per_sig_set( runner->bank, txn_bank->rbh_lamports_per_signature );
71 :
72 : /* Fee rate governor */
73 5230 : FD_TEST( txn_bank->has_fee_rate_governor );
74 5230 : fd_solfuzz_pb_restore_fee_rate_governor( runner->bank, &txn_bank->fee_rate_governor );
75 :
76 : /* Parent slot */
77 5230 : fd_bank_parent_slot_set( runner->bank, slot-1UL );
78 :
79 : /* Total epoch stake */
80 5230 : fd_bank_total_epoch_stake_set( runner->bank, txn_bank->total_epoch_stake );
81 :
82 : /* Epoch schedule */
83 5230 : FD_TEST( txn_bank->has_epoch_schedule );
84 5230 : fd_solfuzz_pb_restore_epoch_schedule( runner->bank, &txn_bank->epoch_schedule );
85 :
86 : /* Rent */
87 5230 : FD_TEST( txn_bank->has_rent );
88 5230 : fd_solfuzz_pb_restore_rent( runner->bank, &txn_bank->rent );
89 :
90 : /* Features */
91 5230 : FD_TEST( txn_bank->has_features );
92 5230 : fd_exec_test_feature_set_t const * feature_set = &txn_bank->features;
93 5230 : fd_features_t * features_bm = fd_bank_features_modify( runner->bank );
94 5230 : FD_TEST( fd_solfuzz_pb_restore_features( features_bm, feature_set ) );
95 :
96 : /* Epoch */
97 5230 : ulong epoch = fd_slot_to_epoch( fd_bank_epoch_schedule_query( runner->bank ), slot, NULL );
98 5230 : fd_bank_epoch_set( runner->bank, epoch );
99 :
100 : /* Load account states into funk (note this is different from the account keys):
101 : Account state = accounts to populate Funk
102 : Account keys = account keys that the transaction needs */
103 126688 : for( ulong i = 0; i < test_ctx->account_shared_data_count; i++ ) {
104 : /* Load the accounts into the account manager
105 : Borrowed accounts get reset anyways - we just need to load the account somewhere */
106 121458 : fd_solfuzz_pb_load_account( runner->runtime, accdb, &xid, &test_ctx->account_shared_data[i], i );
107 121458 : }
108 :
109 5230 : fd_bank_ticks_per_slot_set( runner->bank, 64 );
110 5230 : fd_bank_slots_per_year_set( runner->bank, SECONDS_PER_YEAR * (1000000000.0 / (double)6250000) / (double)(fd_bank_ticks_per_slot_get( runner->bank )) );
111 :
112 : /* Restore sysvars from account context */
113 5230 : fd_sysvar_cache_restore_fuzz( runner->bank, runner->accdb, &xid );
114 :
115 : /* Create the raw txn (https://solana.com/docs/core/transactions#transaction-size) */
116 5230 : fd_txn_p_t * txn = fd_spad_alloc( runner->spad, alignof(fd_txn_p_t), sizeof(fd_txn_p_t) );
117 5230 : ulong msg_sz = fd_solfuzz_pb_txn_serialize( txn->payload, &test_ctx->tx );
118 5230 : if( FD_UNLIKELY( msg_sz==ULONG_MAX ) ) {
119 0 : return NULL;
120 0 : }
121 :
122 : /* Set up txn descriptor from raw data */
123 5230 : if( FD_UNLIKELY( !fd_txn_parse( txn->payload, msg_sz, TXN( txn ), NULL ) ) ) {
124 0 : return NULL;
125 0 : }
126 :
127 5230 : txn->payload_sz = msg_sz;
128 :
129 5230 : return txn;
130 5230 : }
131 :
132 : ulong
133 : fd_solfuzz_pb_txn_serialize( uchar * txn_raw_begin,
134 5543 : fd_exec_test_sanitized_transaction_t const * tx ) {
135 5543 : 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 5543 : uchar signature_cnt = fd_uchar_max( 1, (uchar) tx->signatures_count );
143 5543 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &signature_cnt, sizeof(uchar) );
144 18320 : for( uchar i = 0; i < signature_cnt; ++i ) {
145 12777 : fd_signature_t sig = {0};
146 12777 : if( tx->signatures && tx->signatures[i] ) sig = FD_LOAD( fd_signature_t, tx->signatures[i]->bytes );
147 12777 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &sig, FD_TXN_SIGNATURE_SZ );
148 12777 : }
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 5543 : uchar num_required_signatures = fd_uchar_max( 1, fd_uchar_min( 127, (uchar) tx->message.header.num_required_signatures ) );
157 5543 : if( !tx->message.is_legacy ) {
158 5029 : uchar header_b0 = (uchar) 0x80UL;
159 5029 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &header_b0, sizeof(uchar) );
160 5029 : }
161 :
162 : /* Header (3 bytes) (https://solana.com/docs/core/transactions#message-header) */
163 11086 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &num_required_signatures, sizeof(uchar) );
164 5543 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &tx->message.header.num_readonly_signed_accounts, sizeof(uchar) );
165 5543 : 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 5543 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, num_acct_keys );
171 25974 : for( ushort i = 0; i < num_acct_keys; ++i ) {
172 20431 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, tx->message.account_keys[i]->bytes, sizeof(fd_pubkey_t) );
173 20431 : }
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 5543 : fd_hash_t msg_rbh = {0};
178 5543 : if( tx->message.recent_blockhash ) msg_rbh = FD_LOAD( fd_hash_t, tx->message.recent_blockhash->bytes );
179 5543 : 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 5543 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, instr_count );
185 13436 : for( ushort i = 0; i < instr_count; ++i ) {
186 : // Program ID index
187 7893 : uchar program_id_index = (uchar) tx->message.instructions[i].program_id_index;
188 7893 : 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 7893 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, acct_count );
193 30133 : for( ushort j = 0; j < acct_count; ++j ) {
194 22240 : uchar account_index = (uchar) tx->message.instructions[i].accounts[j];
195 22240 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &account_index, sizeof(uchar) );
196 22240 : }
197 :
198 : // Compact array of 8-bit data
199 7893 : pb_bytes_array_t * data = tx->message.instructions[i].data;
200 7893 : ushort data_len;
201 7893 : if( data ) {
202 6153 : data_len = (ushort) data->size;
203 6153 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data_len );
204 12306 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data->bytes, data_len );
205 12306 : } else {
206 1740 : data_len = 0;
207 1740 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, data_len );
208 1740 : }
209 7893 : }
210 :
211 : /* Address table lookups (N/A for legacy transactions) */
212 5543 : ushort addr_table_cnt = 0;
213 5543 : 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 5029 : addr_table_cnt = (ushort) tx->message.address_table_lookups_count;
217 5029 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, addr_table_cnt );
218 8310 : for( ushort i = 0; i < addr_table_cnt; ++i ) {
219 : // Account key
220 3281 : 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 3281 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, writable_count );
225 8204 : for( ushort j = 0; j < writable_count; ++j ) {
226 4923 : uchar writable_index = (uchar) tx->message.address_table_lookups[i].writable_indexes[j];
227 4923 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &writable_index, sizeof(uchar) );
228 4923 : }
229 :
230 : // Compact array of readonly indexes
231 3281 : ushort readonly_count = (ushort) tx->message.address_table_lookups[i].readonly_indexes_count;
232 3281 : FD_CHECKED_ADD_CU16_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, readonly_count );
233 6564 : for( ushort j = 0; j < readonly_count; ++j ) {
234 3283 : uchar readonly_index = (uchar) tx->message.address_table_lookups[i].readonly_indexes[j];
235 3283 : FD_CHECKED_ADD_TO_TXN_DATA( txn_raw_begin, &txn_raw_cur_ptr, &readonly_index, sizeof(uchar) );
236 3283 : }
237 3281 : }
238 5029 : }
239 :
240 5543 : return (ulong)(txn_raw_cur_ptr - txn_raw_begin);
241 5543 : }
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 5539 : fd_txn_out_t * txn_out ) {
249 :
250 5539 : txn_out->err.is_committable = 1;
251 :
252 5539 : runtime->log.enable_vm_tracing = runner->enable_vm_tracing;
253 5539 : uchar * tracing_mem = NULL;
254 5539 : 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 5539 : runtime->accdb = runner->accdb;
259 5539 : runtime->progcache = runner->progcache;
260 5539 : runtime->status_cache = NULL;
261 5539 : runtime->log.tracing_mem = tracing_mem;
262 5539 : runtime->log.dumping_mem = NULL;
263 5539 : runtime->log.capture_ctx = NULL;
264 5539 : runtime->log.dump_proto_ctx = NULL;
265 5539 : runtime->log.txn_dump_ctx = NULL;
266 5539 : runtime->fuzz.enabled = 1;
267 :
268 5539 : fd_runtime_prepare_and_execute_txn( runtime, runner->bank, txn_in, txn_out );
269 5539 : *exec_res = txn_out->err.txn_err;
270 5539 : }
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 5230 : ulong output_bufsz ) {
278 5230 : fd_exec_test_txn_context_t const * input = fd_type_pun_const( input_ );
279 5230 : fd_exec_test_txn_result_t ** output = fd_type_pun( output_ );
280 :
281 5230 : FD_SPAD_FRAME_BEGIN( runner->spad ) {
282 :
283 : /* Setup the transaction context */
284 5230 : fd_txn_p_t * txn = fd_solfuzz_pb_txn_ctx_create( runner, input );
285 5230 : 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 5230 : int exec_res = 0;
292 5230 : fd_runtime_t * runtime = runner->runtime;
293 5230 : fd_txn_in_t * txn_in = fd_spad_alloc( runner->spad, alignof(fd_txn_in_t), sizeof(fd_txn_in_t) );
294 5230 : fd_txn_out_t * txn_out = fd_spad_alloc( runner->spad, alignof(fd_txn_out_t), sizeof(fd_txn_out_t) );
295 5230 : fd_log_collector_t * log = fd_spad_alloc( runner->spad, alignof(fd_log_collector_t), sizeof(fd_log_collector_t) );
296 5230 : runtime->log.log_collector = log;
297 5230 : runtime->acc_pool = runner->acc_pool;
298 5230 : txn_in->txn = txn;
299 5230 : txn_in->bundle.is_bundle = 0;
300 5230 : 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 5230 : fd_exec_test_txn_result_t * txn_result = NULL;
304 5230 : ulong result_sz = create_txn_result_protobuf_from_txn(
305 5230 : &txn_result,
306 5230 : output_buf,
307 5230 : output_bufsz,
308 5230 : txn_in,
309 5230 : txn_out,
310 5230 : runner->bank,
311 5230 : exec_res
312 5230 : );
313 :
314 5230 : txn_out->err.is_committable = 0;
315 5230 : fd_runtime_cancel_txn( runner->runtime, txn_out );
316 5230 : fd_solfuzz_txn_ctx_destroy( runner );
317 :
318 5230 : *output = txn_result;
319 5230 : return result_sz;
320 5230 : } FD_SPAD_FRAME_END;
321 0 : }
|