LCOV - code coverage report
Current view: top level - flamenco/runtime/tests - fd_block_harness.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 460 0.0 %
Date: 2025-10-27 04:40:00 Functions: 0 10 0.0 %

          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 : }

Generated by: LCOV version 1.14