Line data Source code
1 : #include "fd_solfuzz_private.h"
2 : #include "../fd_cost_tracker.h"
3 : #include "../fd_slot_params.h"
4 : #include "fd_txn_harness.h"
5 : #include "../fd_runtime.h"
6 : #include "../fd_runtime_helpers.h"
7 : #include "../fd_system_ids.h"
8 : #include "../fd_runtime_stack.h"
9 : #include "../../stakes/fd_stake_types.h"
10 : #include "../sysvar/fd_sysvar_epoch_schedule.h"
11 : #include "../../progcache/fd_progcache_admin.h"
12 : #include "../../log_collector/fd_log_collector.h"
13 : #include "../../rewards/fd_rewards.h"
14 : #include "../../rewards/fd_stake_rewards.h"
15 : #include "generated/block.pb.h"
16 : #include "../../capture/fd_capture_ctx.h"
17 : #include "../../capture/fd_solcap_writer.h"
18 :
19 : /* Templatized leader schedule sort helper functions */
20 : typedef struct {
21 : fd_pubkey_t pk;
22 : ulong sched_pos; /* track original position in sched[] */
23 : } pk_with_pos_t;
24 :
25 : #define SORT_NAME sort_pkpos
26 0 : #define SORT_KEY_T pk_with_pos_t
27 0 : #define SORT_BEFORE(a,b) (memcmp(&(a).pk, &(b).pk, sizeof(fd_pubkey_t))<0)
28 : #include "../../../util/tmpl/fd_sort.c" /* generates templatized sort_pkpos_*() APIs */
29 :
30 : /* Fixed leader schedule hash seed (consistent with solfuzz-agave) */
31 0 : #define LEADER_SCHEDULE_HASH_SEED 0xDEADFACEUL
32 :
33 : static void
34 : fd_solfuzz_block_update_prev_epoch_stakes( fd_bank_t const * bank,
35 : fd_top_votes_t * top_votes,
36 : fd_vote_stakes_t * vote_stakes,
37 : fd_exec_test_prev_vote_account_t * vote_accounts,
38 : pb_size_t vote_accounts_cnt,
39 0 : uchar is_t_1 ) {
40 0 : if( FD_UNLIKELY( !vote_accounts ) ) return;
41 :
42 0 : for( uint i=0U; i<vote_accounts_cnt; i++ ) {
43 0 : fd_pubkey_t vote_pubkey = FD_LOAD( fd_pubkey_t, &vote_accounts[i].address );
44 0 : fd_pubkey_t node_pubkey = FD_LOAD( fd_pubkey_t, &vote_accounts[i].node_pubkey );
45 0 : ulong stake = vote_accounts[i].stake;
46 :
47 0 : ushort commission;
48 0 : if( FD_FEATURE_ACTIVE_BANK( bank, commission_rate_in_basis_points ) && vote_accounts[i].version == FD_EXEC_TEST_VOTE_ACCOUNT_VERSION_V4 ) {
49 0 : commission = (ushort)vote_accounts[i].commission_bps;
50 0 : } else if( vote_accounts[i].version == FD_EXEC_TEST_VOTE_ACCOUNT_VERSION_V4 ) {
51 0 : commission = (ushort)( ( vote_accounts[i].commission_bps / 100U ) * 100U );
52 0 : } else {
53 0 : commission = (ushort)( (uchar)( vote_accounts[i].commission_bps / 100U ) * 100U );
54 0 : }
55 :
56 0 : if( is_t_1 ) {
57 0 : fd_vote_stakes_root_insert_key( vote_stakes, &vote_pubkey, &node_pubkey, stake, commission, 0 );
58 0 : } else {
59 0 : fd_vote_stakes_root_update_meta( vote_stakes, &vote_pubkey, &node_pubkey, stake, commission, 0 );
60 0 : }
61 0 : fd_top_votes_insert( top_votes, &vote_pubkey, &node_pubkey, stake, commission );
62 0 : }
63 0 : }
64 :
65 : /* Stores an acc in the stake delegations cache for the given vote
66 : account. Deserializes and uses the present account state to derive
67 : delegation information. */
68 : static void
69 : fd_solfuzz_block_register_stake_delegation( fd_accdb_t * accdb,
70 : fd_accdb_fork_id_t fork_id,
71 : fd_stake_delegations_t * stake_delegations,
72 0 : fd_pubkey_t * pubkey ) {
73 0 : fd_acc_t acc = fd_accdb_read_one( accdb, fork_id, pubkey->key );
74 0 : if( FD_UNLIKELY( !acc.lamports ) ) {
75 0 : fd_accdb_unread_one( accdb, &acc );
76 0 : return;
77 0 : }
78 :
79 0 : fd_stake_state_t const * stake_state = NULL;
80 0 : if( memcmp( acc.owner, fd_solana_stake_program_id.key, 32UL )!=0 ||
81 0 : !( stake_state = fd_stake_state_view( acc.data, acc.data_len ) ) ||
82 0 : stake_state->stake_type!=FD_STAKE_STATE_STAKE ||
83 0 : stake_state->stake.stake.delegation.stake==0UL ) {
84 0 : fd_accdb_unread_one( accdb, &acc );
85 0 : return;
86 0 : }
87 :
88 0 : fd_stake_delegations_root_update(
89 0 : stake_delegations,
90 0 : pubkey,
91 0 : &stake_state->stake.stake.delegation.voter_pubkey,
92 0 : stake_state->stake.stake.delegation.stake,
93 0 : stake_state->stake.stake.delegation.activation_epoch,
94 0 : stake_state->stake.stake.delegation.deactivation_epoch,
95 0 : stake_state->stake.stake.credits_observed,
96 0 : acc.lamports,
97 0 : (uint)acc.data_len,
98 0 : FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_025 );
99 0 : fd_accdb_unread_one( accdb, &acc );
100 0 : }
101 :
102 : static void
103 0 : fd_solfuzz_pb_block_ctx_destroy( fd_solfuzz_runner_t * runner ) {
104 0 : fd_banks_stake_delegations_evict_bank_fork( runner->banks, runner->bank );
105 :
106 0 : runner->bank->stake_rewards_fork_id = UCHAR_MAX;
107 0 : fd_stake_rewards_clear( fd_bank_stake_rewards_modify( runner->bank ) );
108 :
109 0 : fd_progcache_reset( runner->progcache->join );
110 :
111 : /* Purge the fork attached in ctx_create so the accdb fork pool slot
112 : is released back for reuse. Without this, repeated harness
113 : invocations (e.g. under a fuzzer) exhaust max_live_slots. */
114 0 : fd_accdb_purge( runner->accdb, runner->bank->accdb_fork_id );
115 0 : int charge_busy = 0;
116 0 : fd_accdb_background( runner->accdb, &charge_busy );
117 :
118 : /* Compact the progcache allocator so empty superblocks are returned
119 : to the workspace. Required for the leak check to pass. */
120 0 : fd_alloc_compact( runner->progcache->join->alloc );
121 0 : }
122 :
123 : /* Sets up block execution context from an input test case to execute
124 : against the runtime. Returns block_info on success and NULL on
125 : failure. */
126 : static fd_txn_p_t *
127 : fd_solfuzz_pb_block_ctx_create( fd_solfuzz_runner_t * runner,
128 : fd_exec_test_block_context_t const * test_ctx,
129 : ulong * out_txn_cnt,
130 0 : fd_hash_t * poh ) {
131 0 : fd_accdb_t * accdb = runner->accdb;
132 0 : fd_bank_t * bank = runner->bank;
133 0 : fd_banks_t * banks = runner->banks;
134 :
135 0 : fd_runtime_stack_t * runtime_stack = runner->runtime_stack;
136 :
137 : /* Must match fd_banks_footprint max_vote_accounts (2048) to avoid buffer overrun
138 : when fd_vote_stakes_new reinitializes and epoch boundary inserts from vote_ele_map */
139 0 : fd_banks_clear_bank( banks, bank, 2048UL );
140 :
141 0 : runner->bank->progcache_fork_id = fd_progcache_attach_child( runner->progcache->join, fd_progcache_fork_id_initial() );
142 :
143 : /* Attach a fork off the runner's root for context loading */
144 0 : fd_accdb_fork_id_t fork_id = fd_accdb_attach_child( accdb, runner->root_fork_id );
145 0 : bank->accdb_fork_id = fork_id;
146 0 : bank->parent_accdb_fork_id = bank->accdb_fork_id;
147 :
148 : /* Initialize bank from input block bank */
149 0 : FD_TEST( test_ctx->has_bank );
150 0 : fd_exec_test_block_bank_t const * block_bank = &test_ctx->bank;
151 :
152 : /* Slot */
153 0 : ulong slot = block_bank->slot;
154 0 : bank->f.slot = slot;
155 :
156 : /* Register a non-root progcache transaction at the bank's xid so the
157 : BPF loader can insert program cache entries during execution. */
158 0 : bank->progcache_fork_id = fd_progcache_attach_child( runner->progcache->join, bank->progcache_fork_id );
159 :
160 : /* Blockhash queue */
161 0 : fd_solfuzz_pb_restore_blockhash_queue( bank, block_bank->blockhash_queue, block_bank->blockhash_queue_count );
162 :
163 : /* RBH lamports per signature. In the Agave harness this is set inside
164 : the fee rate governor itself. */
165 0 : runner->bank->f.rbh_lamports_per_sig = block_bank->rbh_lamports_per_signature;
166 :
167 : /* Fee rate governor */
168 0 : FD_TEST( block_bank->has_fee_rate_governor );
169 0 : fd_solfuzz_pb_restore_fee_rate_governor( bank, &block_bank->fee_rate_governor );
170 :
171 : /* Parent slot */
172 0 : ulong parent_slot = block_bank->parent_slot;
173 0 : bank->f.parent_slot = parent_slot;
174 :
175 : /* Capitalization */
176 0 : bank->f.capitalization = block_bank->capitalization;
177 :
178 : /* Inflation */
179 0 : FD_TEST( block_bank->has_inflation );
180 0 : fd_inflation_t inflation = {
181 0 : .initial = block_bank->inflation.initial,
182 0 : .terminal = block_bank->inflation.terminal,
183 0 : .taper = block_bank->inflation.taper,
184 0 : .foundation = block_bank->inflation.foundation,
185 0 : .foundation_term = block_bank->inflation.foundation_term,
186 0 : };
187 0 : bank->f.inflation = inflation;
188 :
189 : /* Block height */
190 0 : bank->f.block_height = block_bank->block_height;
191 :
192 : /* POH (set right before finalize since we don't fuzz POH calculation) */
193 0 : fd_memcpy( poh, block_bank->poh, sizeof(fd_hash_t) );
194 :
195 : /* Bank hash (parent bank hash because current bank hash gets computed
196 : after the block executes) */
197 0 : fd_hash_t * bank_hash = &bank->f.bank_hash;
198 0 : fd_memcpy( bank_hash, block_bank->parent_bank_hash, sizeof(fd_hash_t) );
199 :
200 : /* Previous bank hash (used as input to the bank hash computation).
201 : In production this is set by fd_banks_clone_from_parent. */
202 0 : bank->f.prev_bank_hash = *(fd_hash_t const *)block_bank->parent_bank_hash;
203 :
204 : /* Parent signature count */
205 0 : bank->f.parent_signature_cnt = block_bank->parent_signature_count;
206 :
207 : /* Feature set */
208 0 : FD_TEST( block_bank->has_features );
209 0 : fd_exec_test_feature_set_t const * feature_set = &block_bank->features;
210 0 : fd_features_t * features_bm = &bank->f.features;
211 0 : fd_solfuzz_pb_create_feature_accounts( accdb, fork_id, feature_set, test_ctx->acct_states, test_ctx->acct_states_count );
212 0 : FD_TEST( fd_solfuzz_pb_restore_features( features_bm, feature_set ) );
213 :
214 : /* Total epoch stake (derived from T-1 vote accounts) */
215 0 : ulong total_epoch_stake = 0UL;
216 0 : for( uint i=0U; i<block_bank->vote_accounts_t_1_count; i++ ) {
217 0 : total_epoch_stake += block_bank->vote_accounts_t_1[i].stake;
218 0 : }
219 0 : bank->f.total_epoch_stake = total_epoch_stake;
220 :
221 : /* Using default configuration of 64 ticks per slot
222 : https://github.com/anza-xyz/solana-sdk/blob/time-utils%40v3.0.0/time-utils/src/lib.rs#L18-L27 */
223 0 : uint128 ns_per_slot_128 = FD_LOAD(uint128, block_bank->ns_per_slot );
224 0 : FD_TEST( ns_per_slot_128<=(uint128)ULONG_MAX );
225 0 : ulong ns_per_slot = (ulong)ns_per_slot_128;
226 0 : bank->f.slot_params = FD_SLOT_PARAMS_400MS;
227 0 : bank->f.slot_params.ns_per_slot = ns_per_slot;
228 0 : bank->f.ticks_per_slot = 64UL;
229 0 : runner->bank->f.slot_params.slots_per_year = (double)SECONDS_PER_YEAR * 1e9 / (double)ns_per_slot;
230 0 : bank->f.slot_params.hashes_per_tick = (slot+1UL)*64UL;
231 0 : bank->f.slot_params_default = bank->f.slot_params;
232 :
233 : /* Load in accounts, populate stake delegations and vote accounts */
234 0 : fd_stake_delegations_t * stake_delegations = fd_banks_stake_delegations_root_query( banks );
235 0 : fd_stake_delegations_reset( stake_delegations );
236 :
237 0 : bank->stake_delegations_fork_id = fd_stake_delegations_new_fork( stake_delegations );
238 :
239 0 : fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
240 0 : bank->vote_stakes_fork_id = fd_vote_stakes_get_root_idx( vote_stakes );
241 :
242 0 : fd_top_votes_t * top_votes_t_1 = fd_bank_top_votes_t_1_modify( bank );
243 0 : fd_top_votes_init( top_votes_t_1 );
244 :
245 0 : fd_top_votes_t * top_votes_t_2 = fd_bank_top_votes_t_2_modify( bank );
246 0 : fd_top_votes_init( top_votes_t_2 );
247 :
248 : /* Cap number of vote accounts at FD_RUNTIME_EXPECTED_VOTE_ACCOUNTS */
249 0 : FD_TEST( block_bank->vote_accounts_t_1_count<=FD_RUNTIME_EXPECTED_VOTE_ACCOUNTS );
250 0 : FD_TEST( block_bank->vote_accounts_t_2_count<=FD_RUNTIME_EXPECTED_VOTE_ACCOUNTS );
251 :
252 0 : fd_solfuzz_block_update_prev_epoch_stakes( bank, top_votes_t_1, vote_stakes, block_bank->vote_accounts_t_1, block_bank->vote_accounts_t_1_count, 1 );
253 0 : fd_solfuzz_block_update_prev_epoch_stakes( bank, top_votes_t_2, vote_stakes, block_bank->vote_accounts_t_2, block_bank->vote_accounts_t_2_count, 0 );
254 :
255 0 : for( ushort i=0; i<test_ctx->acct_states_count; i++ ) {
256 0 : fd_solfuzz_pb_load_account( runner->runtime, accdb, fork_id, &test_ctx->acct_states[i], i );
257 :
258 : /* Update the stake delegations cache for epoch T */
259 0 : fd_pubkey_t pubkey;
260 0 : memcpy( &pubkey, test_ctx->acct_states[i].address, sizeof(fd_pubkey_t) );
261 0 : fd_solfuzz_block_register_stake_delegation( accdb, fork_id, stake_delegations, &pubkey );
262 0 : }
263 :
264 : /* Refresh top votes after loading accdb. */
265 0 : fd_top_votes_refresh( top_votes_t_2, accdb, fork_id );
266 :
267 : /* reduce_stake_warmup_cooldown is activated on all clusters, so the
268 : new warmup/cooldown rate (0.09) applies from epoch 0 onwards. */
269 0 : bank->f.warmup_cooldown_rate_epoch = 0UL;
270 :
271 : /* Restore sysvar cache */
272 0 : fd_sysvar_cache_restore_fuzz( bank, accdb );
273 :
274 : /* Epoch schedule */
275 0 : FD_TEST( fd_sysvar_cache_epoch_schedule_read( &bank->f.sysvar_cache, &bank->f.epoch_schedule ) );
276 :
277 : /* Current epoch gets updated in process_new_epoch, so use the epoch
278 : from the parent slot */
279 0 : bank->f.epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, parent_slot, NULL );
280 :
281 : /* Initialize total_effective/activating/deactivating_stake from the
282 : loaded stake delegations. These are read by fd_stakes_activate_epoch
283 : at epoch boundary instead of re-scanning all delegations. */
284 0 : fd_stake_history_t stake_history[1];
285 0 : if( FD_UNLIKELY( !fd_sysvar_cache_stake_history_view( &bank->f.sysvar_cache, stake_history ) ) ) {
286 0 : FD_LOG_ERR(( "StakeHistory sysvar missing or invalid" ));
287 0 : }
288 0 : fd_stake_delegations_refresh( stake_delegations, bank->f.epoch, stake_history, &bank->f.warmup_cooldown_rate_epoch, FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ), accdb, fork_id );
289 :
290 : /* Finalize root fork. Required before epoch boundary processing which
291 : may call fd_vote_stakes_advance_root. See fd_vote_stakes.h. */
292 :
293 0 : ulong chain_cnt = fd_vote_rewards_map_chain_cnt_est( runtime_stack->expected_vote_accounts );
294 0 : FD_TEST( fd_vote_rewards_map_join( fd_vote_rewards_map_new( runtime_stack->stakes.vote_map, chain_cnt, 999 ) ) );
295 :
296 : /* Use epoch_credits from the proto if available (captured at epoch
297 : boundary time), otherwise fall back to the vote account in accdb. */
298 0 : for( uint i=0U; i<block_bank->vote_accounts_t_1_count; i++ ) {
299 0 : fd_exec_test_prev_vote_account_t const * prev_vote_accs = &block_bank->vote_accounts_t_1[i];
300 :
301 0 : FD_TEST( prev_vote_accs->epoch_credits_count<=FD_EPOCH_CREDITS_MAX );
302 0 : fd_epoch_credits_t * ec = &fd_bank_epoch_credits( bank )[i];
303 0 : fd_memcpy( ec->pubkey, prev_vote_accs->address, sizeof(fd_pubkey_t) );
304 0 : ec->cnt = prev_vote_accs->epoch_credits_count;
305 0 : ec->base_credits = ec->cnt > 0UL ? prev_vote_accs->epoch_credits[0].prev_credits : 0UL;
306 0 : for( ulong j=0UL; j<prev_vote_accs->epoch_credits_count; j++ ) {
307 0 : ec->epoch[j] = (ushort)prev_vote_accs->epoch_credits[j].epoch;
308 0 : ec->credits_delta[j] = (uint)( prev_vote_accs->epoch_credits[j].credits - ec->base_credits );
309 0 : ec->prev_credits_delta[j] = (uint)( prev_vote_accs->epoch_credits[j].prev_credits - ec->base_credits );
310 0 : }
311 0 : }
312 0 : *fd_bank_epoch_credits_len( bank ) = block_bank->vote_accounts_t_1_count;
313 :
314 : /* Update leader schedule */
315 0 : fd_runtime_update_leaders( bank, runtime_stack );
316 :
317 : /* Set the initial lthash from the input. */
318 0 : fd_lthash_value_t * lthash = fd_bank_lthash_locking_modify( bank );
319 0 : fd_memcpy( lthash, block_bank->parent_lt_hash, sizeof(fd_lthash_value_t) );
320 0 : fd_bank_lthash_end_locking_modify( bank );
321 :
322 : /* Rent */
323 0 : FD_TEST( fd_sysvar_cache_rent_read( &runner->bank->f.sysvar_cache, &runner->bank->f.rent ) );
324 :
325 : /* Prepare raw transaction pointers and block / microblock infos */
326 0 : ulong txn_cnt = test_ctx->txns_count;
327 0 : fd_txn_p_t * txn_ptrs = fd_spad_alloc( runner->spad, alignof(fd_txn_p_t), txn_cnt * sizeof(fd_txn_p_t) );
328 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
329 0 : fd_txn_p_t * txn = &txn_ptrs[i];
330 0 : ulong msg_sz = fd_solfuzz_pb_txn_serialize( txn->payload, &test_ctx->txns[i] );
331 :
332 : // Reject any transactions over 1232 bytes
333 0 : if( FD_UNLIKELY( msg_sz==ULONG_MAX ) ) {
334 0 : return NULL;
335 0 : }
336 0 : txn->payload_sz = msg_sz;
337 :
338 : // Reject any transactions that cannot be parsed
339 0 : if( FD_UNLIKELY( !fd_txn_parse( txn->payload, msg_sz, TXN( txn ), NULL ) ) ) {
340 0 : return NULL;
341 0 : }
342 0 : }
343 :
344 0 : *out_txn_cnt = txn_cnt;
345 0 : return txn_ptrs;
346 0 : }
347 :
348 : /* Takes in a list of txn_p_t created from
349 : fd_runtime_fuzz_block_ctx_create and executes it against the runtime.
350 : Returns the execution result. */
351 : static int
352 : fd_solfuzz_block_ctx_exec( fd_solfuzz_runner_t * runner,
353 : fd_txn_p_t * txn_ptrs,
354 : ulong txn_cnt,
355 0 : fd_hash_t * poh ) {
356 0 : int res = 0;
357 :
358 : // Prepare. Execute. Finalize.
359 0 : FD_SPAD_FRAME_BEGIN( runner->spad ) {
360 0 : fd_capture_ctx_t * capture_ctx = NULL;
361 :
362 0 : if( runner->solcap ) {
363 0 : void * capture_ctx_mem = fd_spad_alloc( runner->spad, fd_capture_ctx_align(), fd_capture_ctx_footprint() );
364 0 : capture_ctx = fd_capture_ctx_join( fd_capture_ctx_new( capture_ctx_mem ) );
365 0 : if( FD_UNLIKELY( !capture_ctx ) ) {
366 0 : FD_LOG_ERR(( "Failed to initialize capture_ctx" ));
367 0 : }
368 :
369 0 : fd_capture_link_file_t * capture_link_file =
370 0 : fd_spad_alloc( runner->spad, alignof(fd_capture_link_file_t), sizeof(fd_capture_link_file_t) );
371 0 : if( FD_UNLIKELY( !capture_link_file ) ) {
372 0 : FD_LOG_ERR(( "Failed to allocate capture_link_file" ));
373 0 : }
374 :
375 0 : capture_link_file->base.vt = &fd_capture_link_file_vt;
376 :
377 0 : int solcap_fd = (int)(ulong)runner->solcap_file;
378 0 : capture_link_file->fd = solcap_fd;
379 0 : capture_ctx->capture_link = &capture_link_file->base;
380 0 : capture_ctx->capctx_type.file = capture_link_file;
381 0 : capture_ctx->solcap_start_slot = runner->bank->f.slot;
382 0 : capture_ctx->capture_solcap = 1;
383 :
384 0 : fd_solcap_writer_init( capture_ctx->capture, solcap_fd );
385 0 : }
386 :
387 0 : fd_rewards_recalculate_partitioned_rewards( runner->banks, runner->bank, runner->accdb, runner->runtime_stack, capture_ctx );
388 :
389 : /* Process new epoch may push a new spad frame onto the runtime spad. We should make sure this frame gets
390 : cleared (if it was allocated) before executing the block. */
391 0 : int is_epoch_boundary = 0;
392 0 : fd_runtime_block_execute_prepare( runner->banks, runner->bank, runner->accdb, runner->runtime_stack, capture_ctx, &is_epoch_boundary );
393 :
394 : /* Sequential transaction execution. Continue processing
395 : transactions even if a prior one was uncommitable. */
396 0 : int has_err = 0;
397 : /* fd_txn_out_t is ~10 MiB (inlined rollback/sysvar buffers), which
398 : overflows a tile's 8 MiB stack (FD_TILE_PRIVATE_STACK_SZ) when run
399 : under the multi-tile sol_compat harness. Allocate it from the
400 : per-runner spad instead (one buffer, reused each iteration). Must
401 : be the spad, not static, since runner tiles execute concurrently. */
402 0 : fd_txn_out_t * txn_out = fd_spad_alloc( runner->spad, alignof(fd_txn_out_t), sizeof(fd_txn_out_t) );
403 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
404 0 : fd_txn_p_t * txn = &txn_ptrs[i];
405 :
406 : /* Execute the transaction against the runtime */
407 0 : res = FD_RUNTIME_EXECUTE_SUCCESS;
408 0 : fd_txn_in_t txn_in = { .txn = txn, .bundle.is_bundle = 0 };
409 0 : fd_runtime_t * runtime = runner->runtime;
410 0 : fd_log_collector_t log[1];
411 0 : runtime->log.log_collector = log;
412 0 : fd_solfuzz_txn_ctx_exec( runner, runtime, &txn_in, &res, txn_out, 1 );
413 0 : txn_out->err.exec_err = res;
414 :
415 0 : if( FD_UNLIKELY( !txn_out->err.is_committable ) ) {
416 0 : fd_runtime_cancel_txn( runtime, NULL, NULL, txn_out, 0 );
417 0 : has_err = 1;
418 0 : continue;
419 0 : }
420 :
421 : /* Finalize the transaction */
422 0 : fd_runtime_commit_txn( runtime, runner->bank, NULL, txn_out, 0 );
423 :
424 0 : if( FD_UNLIKELY( !txn_out->err.is_committable ) ) {
425 0 : has_err = 1;
426 0 : continue;
427 0 : }
428 0 : }
429 :
430 : /* At this point we want to set the poh. This is what will get
431 : updated in the blockhash queue. */
432 0 : runner->bank->f.poh = *poh;
433 : /* Finalize the block */
434 0 : fd_runtime_block_execute_finalize( runner->bank, runner->accdb, capture_ctx );
435 :
436 0 : return !has_err;
437 0 : } FD_SPAD_FRAME_END;
438 0 : }
439 :
440 : /* Canonical (Agave-aligned) schedule hash
441 : Unique pubkeys referenced by sched, sorted deterministically
442 : Per-rotation indices mapped into sorted-uniq array */
443 : ulong
444 : fd_solfuzz_block_hash_epoch_leaders( fd_solfuzz_runner_t * runner,
445 : fd_epoch_leaders_t const * leaders,
446 : ulong seed,
447 0 : uchar out[16] ) {
448 : /* Single contiguous spad allocation for uniq[] and sched_mapped[] */
449 0 : void *buf = fd_spad_alloc(
450 0 : runner->spad,
451 0 : alignof(pk_with_pos_t),
452 0 : leaders->sched_cnt*sizeof(pk_with_pos_t) +
453 0 : leaders->sched_cnt*sizeof(uint) );
454 :
455 0 : pk_with_pos_t * tmp = (pk_with_pos_t *)buf;
456 0 : uint * sched_mapped = (uint *)( tmp + leaders->sched_cnt );
457 :
458 : /* Gather all pubkeys and original positions from sched[] (skip invalid) */
459 0 : ulong gather_cnt = 0UL;
460 0 : for( ulong i=0UL; i<leaders->sched_cnt; i++ ) {
461 0 : uint idx = leaders->sched[i];
462 0 : if( idx>=leaders->pub_cnt ) { /* invalid slot leader */
463 0 : sched_mapped[i] = 0U; /* prefill invalid mapping */
464 0 : continue;
465 0 : }
466 0 : fd_memcpy( &tmp[gather_cnt].pk, &leaders->pub[idx], sizeof(fd_pubkey_t) );
467 0 : tmp[gather_cnt].sched_pos = i;
468 0 : gather_cnt++;
469 0 : }
470 :
471 0 : if( gather_cnt==0UL ) {
472 : /* No leaders => hash:=0, count:=0 */
473 0 : fd_memset( out, 0, sizeof(ulong)*2 );
474 0 : return 0UL;
475 0 : }
476 :
477 : /* Sort tmp[] by pubkey, note: comparator relies on first struct member */
478 0 : sort_pkpos_inplace( tmp, (ulong)gather_cnt );
479 :
480 : /* Dedupe and assign indices into sched_mapped[] during single pass */
481 0 : ulong uniq_cnt = 0UL;
482 0 : for( ulong i=0UL; i<gather_cnt; i++ ) {
483 0 : if( i==0UL || memcmp( &tmp[i].pk, &tmp[i-1].pk, sizeof(fd_pubkey_t) )!=0 )
484 0 : uniq_cnt++;
485 : /* uniq_cnt-1 is index in uniq set */
486 0 : sched_mapped[tmp[i].sched_pos] = (uint)(uniq_cnt-1UL);
487 0 : }
488 :
489 : /* Reconstruct contiguous uniq[] for hashing */
490 0 : fd_pubkey_t *uniq = fd_spad_alloc( runner->spad,
491 0 : alignof(fd_pubkey_t),
492 0 : uniq_cnt*sizeof(fd_pubkey_t) );
493 0 : {
494 0 : ulong write_pos = 0UL;
495 0 : for( ulong i=0UL; i<gather_cnt; i++ ) {
496 0 : if( i==0UL || memcmp( &tmp[i].pk, &tmp[i-1].pk, sizeof(fd_pubkey_t) )!=0 )
497 0 : fd_memcpy( &uniq[write_pos++], &tmp[i].pk, sizeof(fd_pubkey_t) );
498 0 : }
499 0 : }
500 :
501 : /* Hash sorted unique pubkeys */
502 0 : ulong h1 = fd_hash( seed, uniq, uniq_cnt * sizeof(fd_pubkey_t) );
503 0 : fd_memcpy( out, &h1, sizeof(ulong) );
504 :
505 : /* Hash mapped indices */
506 0 : ulong h2 = fd_hash( seed, sched_mapped, leaders->sched_cnt * sizeof(uint) );
507 0 : fd_memcpy( out + sizeof(ulong), &h2, sizeof(ulong) );
508 :
509 0 : return uniq_cnt;
510 0 : }
511 :
512 : static void
513 : fd_solfuzz_pb_build_leader_schedule_effects( fd_solfuzz_runner_t * runner,
514 0 : fd_exec_test_block_effects_t * effects ) {
515 : /* Read epoch schedule sysvar */
516 0 : fd_epoch_schedule_t const * epoch_schedule = &runner->bank->f.epoch_schedule;
517 0 : FD_TEST( epoch_schedule );
518 :
519 : /* We will capture the leader schedule for the current epoch that we
520 : are in. This will capture the leader schedule generated by an
521 : epoch boundary if one was crossed. */
522 0 : ulong epoch = runner->bank->f.epoch;
523 0 : ulong ls_slot0 = fd_epoch_slot0( epoch_schedule, epoch );
524 0 : ulong slots_in_epoch = fd_epoch_slot_cnt( epoch_schedule, epoch );
525 :
526 0 : fd_epoch_leaders_t const * effects_leaders = fd_bank_epoch_leaders_query( runner->bank, epoch );
527 :
528 : /* Fill out effects struct from the Agave epoch info */
529 0 : effects->has_leader_schedule = 1;
530 0 : effects->leader_schedule.leaders_epoch = epoch;
531 0 : effects->leader_schedule.leaders_slot0 = ls_slot0;
532 0 : effects->leader_schedule.leaders_slot_cnt = slots_in_epoch;
533 0 : effects->leader_schedule.leaders_sched_cnt = slots_in_epoch;
534 0 : effects->leader_schedule.leader_pub_cnt = fd_solfuzz_block_hash_epoch_leaders(
535 0 : runner, effects_leaders,
536 0 : LEADER_SCHEDULE_HASH_SEED,
537 0 : effects->leader_schedule.leader_schedule_hash
538 0 : );
539 0 : }
540 :
541 : ulong
542 : fd_solfuzz_pb_block_run( fd_solfuzz_runner_t * runner,
543 : void const * input_,
544 : void ** output_,
545 : void * output_buf,
546 0 : ulong output_bufsz ) {
547 0 : fd_exec_test_block_context_t const * input = fd_type_pun_const( input_ );
548 0 : fd_exec_test_block_effects_t ** output = fd_type_pun( output_ );
549 :
550 0 : FD_SPAD_FRAME_BEGIN( runner->spad ) {
551 0 : ulong txn_cnt;
552 0 : fd_hash_t poh = {0};
553 0 : fd_txn_p_t * txn_ptrs = fd_solfuzz_pb_block_ctx_create( runner, input, &txn_cnt, &poh );
554 0 : if( txn_ptrs==NULL ) {
555 0 : fd_solfuzz_pb_block_ctx_destroy( runner );
556 0 : return 0;
557 0 : }
558 :
559 : /* Execute the constructed block against the runtime. */
560 0 : int is_committable = fd_solfuzz_block_ctx_exec( runner, txn_ptrs, txn_cnt, &poh );
561 :
562 : /* Start saving block exec results */
563 0 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
564 0 : ulong output_end = (ulong)output_buf + output_bufsz;
565 :
566 0 : fd_exec_test_block_effects_t * effects =
567 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_block_effects_t),
568 0 : sizeof(fd_exec_test_block_effects_t) );
569 0 : if( FD_UNLIKELY( _l > output_end ) ) {
570 0 : abort();
571 0 : }
572 0 : fd_memset( effects, 0, sizeof(fd_exec_test_block_effects_t) );
573 :
574 : /* Capture error status */
575 0 : effects->has_error = !is_committable;
576 :
577 : /* Capture capitalization */
578 0 : effects->slot_capitalization = !effects->has_error ? runner->bank->f.capitalization : 0UL;
579 :
580 : /* Capture hashes */
581 0 : fd_hash_t bank_hash = !effects->has_error ? runner->bank->f.bank_hash : (fd_hash_t){0};
582 0 : fd_memcpy( effects->bank_hash, bank_hash.hash, sizeof(fd_hash_t) );
583 :
584 : /* Capture cost tracker */
585 0 : fd_cost_tracker_t const * cost_tracker = fd_bank_cost_tracker_query( runner->bank );
586 0 : effects->has_cost_tracker = 1;
587 0 : effects->cost_tracker = (fd_exec_test_cost_tracker_t) {
588 0 : .block_cost = cost_tracker ? cost_tracker->block_cost : 0UL,
589 0 : };
590 :
591 : /* Effects: build T-epoch (bank epoch), T-stakes ephemeral leaders and report */
592 0 : fd_solfuzz_pb_build_leader_schedule_effects( runner, effects );
593 :
594 0 : ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
595 0 : fd_solfuzz_pb_block_ctx_destroy( runner );
596 :
597 0 : *output = effects;
598 0 : return actual_end - (ulong)output_buf;
599 0 : } FD_SPAD_FRAME_END;
600 0 : }
|