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