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