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