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_txn_account.h"
7 : #include "../fd_runtime_stack.h"
8 : #include "../program/fd_stake_program.h"
9 : #include "../program/fd_vote_program.h"
10 : #include "../sysvar/fd_sysvar_epoch_schedule.h"
11 : #include "../sysvar/fd_sysvar_rent.h"
12 : #include "../sysvar/fd_sysvar_recent_hashes.h"
13 : #include "../../rewards/fd_rewards.h"
14 : #include "../../stakes/fd_stakes.h"
15 : #include "../../types/fd_types.h"
16 : #include "../../../disco/pack/fd_pack.h"
17 : #include "generated/block.pb.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 : /* Stripped down version of `fd_refresh_vote_accounts()` that simply refreshes the stake delegation amount
34 : for each of the vote accounts using the stake delegations cache. */
35 : static void
36 : fd_runtime_fuzz_block_refresh_vote_accounts( fd_vote_states_t * vote_states,
37 : fd_vote_states_t * vote_states_prev_prev,
38 : fd_stake_delegations_t * stake_delegations,
39 0 : ulong epoch ) {
40 0 : fd_stake_delegations_iter_t iter_[1];
41 0 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
42 0 : !fd_stake_delegations_iter_done( iter );
43 0 : fd_stake_delegations_iter_next( iter ) ) {
44 0 : fd_stake_delegation_t * node = fd_stake_delegations_iter_ele( iter );
45 :
46 0 : fd_pubkey_t * voter_pubkey = &node->vote_account;
47 0 : ulong stake = node->stake;
48 :
49 : /* Find the voter in the vote accounts cache and update their
50 : delegation amount */
51 0 : fd_vote_state_ele_t * vote_state = fd_vote_states_query( vote_states, voter_pubkey );
52 0 : if( !vote_state ) continue;
53 :
54 0 : vote_state->stake += stake;
55 0 : vote_state->stake_t_2 += stake;
56 0 : }
57 :
58 : /* We need to set the stake_t_2 for the vote accounts in the vote
59 : states cache. An important edge case to handle is if the current
60 : epoch is less than 2, that means we should use the current stakes
61 : because the stake_t_2 field is not yet populated. */
62 0 : fd_vote_states_iter_t vs_iter_[1];
63 0 : for( fd_vote_states_iter_t * iter = fd_vote_states_iter_init( vs_iter_, vote_states_prev_prev );
64 0 : !fd_vote_states_iter_done( iter );
65 0 : fd_vote_states_iter_next( iter ) ) {
66 0 : fd_vote_state_ele_t * vote_state = fd_vote_states_iter_ele( iter );
67 0 : fd_vote_state_ele_t * vote_state_prev_prev = fd_vote_states_query( vote_states_prev_prev, &vote_state->vote_account );
68 0 : ulong t_2_stake = !!vote_state_prev_prev ? vote_state_prev_prev->stake : 0UL;
69 0 : vote_state->stake_t_2 = epoch>=2UL ? t_2_stake : vote_state->stake;
70 0 : vote_state->stake_t_2 = vote_state->stake;
71 0 : }
72 0 : }
73 :
74 : /* Registers a single vote account into the current votes cache. The entry is derived
75 : from the current present account state. This function also registers a vote timestamp
76 : for the vote account */
77 : static void
78 : fd_runtime_fuzz_block_register_vote_account( fd_funk_t * funk,
79 : fd_funk_txn_xid_t const * xid,
80 : fd_vote_states_t * vote_states,
81 : fd_pubkey_t * pubkey,
82 0 : fd_spad_t * spad ) {
83 0 : fd_txn_account_t acc[1];
84 0 : if( FD_UNLIKELY( fd_txn_account_init_from_funk_readonly( acc, pubkey, funk, xid ) ) ) {
85 0 : return;
86 0 : }
87 :
88 : /* Account must be owned by the vote program */
89 0 : if( memcmp( fd_txn_account_get_owner( acc ), fd_solana_vote_program_id.key, sizeof(fd_pubkey_t) ) ) {
90 0 : return;
91 0 : }
92 :
93 : /* Account must have > 0 lamports */
94 0 : if( fd_txn_account_get_lamports( acc )==0UL ) {
95 0 : return;
96 0 : }
97 :
98 : /* Account must be initialized correctly */
99 0 : if( FD_UNLIKELY( !fd_vote_state_versions_is_correct_and_initialized( acc ) ) ) {
100 0 : return;
101 0 : }
102 :
103 : /* Get the vote state from the account data */
104 0 : fd_vote_state_versioned_t * vsv = NULL;
105 0 : int err = fd_vote_get_state( acc, spad, &vsv );
106 0 : if( FD_UNLIKELY( err ) ) {
107 0 : return;
108 0 : }
109 :
110 0 : fd_vote_states_update_from_account(
111 0 : vote_states,
112 0 : acc->pubkey,
113 0 : fd_txn_account_get_data( acc ),
114 0 : fd_txn_account_get_data_len( acc ) );
115 0 : }
116 :
117 : /* Stores an entry in the stake delegations cache for the given vote account. Deserializes and uses the present
118 : account state to derive delegation information. */
119 : static void
120 : fd_runtime_fuzz_block_register_stake_delegation( fd_funk_t * funk,
121 : fd_funk_txn_xid_t const * xid,
122 : fd_stake_delegations_t * stake_delegations,
123 0 : fd_pubkey_t * pubkey ) {
124 0 : fd_txn_account_t acc[1];
125 0 : if( FD_UNLIKELY( fd_txn_account_init_from_funk_readonly( acc, pubkey, funk, xid ) ) ) {
126 0 : return;
127 0 : }
128 :
129 : /* Account must be owned by the stake program */
130 0 : if( memcmp( fd_txn_account_get_owner( acc ), fd_solana_stake_program_id.key, sizeof(fd_pubkey_t) ) ) {
131 0 : return;
132 0 : }
133 :
134 : /* Account must have > 0 lamports */
135 0 : if( fd_txn_account_get_lamports( acc )==0UL ) {
136 0 : return;
137 0 : }
138 :
139 : /* Stake state must exist and be initialized correctly */
140 0 : fd_stake_state_v2_t stake_state;
141 0 : if( FD_UNLIKELY( fd_stake_get_state( acc, &stake_state ) || !fd_stake_state_v2_is_stake( &stake_state ) ) ) {
142 0 : return;
143 0 : }
144 :
145 : /* Skip 0-stake accounts */
146 0 : if( FD_UNLIKELY( stake_state.inner.stake.stake.delegation.stake==0UL ) ) {
147 0 : return;
148 0 : }
149 :
150 : /* Nothing to do if the account already exists in the cache */
151 0 : fd_stake_delegations_update(
152 0 : stake_delegations,
153 0 : pubkey,
154 0 : &stake_state.inner.stake.stake.delegation.voter_pubkey,
155 0 : stake_state.inner.stake.stake.delegation.stake,
156 0 : stake_state.inner.stake.stake.delegation.activation_epoch,
157 0 : stake_state.inner.stake.stake.delegation.deactivation_epoch,
158 0 : stake_state.inner.stake.stake.credits_observed,
159 0 : stake_state.inner.stake.stake.delegation.warmup_cooldown_rate );
160 0 : }
161 :
162 : /* Common helper method for populating a previous epoch's vote cache. */
163 : static void
164 : fd_runtime_fuzz_block_update_prev_epoch_votes_cache( fd_vote_states_t * vote_states,
165 : fd_exec_test_vote_account_t * vote_accounts,
166 : pb_size_t vote_accounts_cnt,
167 0 : fd_spad_t * spad ) {
168 0 : FD_SPAD_FRAME_BEGIN( spad ) {
169 0 : for( uint i=0U; i<vote_accounts_cnt; i++ ) {
170 0 : fd_exec_test_acct_state_t * vote_account = &vote_accounts[i].vote_account;
171 0 : ulong stake = vote_accounts[i].stake;
172 0 : uchar * vote_data = vote_account->data->bytes;
173 0 : ulong vote_data_len = vote_account->data->size;
174 0 : fd_pubkey_t vote_address = {0};
175 0 : fd_memcpy( &vote_address, vote_account->address, sizeof(fd_pubkey_t) );
176 :
177 : /* Try decoding the vote state from the account data. If it isn't
178 : decodable, don't try inserting it into the cache. */
179 0 : fd_vote_state_versioned_t * res = fd_bincode_decode_spad(
180 0 : vote_state_versioned, spad,
181 0 : vote_data,
182 0 : vote_data_len,
183 0 : NULL );
184 0 : if( res==NULL ) continue;
185 :
186 0 : fd_vote_states_update_from_account( vote_states, &vote_address, vote_data, vote_data_len );
187 0 : fd_vote_state_ele_t * vote_state = fd_vote_states_query( vote_states, &vote_address );
188 0 : vote_state->stake += stake;
189 0 : vote_state->stake_t_2 += stake;
190 0 : }
191 0 : } FD_SPAD_FRAME_END;
192 0 : }
193 :
194 : static void
195 0 : fd_runtime_fuzz_block_ctx_destroy( fd_solfuzz_runner_t * runner ) {
196 0 : fd_accdb_clear( runner->accdb_admin );
197 0 : fd_progcache_clear( runner->progcache_admin );
198 0 : }
199 :
200 : /* Sets up block execution context from an input test case to execute against the runtime.
201 : Returns block_info on success and NULL on failure. */
202 : static fd_txn_p_t *
203 : fd_runtime_fuzz_block_ctx_create( fd_solfuzz_runner_t * runner,
204 : fd_runtime_stack_t * runtime_stack,
205 : fd_exec_test_block_context_t const * test_ctx,
206 0 : ulong * out_txn_cnt ) {
207 0 : fd_accdb_user_t * accdb = runner->accdb;
208 0 : fd_funk_t * funk = runner->accdb->funk;
209 0 : fd_bank_t * bank = runner->bank;
210 0 : fd_banks_t * banks = runner->banks;
211 :
212 0 : fd_banks_clear_bank( banks, bank );
213 :
214 : /* Generate unique ID for funk txn */
215 0 : fd_funk_txn_xid_t xid[1] = {{ .ul={ LONG_MAX,LONG_MAX } }};
216 :
217 : /* Create temporary funk transaction and slot / epoch contexts */
218 0 : fd_funk_txn_xid_t parent_xid; fd_funk_txn_xid_set_root( &parent_xid );
219 0 : fd_accdb_attach_child( runner->accdb_admin, &parent_xid, xid );
220 0 : fd_progcache_txn_attach_child( runner->progcache_admin, &parent_xid, xid );
221 :
222 : /* Restore feature flags */
223 0 : fd_features_t features = {0};
224 0 : if( !fd_runtime_fuzz_restore_features( &features, &test_ctx->epoch_ctx.features ) ) {
225 0 : return NULL;
226 0 : }
227 0 : fd_bank_features_set( bank, features );
228 :
229 : /* Set up slot context */
230 0 : ulong slot = test_ctx->slot_ctx.slot;
231 0 : ulong parent_slot = test_ctx->slot_ctx.prev_slot;
232 :
233 0 : fd_hash_t * bank_hash = fd_bank_bank_hash_modify( bank );
234 0 : fd_memcpy( bank_hash, test_ctx->slot_ctx.parent_bank_hash, sizeof(fd_hash_t) );
235 :
236 : /* All bank mgr stuff here. */
237 :
238 0 : fd_bank_slot_set( bank, slot );
239 :
240 0 : fd_bank_parent_slot_set( bank, parent_slot );
241 :
242 0 : fd_bank_block_height_set( bank, test_ctx->slot_ctx.block_height );
243 :
244 0 : fd_bank_capitalization_set( bank, test_ctx->slot_ctx.prev_epoch_capitalization );
245 :
246 0 : fd_bank_lamports_per_signature_set( bank, 5000UL );
247 :
248 0 : fd_bank_prev_lamports_per_signature_set( bank, test_ctx->slot_ctx.prev_lps );
249 :
250 : // self.max_tick_height = (self.slot + 1) * self.ticks_per_slot;
251 0 : fd_bank_hashes_per_tick_set( bank, test_ctx->epoch_ctx.hashes_per_tick );
252 :
253 0 : fd_bank_ticks_per_slot_set( bank, test_ctx->epoch_ctx.ticks_per_slot );
254 :
255 0 : fd_bank_ns_per_slot_set( bank, 400000000 ); // TODO: restore from input
256 :
257 0 : fd_bank_genesis_creation_time_set( bank, test_ctx->epoch_ctx.genesis_creation_time );
258 :
259 0 : fd_bank_slots_per_year_set( bank, test_ctx->epoch_ctx.slots_per_year );
260 :
261 0 : fd_bank_parent_signature_cnt_set( bank, test_ctx->slot_ctx.parent_signature_count );
262 :
263 0 : fd_fee_rate_governor_t * fee_rate_governor = fd_bank_fee_rate_governor_modify( bank );
264 0 : *fee_rate_governor = (fd_fee_rate_governor_t){
265 0 : .target_lamports_per_signature = test_ctx->slot_ctx.fee_rate_governor.target_lamports_per_signature,
266 0 : .target_signatures_per_slot = test_ctx->slot_ctx.fee_rate_governor.target_signatures_per_slot,
267 0 : .min_lamports_per_signature = test_ctx->slot_ctx.fee_rate_governor.min_lamports_per_signature,
268 0 : .max_lamports_per_signature = test_ctx->slot_ctx.fee_rate_governor.max_lamports_per_signature,
269 0 : .burn_percent = (uchar)test_ctx->slot_ctx.fee_rate_governor.burn_percent
270 0 : };
271 :
272 0 : fd_inflation_t * inflation = fd_bank_inflation_modify( bank );
273 0 : *inflation = (fd_inflation_t){
274 0 : .initial = test_ctx->epoch_ctx.inflation.initial,
275 0 : .terminal = test_ctx->epoch_ctx.inflation.terminal,
276 0 : .taper = test_ctx->epoch_ctx.inflation.taper,
277 0 : .foundation = test_ctx->epoch_ctx.inflation.foundation,
278 0 : .foundation_term = test_ctx->epoch_ctx.inflation.foundation_term
279 0 : };
280 :
281 0 : fd_bank_block_height_set( bank, test_ctx->slot_ctx.block_height );
282 :
283 : /* Initialize the current running epoch stake and vote accounts */
284 :
285 0 : fd_vote_states_t * vote_states = fd_bank_vote_states_locking_modify( bank );
286 0 : vote_states = fd_vote_states_join( fd_vote_states_new( vote_states, FD_RUNTIME_MAX_VOTE_ACCOUNTS, 999UL ) );
287 0 : fd_bank_vote_states_end_locking_modify( bank );
288 :
289 0 : fd_vote_states_t * vote_states_prev = fd_bank_vote_states_prev_locking_modify( bank );
290 0 : vote_states_prev = fd_vote_states_join( fd_vote_states_new( vote_states_prev, FD_RUNTIME_MAX_VOTE_ACCOUNTS, 999UL ) );
291 0 : fd_bank_vote_states_prev_end_locking_modify( bank );
292 :
293 0 : fd_vote_states_t * vote_states_prev_prev = fd_bank_vote_states_prev_prev_locking_modify( bank );
294 0 : vote_states_prev_prev = fd_vote_states_join( fd_vote_states_new( vote_states_prev_prev, FD_RUNTIME_MAX_VOTE_ACCOUNTS, 999UL ) );
295 0 : fd_bank_vote_states_prev_prev_end_locking_modify( bank );
296 :
297 0 : fd_stake_delegations_t * stake_delegations = fd_banks_stake_delegations_root_query( banks );
298 0 : stake_delegations = fd_stake_delegations_join( fd_stake_delegations_new( stake_delegations, FD_RUNTIME_MAX_STAKE_ACCOUNTS, 0 ) );
299 :
300 : /* Load in all accounts with > 0 lamports provided in the context. The input expects unique account pubkeys. */
301 0 : vote_states = fd_bank_vote_states_locking_modify( bank );
302 0 : for( ushort i=0; i<test_ctx->acct_states_count; i++ ) {
303 0 : fd_txn_account_t acc[1];
304 0 : fd_runtime_fuzz_load_account( acc, funk, xid, &test_ctx->acct_states[i], 1 );
305 :
306 : /* Update vote accounts cache for epoch T */
307 0 : fd_pubkey_t pubkey;
308 0 : memcpy( &pubkey, test_ctx->acct_states[i].address, sizeof(fd_pubkey_t) );
309 0 : fd_runtime_fuzz_block_register_vote_account(
310 0 : funk,
311 0 : xid,
312 0 : vote_states,
313 0 : &pubkey,
314 0 : runner->spad );
315 :
316 : /* Update the stake delegations cache for epoch T */
317 0 : fd_runtime_fuzz_block_register_stake_delegation( funk,
318 0 : xid,
319 0 : stake_delegations,
320 0 : &pubkey );
321 0 : }
322 :
323 : /* Zero out vote stakes to avoid leakage across tests */
324 0 : fd_vote_states_reset_stakes( vote_states );
325 :
326 : /* Finish init epoch bank sysvars */
327 0 : fd_epoch_schedule_t epoch_schedule_[1];
328 0 : fd_epoch_schedule_t * epoch_schedule = fd_sysvar_epoch_schedule_read( funk, xid, epoch_schedule_ );
329 0 : FD_TEST( epoch_schedule );
330 0 : fd_bank_epoch_schedule_set( bank, *epoch_schedule );
331 :
332 0 : fd_rent_t const * rent = fd_sysvar_rent_read( funk, xid, runner->spad );
333 0 : FD_TEST( rent );
334 0 : fd_bank_rent_set( bank, *rent );
335 :
336 : /* Current epoch gets updated in process_new_epoch, so use the epoch
337 : from the parent slot */
338 0 : fd_bank_epoch_set( bank, fd_slot_to_epoch( epoch_schedule, parent_slot, NULL ) );
339 :
340 : /* Update vote cache for epoch T-1 */
341 0 : vote_states_prev = fd_bank_vote_states_prev_locking_modify( bank );
342 0 : fd_runtime_fuzz_block_update_prev_epoch_votes_cache( vote_states_prev,
343 0 : test_ctx->epoch_ctx.vote_accounts_t_1,
344 0 : test_ctx->epoch_ctx.vote_accounts_t_1_count,
345 0 : runner->spad );
346 0 : fd_bank_vote_states_prev_end_locking_modify( bank );
347 :
348 : /* Update vote cache for epoch T-2 */
349 0 : vote_states_prev_prev = fd_bank_vote_states_prev_prev_locking_modify( bank );
350 0 : fd_runtime_fuzz_block_update_prev_epoch_votes_cache( vote_states_prev_prev,
351 0 : test_ctx->epoch_ctx.vote_accounts_t_2,
352 0 : test_ctx->epoch_ctx.vote_accounts_t_2_count,
353 0 : runner->spad );
354 :
355 : /* Refresh vote accounts to calculate stake delegations */
356 0 : fd_runtime_fuzz_block_refresh_vote_accounts( vote_states, vote_states_prev_prev, stake_delegations, fd_bank_epoch_get( bank ) );
357 0 : fd_bank_vote_states_end_locking_modify( bank );
358 :
359 0 : fd_bank_vote_states_prev_prev_end_locking_modify( bank );
360 :
361 : /* Update leader schedule */
362 0 : fd_runtime_update_leaders( bank, runtime_stack );
363 :
364 : /* Initialize the blockhash queue and recent blockhashes sysvar from the input blockhash queue */
365 0 : ulong blockhash_seed; FD_TEST( fd_rng_secure( &blockhash_seed, sizeof(ulong) ) );
366 0 : fd_blockhashes_init( fd_bank_block_hash_queue_modify( bank ), blockhash_seed );
367 :
368 : /* TODO: We might need to load this in from the input. We also need to
369 : size this out for worst case, but this also blows up the memory
370 : requirement. */
371 : /* Allocate all the memory for the rent fresh accounts list */
372 :
373 : // Set genesis hash to {0}
374 0 : fd_hash_t * genesis_hash = fd_bank_genesis_hash_modify( bank );
375 0 : fd_memset( genesis_hash->hash, 0, sizeof(fd_hash_t) );
376 :
377 : // Use the latest lamports per signature
378 0 : fd_recent_block_hashes_t const * rbh = fd_sysvar_recent_hashes_read( funk, xid, runner->spad );
379 0 : if( rbh && !deq_fd_block_block_hash_entry_t_empty( rbh->hashes ) ) {
380 0 : fd_block_block_hash_entry_t const * last = deq_fd_block_block_hash_entry_t_peek_head_const( rbh->hashes );
381 0 : if( last && last->fee_calculator.lamports_per_signature!=0UL ) {
382 0 : fd_bank_lamports_per_signature_set( bank, last->fee_calculator.lamports_per_signature );
383 0 : fd_bank_prev_lamports_per_signature_set( bank, last->fee_calculator.lamports_per_signature );
384 0 : }
385 0 : }
386 :
387 : /* Make a new funk transaction since we're done loading in accounts for context */
388 0 : fd_funk_txn_xid_t fork_xid = { .ul = { slot, 0UL } };
389 0 : fd_accdb_attach_child ( runner->accdb_admin, xid, &fork_xid );
390 0 : fd_progcache_txn_attach_child( runner->progcache_admin, xid, &fork_xid );
391 0 : xid[0] = fork_xid;
392 :
393 : /* Set the initial lthash from the input since we're in a new Funk txn */
394 0 : fd_lthash_value_t * lthash = fd_bank_lthash_locking_modify( bank );
395 0 : fd_memcpy( lthash, test_ctx->slot_ctx.parent_lthash, sizeof(fd_lthash_value_t) );
396 0 : fd_bank_lthash_end_locking_modify( bank );
397 :
398 : // Populate blockhash queue and recent blockhashes sysvar
399 0 : for( ushort i=0; i<test_ctx->blockhash_queue_count; ++i ) {
400 0 : fd_hash_t hash;
401 0 : memcpy( &hash, test_ctx->blockhash_queue[i]->bytes, sizeof(fd_hash_t) );
402 0 : fd_bank_poh_set( bank, hash );
403 0 : fd_sysvar_recent_hashes_update( bank, accdb, xid, NULL ); /* appends an entry */
404 0 : }
405 :
406 : // Set the current poh from the input (we skip POH verification in this fuzzing target)
407 0 : fd_hash_t * poh = fd_bank_poh_modify( bank );
408 0 : fd_memcpy( poh->hash, test_ctx->slot_ctx.poh, sizeof(fd_hash_t) );
409 :
410 : /* Restore sysvar cache */
411 0 : fd_sysvar_cache_restore_fuzz( bank, funk, xid );
412 :
413 : /* Prepare raw transaction pointers and block / microblock infos */
414 0 : ulong txn_cnt = test_ctx->txns_count;
415 0 : fd_txn_p_t * txn_ptrs = fd_spad_alloc( runner->spad, alignof(fd_txn_p_t), txn_cnt * sizeof(fd_txn_p_t) );
416 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
417 0 : fd_txn_p_t * txn = &txn_ptrs[i];
418 0 : ulong msg_sz = fd_runtime_fuzz_serialize_txn( txn->payload, &test_ctx->txns[i] );
419 :
420 : // Reject any transactions over 1232 bytes
421 0 : if( FD_UNLIKELY( msg_sz==ULONG_MAX ) ) {
422 0 : return NULL;
423 0 : }
424 0 : txn->payload_sz = msg_sz;
425 :
426 : // Reject any transactions that cannot be parsed
427 0 : if( FD_UNLIKELY( !fd_txn_parse( txn->payload, msg_sz, TXN( txn ), NULL ) ) ) {
428 0 : return NULL;
429 0 : }
430 0 : }
431 :
432 0 : *out_txn_cnt = txn_cnt;
433 0 : return txn_ptrs;
434 0 : }
435 :
436 : /* Takes in a list of txn_p_t created from
437 : fd_runtime_fuzz_block_ctx_create and executes it against the runtime.
438 : Returns the execution result. */
439 : static int
440 : fd_runtime_fuzz_block_ctx_exec( fd_solfuzz_runner_t * runner,
441 : fd_runtime_stack_t * runtime_stack,
442 : fd_funk_txn_xid_t const * xid,
443 : fd_txn_p_t * txn_ptrs,
444 0 : ulong txn_cnt ) {
445 0 : int res = 0;
446 :
447 : // Prepare. Execute. Finalize.
448 0 : FD_SPAD_FRAME_BEGIN( runner->spad ) {
449 0 : fd_capture_ctx_t * capture_ctx = NULL;
450 0 : if( runner->solcap ) {
451 0 : void * capture_ctx_mem = fd_spad_alloc( runner->spad, fd_capture_ctx_align(), fd_capture_ctx_footprint() );
452 0 : capture_ctx = fd_capture_ctx_new( capture_ctx_mem );
453 0 : if( FD_UNLIKELY( capture_ctx==NULL ) ) {
454 0 : FD_LOG_ERR(("capture_ctx_mem is NULL, cannot write solcap"));
455 0 : }
456 0 : capture_ctx->capture = runner->solcap;
457 0 : capture_ctx->solcap_start_slot = fd_bank_slot_get( runner->bank );
458 0 : fd_solcap_writer_set_slot( capture_ctx->capture, fd_bank_slot_get( runner->bank ) );
459 0 : }
460 :
461 0 : fd_rewards_recalculate_partitioned_rewards( runner->banks, runner->bank, runner->accdb->funk, xid, runtime_stack, capture_ctx );
462 :
463 : /* Process new epoch may push a new spad frame onto the runtime spad. We should make sure this frame gets
464 : cleared (if it was allocated) before executing the block. */
465 0 : int is_epoch_boundary = 0;
466 0 : fd_runtime_block_pre_execute_process_new_epoch( runner->banks, runner->bank, runner->accdb, xid, capture_ctx, runtime_stack, &is_epoch_boundary );
467 :
468 0 : res = fd_runtime_block_execute_prepare( runner->bank, runner->accdb, xid, runtime_stack, capture_ctx );
469 0 : if( FD_UNLIKELY( res ) ) {
470 0 : return res;
471 0 : }
472 :
473 : /* Sequential transaction execution */
474 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
475 0 : fd_txn_p_t * txn = &txn_ptrs[i];
476 :
477 : /* Execute the transaction against the runtime */
478 0 : res = FD_RUNTIME_EXECUTE_SUCCESS;
479 0 : fd_exec_txn_ctx_t * txn_ctx = fd_runtime_fuzz_txn_ctx_exec( runner, xid, txn, &res );
480 0 : txn_ctx->exec_err = res;
481 :
482 0 : if( FD_UNLIKELY( !(txn_ctx->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS) ) ) {
483 0 : break;
484 0 : }
485 :
486 : /* Finalize the transaction */
487 0 : fd_runtime_finalize_txn(
488 0 : runner->accdb->funk,
489 0 : runner->progcache,
490 0 : NULL,
491 0 : xid,
492 0 : txn_ctx,
493 0 : runner->bank,
494 0 : capture_ctx );
495 :
496 0 : if( FD_UNLIKELY( !(txn_ctx->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS) ) ) {
497 0 : break;
498 0 : }
499 :
500 0 : res = FD_RUNTIME_EXECUTE_SUCCESS;
501 0 : }
502 :
503 : /* Finalize the block */
504 0 : fd_runtime_block_execute_finalize( runner->bank, runner->accdb, xid, capture_ctx, 1 );
505 0 : } FD_SPAD_FRAME_END;
506 :
507 0 : return res;
508 0 : }
509 :
510 : /* Canonical (Agave-aligned) schedule hash
511 : Unique pubkeys referenced by sched, sorted deterministically
512 : Per-rotation indices mapped into sorted-uniq array */
513 : ulong
514 : fd_hash_epoch_leaders( fd_solfuzz_runner_t * runner,
515 : fd_epoch_leaders_t const * leaders,
516 : ulong seed,
517 0 : uchar out[16] ) {
518 : /* Single contiguous spad allocation for uniq[] and sched_mapped[] */
519 0 : void *buf = fd_spad_alloc(
520 0 : runner->spad,
521 0 : alignof(pk_with_pos_t),
522 0 : leaders->sched_cnt*sizeof(pk_with_pos_t) +
523 0 : leaders->sched_cnt*sizeof(uint) );
524 :
525 0 : pk_with_pos_t * tmp = (pk_with_pos_t *)buf;
526 0 : uint * sched_mapped = (uint *)( tmp + leaders->sched_cnt );
527 :
528 : /* Gather all pubkeys and original positions from sched[] (skip invalid) */
529 0 : ulong gather_cnt = 0UL;
530 0 : for( ulong i=0UL; i<leaders->sched_cnt; i++ ) {
531 0 : uint idx = leaders->sched[i];
532 0 : if( idx>=leaders->pub_cnt ) { /* invalid slot leader */
533 0 : sched_mapped[i] = 0U; /* prefill invalid mapping */
534 0 : continue;
535 0 : }
536 0 : fd_memcpy( &tmp[gather_cnt].pk, &leaders->pub[idx], sizeof(fd_pubkey_t) );
537 0 : tmp[gather_cnt].sched_pos = i;
538 0 : gather_cnt++;
539 0 : }
540 :
541 0 : if( gather_cnt==0UL ) {
542 : /* No leaders => hash:=0, count:=0 */
543 0 : fd_memset( out, 0, sizeof(ulong)*2 );
544 0 : return 0UL;
545 0 : }
546 :
547 : /* Sort tmp[] by pubkey, note: comparator relies on first struct member */
548 0 : sort_pkpos_inplace( tmp, (ulong)gather_cnt );
549 :
550 : /* Dedupe and assign indices into sched_mapped[] during single pass */
551 0 : ulong uniq_cnt = 0UL;
552 0 : for( ulong i=0UL; i<gather_cnt; i++ ) {
553 0 : if( i==0UL || memcmp( &tmp[i].pk, &tmp[i-1].pk, sizeof(fd_pubkey_t) )!=0 )
554 0 : uniq_cnt++;
555 : /* uniq_cnt-1 is index in uniq set */
556 0 : sched_mapped[tmp[i].sched_pos] = (uint)(uniq_cnt-1UL);
557 0 : }
558 :
559 : /* Reconstruct contiguous uniq[] for hashing */
560 0 : fd_pubkey_t *uniq = fd_spad_alloc( runner->spad,
561 0 : alignof(fd_pubkey_t),
562 0 : uniq_cnt*sizeof(fd_pubkey_t) );
563 0 : {
564 0 : ulong write_pos = 0UL;
565 0 : for( ulong i=0UL; i<gather_cnt; i++ ) {
566 0 : if( i==0UL || memcmp( &tmp[i].pk, &tmp[i-1].pk, sizeof(fd_pubkey_t) )!=0 )
567 0 : fd_memcpy( &uniq[write_pos++], &tmp[i].pk, sizeof(fd_pubkey_t) );
568 0 : }
569 0 : }
570 :
571 : /* Hash sorted unique pubkeys */
572 0 : ulong h1 = fd_hash( seed, uniq, uniq_cnt * sizeof(fd_pubkey_t) );
573 0 : fd_memcpy( out, &h1, sizeof(ulong) );
574 :
575 : /* Hash mapped indices */
576 0 : ulong h2 = fd_hash( seed, sched_mapped, leaders->sched_cnt * sizeof(uint) );
577 0 : fd_memcpy( out + sizeof(ulong), &h2, sizeof(ulong) );
578 :
579 0 : return uniq_cnt;
580 0 : }
581 :
582 : static void
583 : fd_runtime_fuzz_build_leader_schedule_effects( fd_solfuzz_runner_t * runner,
584 : fd_funk_txn_xid_t const * xid,
585 : fd_exec_test_block_effects_t * effects,
586 0 : fd_exec_test_block_context_t const * test_ctx ) {
587 : /* Read epoch schedule sysvar */
588 0 : fd_epoch_schedule_t es_;
589 0 : fd_epoch_schedule_t *sched = fd_sysvar_epoch_schedule_read( runner->accdb->funk, xid, &es_ );
590 0 : FD_TEST( sched!=NULL );
591 :
592 0 : ulong parent_slot = fd_bank_parent_slot_get( runner->bank );
593 :
594 : /* Epoch we will use for effects (Agave: parent slot's leader schedule epoch) */
595 0 : ulong agave_epoch = fd_slot_to_leader_schedule_epoch( sched, parent_slot );
596 0 : ulong agave_slot0 = fd_epoch_slot0( sched, agave_epoch );
597 0 : ulong slots_in_epoch = fd_epoch_slot_cnt( sched, agave_epoch );
598 :
599 : /* Temporary vote_states for building stake weights */
600 0 : fd_vote_states_t *tmp_vs = fd_vote_states_join(
601 0 : fd_vote_states_new(
602 0 : fd_spad_alloc( runner->spad,
603 0 : fd_vote_states_align(),
604 0 : fd_vote_states_footprint( FD_RUNTIME_MAX_VOTE_ACCOUNTS ) ),
605 0 : FD_RUNTIME_MAX_VOTE_ACCOUNTS,
606 0 : 999UL /* stake_delegations_max */ ) );
607 :
608 : /* Select stake source based on the Agave-consistent epoch */
609 : /* Agave code pointers: see stake source selection in execute_block() at
610 : solfuzz-agave/src/block.rs (build_*_stake_delegations and epoch_stakes inserts) */
611 0 : if ( agave_epoch==fd_slot_to_leader_schedule_epoch( sched, parent_slot ) ) {
612 : /* Same as parent epoch, so use vote_accounts_t_1 */
613 0 : fd_runtime_fuzz_block_update_prev_epoch_votes_cache(
614 0 : tmp_vs,
615 0 : test_ctx->epoch_ctx.vote_accounts_t_1,
616 0 : test_ctx->epoch_ctx.vote_accounts_t_1_count,
617 0 : runner->spad );
618 0 : } else if ( agave_epoch==fd_slot_to_leader_schedule_epoch( sched, parent_slot )-1UL ) {
619 : /* One before parent epoch, so use vote_accounts_t_2 */
620 0 : fd_runtime_fuzz_block_update_prev_epoch_votes_cache(
621 0 : tmp_vs,
622 0 : test_ctx->epoch_ctx.vote_accounts_t_2,
623 0 : test_ctx->epoch_ctx.vote_accounts_t_2_count,
624 0 : runner->spad );
625 0 : } else if (agave_epoch==fd_slot_to_leader_schedule_epoch(sched, parent_slot)+1UL) {
626 : /* One ahead of parent epoch, so use current acct_states */
627 0 : for ( ushort i=0; i<test_ctx->acct_states_count; i++ ) {
628 0 : fd_txn_account_t acc[1];
629 0 : fd_runtime_fuzz_load_account( acc, runner->accdb->funk, xid, &test_ctx->acct_states[i], 1 );
630 0 : fd_pubkey_t pubkey;
631 0 : memcpy( &pubkey, test_ctx->acct_states[i].address, sizeof(fd_pubkey_t) );
632 0 : fd_runtime_fuzz_block_register_vote_account( runner->accdb->funk, xid, tmp_vs, &pubkey, runner->spad );
633 0 : }
634 0 : fd_stake_delegations_t * stake_delegations =
635 0 : fd_stake_delegations_join(
636 0 : fd_stake_delegations_new(
637 0 : fd_spad_alloc( runner->spad,
638 0 : fd_stake_delegations_align(),
639 0 : fd_stake_delegations_footprint( FD_RUNTIME_MAX_STAKE_ACCOUNTS ) ),
640 0 : FD_RUNTIME_MAX_STAKE_ACCOUNTS, 0 ) );
641 0 : for ( ushort i=0; i<test_ctx->acct_states_count; i++ ) {
642 0 : fd_pubkey_t pubkey;
643 0 : memcpy( &pubkey, test_ctx->acct_states[i].address, sizeof(fd_pubkey_t) );
644 0 : fd_runtime_fuzz_block_register_stake_delegation( runner->accdb->funk, xid, stake_delegations, &pubkey );
645 0 : }
646 0 : fd_runtime_fuzz_block_refresh_vote_accounts( tmp_vs, tmp_vs, stake_delegations, fd_bank_epoch_get( runner->bank ) );
647 0 : }
648 :
649 : /* Build weights from the selected stake source */
650 0 : ulong vote_acc_cnt = fd_vote_states_cnt( tmp_vs );
651 0 : fd_vote_stake_weight_t *weights =
652 0 : fd_spad_alloc( runner->spad, alignof(fd_vote_stake_weight_t),
653 0 : vote_acc_cnt * sizeof(fd_vote_stake_weight_t) );
654 0 : ulong weight_cnt = fd_stake_weights_by_node( tmp_vs, weights );
655 :
656 : /* Build ephemeral leader schedule for the Agave epoch */
657 0 : ulong fp = fd_epoch_leaders_footprint( weight_cnt, slots_in_epoch );
658 0 : FD_TEST( fp!=0UL );
659 0 : void *mem = fd_spad_alloc( runner->spad, fd_epoch_leaders_align(), fp );
660 0 : ulong vote_keyed = (ulong)fd_runtime_should_use_vote_keyed_leader_schedule( runner->bank );
661 0 : fd_epoch_leaders_t *effects_leaders = fd_epoch_leaders_join(
662 0 : fd_epoch_leaders_new( mem,
663 0 : agave_epoch,
664 0 : agave_slot0,
665 0 : slots_in_epoch,
666 0 : weight_cnt,
667 0 : weights,
668 0 : 0UL,
669 0 : vote_keyed ) );
670 0 : FD_TEST( effects_leaders!=NULL );
671 :
672 : /* Fill out effects struct from the Agave epoch info */
673 0 : effects->has_leader_schedule = 1;
674 0 : effects->leader_schedule.leaders_epoch = agave_epoch;
675 0 : effects->leader_schedule.leaders_slot0 = agave_slot0;
676 0 : effects->leader_schedule.leaders_slot_cnt = slots_in_epoch;
677 0 : effects->leader_schedule.leaders_sched_cnt = slots_in_epoch;
678 0 : effects->leader_schedule.leader_pub_cnt =
679 0 : fd_hash_epoch_leaders( runner, effects_leaders,
680 0 : LEADER_SCHEDULE_HASH_SEED,
681 0 : effects->leader_schedule.leader_schedule_hash );
682 0 : }
683 :
684 : ulong
685 : fd_solfuzz_block_run( fd_solfuzz_runner_t * runner,
686 : void const * input_,
687 : void ** output_,
688 : void * output_buf,
689 0 : ulong output_bufsz ) {
690 0 : fd_exec_test_block_context_t const * input = fd_type_pun_const( input_ );
691 0 : fd_exec_test_block_effects_t ** output = fd_type_pun( output_ );
692 :
693 0 : FD_SPAD_FRAME_BEGIN( runner->spad ) {
694 : /* Set up the block execution context */
695 0 : fd_runtime_stack_t * runtime_stack = fd_spad_alloc( runner->spad, alignof(fd_runtime_stack_t), sizeof(fd_runtime_stack_t) );
696 0 : ulong txn_cnt;
697 0 : fd_txn_p_t * txn_ptrs = fd_runtime_fuzz_block_ctx_create( runner, runtime_stack, input, &txn_cnt );
698 0 : if( txn_ptrs==NULL ) {
699 0 : fd_runtime_fuzz_block_ctx_destroy( runner );
700 0 : return 0;
701 0 : }
702 :
703 0 : fd_funk_txn_xid_t xid = { .ul = { fd_bank_slot_get( runner->bank ), 0UL } };
704 :
705 : /* Execute the constructed block against the runtime. */
706 0 : int res = fd_runtime_fuzz_block_ctx_exec( runner, runtime_stack, &xid, txn_ptrs, txn_cnt );
707 :
708 : /* Start saving block exec results */
709 0 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
710 0 : ulong output_end = (ulong)output_buf + output_bufsz;
711 :
712 0 : fd_exec_test_block_effects_t * effects =
713 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_block_effects_t),
714 0 : sizeof(fd_exec_test_block_effects_t) );
715 0 : if( FD_UNLIKELY( _l > output_end ) ) {
716 0 : abort();
717 0 : }
718 0 : fd_memset( effects, 0, sizeof(fd_exec_test_block_effects_t) );
719 :
720 : /* Capture error status */
721 0 : effects->has_error = !!( res );
722 :
723 : /* Capture capitalization */
724 0 : effects->slot_capitalization = fd_bank_capitalization_get( runner->bank );
725 :
726 : /* Capture hashes */
727 0 : fd_hash_t bank_hash = fd_bank_bank_hash_get( runner->bank );
728 0 : fd_memcpy( effects->bank_hash, bank_hash.hash, sizeof(fd_hash_t) );
729 :
730 : /* Capture cost tracker */
731 0 : fd_cost_tracker_t const * cost_tracker = fd_bank_cost_tracker_locking_query( runner->bank );
732 0 : effects->has_cost_tracker = 1;
733 0 : effects->cost_tracker = (fd_exec_test_cost_tracker_t) {
734 0 : .block_cost = cost_tracker ? cost_tracker->block_cost : 0UL,
735 0 : .vote_cost = cost_tracker ? cost_tracker->vote_cost : 0UL,
736 0 : };
737 0 : fd_bank_cost_tracker_end_locking_query( runner->bank );
738 :
739 : /* Effects: build T-epoch (bank epoch), T-stakes ephemeral leaders and report */
740 0 : fd_runtime_fuzz_build_leader_schedule_effects( runner, &xid, effects, input );
741 :
742 0 : ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
743 0 : fd_runtime_fuzz_block_ctx_destroy( runner );
744 :
745 0 : *output = effects;
746 0 : return actual_end - (ulong)output_buf;
747 0 : } FD_SPAD_FRAME_END;
748 0 : }
|