Line data Source code
1 : #include "fd_bundle_harness.h"
2 : #include "fd_solfuzz_private.h"
3 : #include "fd_txn_harness.h"
4 : #include "fd_dump_pb.h"
5 : #include "generated/bundle.pb.h"
6 : #include "../fd_cost_tracker.h"
7 : #include "../fd_slot_params.h"
8 : #include "../fd_runtime.h"
9 : #include "../sysvar/fd_sysvar_cache.h"
10 : #include "../sysvar/fd_sysvar_epoch_schedule.h"
11 : #include "../../progcache/fd_progcache_admin.h"
12 : #include "../../log_collector/fd_log_collector.h" /* IWYU pragma: keep */
13 : #include "../../stakes/fd_stakes.h"
14 :
15 : static void
16 0 : fd_solfuzz_bundle_ctx_destroy( fd_solfuzz_runner_t * runner ) {
17 0 : if( runner->bank->new_votes_fork_id!=USHORT_MAX ) {
18 0 : fd_new_votes_evict_fork( fd_bank_new_votes( runner->bank ), runner->bank->new_votes_fork_id );
19 0 : runner->bank->new_votes_fork_id = USHORT_MAX;
20 0 : }
21 0 : fd_banks_stake_delegations_evict_bank_fork( runner->banks, runner->bank );
22 :
23 0 : fd_progcache_reset( runner->progcache->join );
24 :
25 : /* Purge the fork attached in ctx_create so the accdb fork pool slot
26 : is released back for reuse. Without this, repeated harness
27 : invocations (e.g. under a fuzzer) exhaust max_live_slots. */
28 0 : fd_accdb_purge( runner->accdb, runner->bank->accdb_fork_id );
29 0 : int charge_busy = 0;
30 0 : fd_accdb_background( runner->accdb, &charge_busy );
31 :
32 : /* Keep the runner reusable across many bundle inputs. */
33 0 : fd_alloc_compact( runner->progcache->join->alloc );
34 0 : }
35 :
36 : static fd_txn_p_t *
37 : fd_solfuzz_pb_bundle_ctx_create( fd_solfuzz_runner_t * runner,
38 : fd_exec_test_bundle_context_t const * test_ctx,
39 0 : ulong * out_txn_cnt ) {
40 0 : ulong txn_cnt = (ulong)test_ctx->txns_count;
41 0 : FD_TEST( txn_cnt<=FD_PACK_MAX_TXN_PER_BUNDLE );
42 :
43 0 : fd_accdb_t * accdb = runner->accdb;
44 :
45 0 : ulong slot = fd_solfuzz_pb_get_slot( test_ctx->account_shared_data, test_ctx->account_shared_data_count );
46 :
47 : /* Initialize bank from input txn bank */
48 0 : fd_banks_clear_bank( runner->banks, runner->bank, 2048UL );
49 :
50 0 : runner->bank->f.slot = slot;
51 0 : runner->bank->bank_seq = runner->bank->idx;
52 :
53 0 : runner->bank->progcache_fork_id = fd_progcache_attach_child( runner->progcache->join, fd_progcache_fork_id_initial() );
54 0 : runner->bank->accdb_fork_id = fd_accdb_attach_child( accdb, runner->root_fork_id );
55 0 : runner->bank->parent_accdb_fork_id = runner->bank->accdb_fork_id;
56 :
57 0 : FD_TEST( test_ctx->has_bank );
58 0 : fd_exec_test_txn_bank_t const * txn_bank = &test_ctx->bank;
59 :
60 0 : fd_stake_delegations_t * stake_delegations = fd_banks_stake_delegations_root_query( runner->banks );
61 0 : runner->bank->stake_delegations_fork_id = fd_stake_delegations_new_fork( stake_delegations );
62 0 : runner->bank->new_votes_fork_id = fd_new_votes_new_fork( fd_bank_new_votes( runner->bank ) );
63 :
64 0 : fd_solfuzz_pb_restore_blockhash_queue( runner->bank, txn_bank->blockhash_queue, txn_bank->blockhash_queue_count );
65 0 : runner->bank->f.rbh_lamports_per_sig = txn_bank->rbh_lamports_per_signature;
66 :
67 0 : FD_TEST( txn_bank->has_fee_rate_governor );
68 0 : fd_solfuzz_pb_restore_fee_rate_governor( runner->bank, &txn_bank->fee_rate_governor );
69 :
70 0 : runner->bank->f.parent_slot = slot-1UL;
71 0 : runner->bank->f.total_epoch_stake = txn_bank->total_epoch_stake;
72 :
73 0 : FD_TEST( txn_bank->has_features );
74 0 : FD_TEST( fd_solfuzz_pb_restore_features( &runner->bank->f.features, &txn_bank->features ) );
75 :
76 0 : for( ulong i=0UL; i<test_ctx->account_shared_data_count; i++ ) {
77 0 : fd_solfuzz_pb_load_account( runner->runtime, accdb, runner->bank->accdb_fork_id, &test_ctx->account_shared_data[i], i );
78 0 : }
79 :
80 0 : runner->bank->f.ticks_per_slot = 64;
81 0 : runner->bank->f.slot_params = FD_SLOT_PARAMS_400MS;
82 0 : runner->bank->f.slot_params.slots_per_year = SECONDS_PER_YEAR * (1000000000.0 / (double)6250000) / (double)(runner->bank->f.ticks_per_slot);
83 0 : runner->bank->f.slot_params_default = runner->bank->f.slot_params;
84 :
85 0 : fd_sysvar_cache_restore_fuzz( runner->bank, runner->accdb );
86 :
87 0 : FD_TEST( fd_sysvar_cache_epoch_schedule_read( &runner->bank->f.sysvar_cache, &runner->bank->f.epoch_schedule ) );
88 0 : runner->bank->f.epoch = fd_slot_to_epoch( &runner->bank->f.epoch_schedule, slot, NULL );
89 :
90 0 : FD_TEST( fd_sysvar_cache_rent_read( &runner->bank->f.sysvar_cache, &runner->bank->f.rent ) );
91 :
92 : /* Initialize cost tracker */
93 0 : fd_cost_tracker_t * cost_tracker = fd_bank_cost_tracker_modify( runner->bank );
94 0 : fd_cost_tracker_init( cost_tracker, &runner->bank->f.features, &runner->bank->f.slot_params, slot );
95 :
96 0 : fd_txn_p_t * txns = fd_spad_alloc( runner->spad, alignof(fd_txn_p_t), txn_cnt*sizeof(fd_txn_p_t) );
97 0 : fd_memset( txns, 0, txn_cnt*sizeof(fd_txn_p_t) );
98 :
99 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
100 0 : ulong msg_sz = fd_solfuzz_pb_txn_serialize( txns[i].payload, &test_ctx->txns[i] );
101 0 : if( FD_UNLIKELY( msg_sz==ULONG_MAX ) ) return NULL;
102 0 : if( FD_UNLIKELY( !fd_txn_parse( txns[i].payload, msg_sz, TXN( &txns[i] ), NULL ) ) ) return NULL;
103 0 : txns[i].payload_sz = msg_sz;
104 0 : }
105 :
106 0 : *out_txn_cnt = txn_cnt;
107 0 : return txns;
108 0 : }
109 :
110 : static void
111 : fd_solfuzz_bundle_mark_uncommittable( fd_txn_out_t * txn_outs,
112 0 : ulong txn_cnt ) {
113 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
114 0 : txn_outs[i].err.is_committable = 0;
115 0 : }
116 0 : }
117 :
118 : /* Record the latest stake and vote account updates only. */
119 :
120 : static fd_exec_test_stake_delta_t *
121 : fd_solfuzz_bundle_stake_delta_upsert( fd_exec_test_bundle_effects_t * effects,
122 0 : fd_pubkey_t const * key ) {
123 0 : for( ulong i=0UL; i<effects->stake_deltas_count; i++ ) {
124 0 : if( fd_memeq( effects->stake_deltas[i].address, key, sizeof(fd_pubkey_t) ) ) return &effects->stake_deltas[i];
125 0 : }
126 0 : fd_exec_test_stake_delta_t * stake_delta = &effects->stake_deltas[effects->stake_deltas_count++];
127 0 : fd_memcpy( stake_delta->address, key, sizeof(fd_pubkey_t) );
128 0 : return stake_delta;
129 0 : }
130 :
131 : static fd_exec_test_vote_update_t *
132 : fd_solfuzz_bundle_vote_update_upsert( fd_exec_test_bundle_effects_t * effects,
133 0 : fd_pubkey_t const * key ) {
134 0 : for( ulong i=0UL; i<effects->vote_updates_count; i++ ) {
135 0 : if( fd_memeq( effects->vote_updates[i].address, key, sizeof(fd_pubkey_t) ) ) return &effects->vote_updates[i];
136 0 : }
137 0 : fd_exec_test_vote_update_t * vote_update = &effects->vote_updates[effects->vote_updates_count++];
138 0 : fd_memcpy( vote_update->address, key, sizeof(fd_pubkey_t) );
139 0 : return vote_update;
140 0 : }
141 :
142 : static int
143 : fd_solfuzz_bundle_execute( fd_solfuzz_runner_t * runner,
144 : fd_exec_test_bundle_context_t const * input,
145 : int is_bundle,
146 : void * output_buf,
147 : ulong output_bufsz,
148 : fd_exec_test_bundle_effects_t ** effects_out,
149 0 : ulong * output_used ) {
150 0 : ulong txn_cnt = 0UL;
151 0 : fd_txn_p_t * txns = fd_solfuzz_pb_bundle_ctx_create( runner, input, &txn_cnt );
152 0 : if( FD_UNLIKELY( !txns ) ) {
153 0 : fd_solfuzz_bundle_ctx_destroy( runner );
154 0 : return 0;
155 0 : }
156 :
157 0 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
158 0 : ulong output_end = (ulong)output_buf + output_bufsz;
159 :
160 0 : fd_exec_test_bundle_effects_t * effects =
161 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_bundle_effects_t),
162 0 : sizeof(fd_exec_test_bundle_effects_t) );
163 0 : if( FD_UNLIKELY( _l>output_end ) ) abort();
164 0 : fd_memset( effects, 0, sizeof(fd_exec_test_bundle_effects_t) );
165 :
166 0 : effects->txn_results_count = (pb_size_t)txn_cnt;
167 0 : effects->txn_results = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_txn_result_t),
168 0 : txn_cnt*sizeof(fd_exec_test_txn_result_t) );
169 0 : if( FD_UNLIKELY( _l>output_end ) ) abort();
170 0 : fd_memset( effects->txn_results, 0, txn_cnt*sizeof(fd_exec_test_txn_result_t) );
171 :
172 0 : ulong update_max = txn_cnt*MAX_TX_ACCOUNT_LOCKS;
173 0 : effects->stake_deltas = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_stake_delta_t),
174 0 : update_max*sizeof(fd_exec_test_stake_delta_t) );
175 0 : if( FD_UNLIKELY( _l>output_end ) ) abort();
176 0 : fd_memset( effects->stake_deltas, 0, update_max*sizeof(fd_exec_test_stake_delta_t) );
177 0 : effects->stake_deltas_count = 0UL;
178 :
179 0 : effects->vote_updates = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_vote_update_t),
180 0 : update_max*sizeof(fd_exec_test_vote_update_t) );
181 0 : if( FD_UNLIKELY( _l>output_end ) ) abort();
182 0 : fd_memset( effects->vote_updates, 0, update_max*sizeof(fd_exec_test_vote_update_t) );
183 0 : effects->vote_updates_count = 0UL;
184 :
185 0 : effects->new_votes = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_new_vote_t),
186 0 : update_max*sizeof(fd_exec_test_new_vote_t) );
187 0 : if( FD_UNLIKELY( _l>output_end ) ) abort();
188 0 : fd_memset( effects->new_votes, 0, update_max*sizeof(fd_exec_test_new_vote_t) );
189 0 : effects->new_votes_count = 0UL;
190 :
191 0 : fd_runtime_t * runtime = runner->runtime;
192 0 : fd_txn_out_t * txn_outs = fd_spad_alloc( runner->spad, alignof(fd_txn_out_t), txn_cnt*sizeof(fd_txn_out_t) );
193 0 : fd_txn_in_t * txn_ins = fd_spad_alloc( runner->spad, alignof(fd_txn_in_t), txn_cnt*sizeof(fd_txn_in_t) );
194 0 : fd_log_collector_t * logs = fd_spad_alloc( runner->spad, alignof(fd_log_collector_t), txn_cnt*sizeof(fd_log_collector_t) );
195 0 : ulong ran_cnt = 0UL;
196 0 : int saw_exec_err = 0;
197 :
198 : /* Set up every txn_in up-front. For bundles, acquire all of the
199 : bundle's accounts in a single acquire_a/acquire_b pair before any
200 : txn executes; each txn then binds to this shared pool, which is
201 : released once after the bundle is committed or cancelled. */
202 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
203 0 : txn_ins[i] = (fd_txn_in_t){0};
204 0 : txn_ins[i].txn = &txns[i];
205 0 : txn_ins[i].bundle.is_bundle = is_bundle;
206 0 : txn_ins[i].bundle.prev_txn_cnt = is_bundle ? i : 0UL;
207 0 : for( ulong j=0UL; is_bundle && j<i; j++ ) txn_ins[i].bundle.prev_txn_outs[j] = &txn_outs[j];
208 0 : }
209 : /* Mirror fd_execle_tile: a failed bundle prepare acquires nothing, so
210 : every txn fails and fd_runtime_fini_bundle must be skipped. An
211 : empty bundle likewise acquires nothing and must skip fini. */
212 0 : int bundle_prep_ok = 1;
213 0 : if( is_bundle ) {
214 0 : runtime->accdb = runner->accdb;
215 0 : if( FD_LIKELY( txn_cnt ) ) {
216 0 : int prepare_err = fd_runtime_prepare_bundle_accounts( runtime, runner->bank, txn_ins, txn_outs, txn_cnt );
217 0 : if( FD_UNLIKELY( prepare_err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
218 0 : bundle_prep_ok = 0;
219 0 : saw_exec_err = 1;
220 0 : }
221 0 : }
222 0 : }
223 :
224 0 : for( ulong i=0UL; bundle_prep_ok && i<txn_cnt; i++ ) {
225 0 : fd_txn_in_t * txn_in = &txn_ins[i];
226 :
227 0 : int exec_res = 0;
228 0 : runtime->log.log_collector = &logs[i];
229 0 : fd_solfuzz_txn_ctx_exec( runner, runtime, txn_in, &exec_res, &txn_outs[i], 1 );
230 0 : ran_cnt = i+1UL;
231 :
232 0 : if( exec_res!=FD_RUNTIME_EXECUTE_SUCCESS ) {
233 0 : saw_exec_err = 1;
234 0 : if( is_bundle ) {
235 0 : fd_solfuzz_bundle_mark_uncommittable( txn_outs, ran_cnt );
236 0 : } else {
237 0 : txn_outs[i].err.is_committable = 0;
238 0 : fd_runtime_cancel_txn( runtime, NULL, NULL, &txn_outs[i], 0 );
239 0 : }
240 0 : break;
241 0 : }
242 :
243 0 : fd_exec_test_txn_result_t * txn_result = NULL;
244 0 : ulong txn_result_sz = create_txn_result_protobuf_from_txn(
245 0 : &txn_result,
246 0 : (void *)_l,
247 0 : output_end - _l,
248 0 : txn_in,
249 0 : &txn_outs[i],
250 0 : exec_res );
251 0 : FD_TEST( txn_result_sz );
252 0 : FD_TEST( txn_result );
253 0 : effects->txn_results[i] = *txn_result;
254 0 : _l += txn_result_sz;
255 :
256 0 : for( ulong j=0UL; j<txn_outs[i].accounts.cnt; j++ ) {
257 0 : if( txn_outs[i].accounts.stake_update[j] ) {
258 0 : fd_exec_test_stake_delta_t * stake_delta = fd_solfuzz_bundle_stake_delta_upsert( effects, &txn_outs[i].accounts.keys[j] );
259 0 : stake_delta->new_stake = 0UL;
260 :
261 0 : fd_stake_state_t const * stake_state = fd_stakes_get_state( txn_outs[i].accounts.account[j] );
262 0 : if( stake_state && stake_state->stake_type==FD_STAKE_STATE_STAKE ) {
263 0 : stake_delta->new_stake = stake_state->stake.stake.delegation.stake;
264 0 : }
265 0 : }
266 :
267 0 : if( txn_outs[i].accounts.vote_update[j] ) {
268 0 : fd_vote_block_timestamp_t last_vote;
269 0 : if( !fd_vote_account_last_timestamp( txn_outs[i].accounts.account[j]->data,
270 0 : txn_outs[i].accounts.account[j]->data_len,
271 0 : &last_vote ) ) {
272 0 : fd_exec_test_vote_update_t * vote_update = fd_solfuzz_bundle_vote_update_upsert( effects, &txn_outs[i].accounts.keys[j] );
273 0 : vote_update->last_vote_slot = last_vote.slot;
274 0 : vote_update->last_vote_timestamp = (ulong)last_vote.timestamp;
275 0 : }
276 0 : }
277 0 : }
278 :
279 0 : if( !is_bundle ) fd_runtime_commit_txn( runtime, runner->bank, NULL, &txn_outs[i], 0 );
280 0 : }
281 :
282 0 : if( is_bundle && !saw_exec_err ) {
283 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
284 0 : fd_runtime_commit_txn( runtime, runner->bank, NULL, &txn_outs[i], 0 );
285 0 : }
286 0 : }
287 :
288 : /* Release the bundle's shared account pool exactly once now that all of
289 : its txns have been committed or cancelled. */
290 0 : if( is_bundle && bundle_prep_ok && txn_cnt ) fd_runtime_fini_bundle( runtime );
291 :
292 0 : effects->has_error = saw_exec_err;
293 0 : if( saw_exec_err ) {
294 0 : effects->txn_results_count = 0UL;
295 0 : effects->txn_results = NULL;
296 0 : effects->stake_deltas_count = 0UL;
297 0 : effects->stake_deltas = NULL;
298 0 : effects->vote_updates_count = 0UL;
299 0 : effects->vote_updates = NULL;
300 0 : effects->new_votes_count = 0UL;
301 0 : effects->new_votes = NULL;
302 0 : } else {
303 0 : ushort fork_idx = runner->bank->new_votes_fork_id;
304 0 : uchar iter_mem[ FD_NEW_VOTES_ITER_FOOTPRINT ] __attribute__((aligned(FD_NEW_VOTES_ITER_ALIGN)));
305 0 : fd_new_votes_iter_t * iter = fd_new_votes_iter_init( fd_bank_new_votes( runner->bank ), &fork_idx, 1UL, iter_mem );
306 0 : for( ; !fd_new_votes_iter_done( iter ); fd_new_votes_iter_next( iter ) ) {
307 0 : FD_TEST( effects->new_votes_count<update_max );
308 :
309 0 : fd_exec_test_new_vote_t * new_vote = &effects->new_votes[effects->new_votes_count++];
310 0 : int is_tombstone;
311 0 : fd_pubkey_t const * pubkey = fd_new_votes_iter_ele( iter, &is_tombstone );
312 0 : new_vote->is_tombstone = !!is_tombstone;
313 0 : fd_memcpy( new_vote->address, pubkey, sizeof(fd_pubkey_t) );
314 0 : }
315 0 : fd_new_votes_iter_fini( iter );
316 0 : }
317 :
318 0 : *effects_out = effects;
319 0 : *output_used = FD_SCRATCH_ALLOC_FINI( l, 1UL ) - (ulong)output_buf;
320 :
321 0 : fd_solfuzz_bundle_ctx_destroy( runner );
322 0 : return 1;
323 0 : }
324 :
325 : static void
326 : fd_solfuzz_bundle_assert_same_success( fd_solfuzz_runner_t * runner,
327 : fd_exec_test_bundle_effects_t * regular,
328 0 : fd_exec_test_bundle_effects_t * bundle ) {
329 0 : ulong buf_sz = 100000000UL;
330 0 : uchar * regular_buf = fd_spad_alloc( runner->spad, 1UL, buf_sz );
331 0 : uchar * bundle_buf = fd_spad_alloc( runner->spad, 1UL, buf_sz );
332 :
333 0 : ulong regular_sz = buf_sz;
334 0 : ulong bundle_sz = buf_sz;
335 0 : FD_TEST( sol_compat_encode( regular_buf, ®ular_sz, regular, &fd_exec_test_bundle_effects_t_msg ) );
336 0 : FD_TEST( sol_compat_encode( bundle_buf, &bundle_sz, bundle, &fd_exec_test_bundle_effects_t_msg ) );
337 :
338 0 : if( FD_UNLIKELY( regular_sz!=bundle_sz || !fd_memeq( regular_buf, bundle_buf, regular_sz ) ) ) {
339 0 : FD_LOG_ERR(( "bundle transaction effects mismatch" ));
340 0 : }
341 0 : }
342 :
343 : ulong
344 : fd_solfuzz_pb_bundle_run( fd_solfuzz_runner_t * runner,
345 : void const * input_,
346 : void ** output_,
347 : void * output_buf,
348 0 : ulong output_bufsz ) {
349 0 : fd_exec_test_bundle_context_t const * input = fd_type_pun_const( input_ );
350 0 : fd_exec_test_bundle_effects_t ** output = fd_type_pun( output_ );
351 :
352 0 : FD_SPAD_FRAME_BEGIN( runner->spad ) {
353 0 : void * regular_buf = fd_spad_alloc( runner->spad, 1UL, output_bufsz );
354 :
355 0 : fd_exec_test_bundle_effects_t * regular_effects = NULL;
356 0 : fd_exec_test_bundle_effects_t * bundle_effects = NULL;
357 0 : ulong regular_sz = 0UL;
358 0 : ulong bundle_sz = 0UL;
359 :
360 0 : int regular_ok = fd_solfuzz_bundle_execute( runner, input, 0, regular_buf, output_bufsz, ®ular_effects, ®ular_sz );
361 0 : int bundle_ok = fd_solfuzz_bundle_execute( runner, input, 1, output_buf, output_bufsz, &bundle_effects, &bundle_sz );
362 :
363 0 : if( FD_UNLIKELY( regular_ok!=bundle_ok ) ) {
364 0 : FD_LOG_ERR(( "bundle harness setup parity mismatch: regular_ok=%d bundle_ok=%d", regular_ok, bundle_ok ));
365 0 : }
366 0 : if( FD_UNLIKELY( !regular_ok ) ) {
367 0 : return 0UL;
368 0 : }
369 :
370 0 : (void)regular_sz;
371 0 : if( FD_UNLIKELY( regular_effects->has_error!=bundle_effects->has_error ) ) {
372 0 : FD_LOG_ERR(( "bundle success parity mismatch: regular_has_error=%d bundle_has_error=%d",
373 0 : regular_effects->has_error, bundle_effects->has_error ));
374 0 : }
375 :
376 0 : if( !regular_effects->has_error ) {
377 0 : fd_solfuzz_bundle_assert_same_success( runner, regular_effects, bundle_effects );
378 0 : }
379 :
380 0 : *output = bundle_effects;
381 0 : return bundle_sz;
382 0 : } FD_SPAD_FRAME_END;
383 0 : }
|