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