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 369 0.0 %
Date: 2026-09-01 04:28:17 Functions: 0 10 0.0 %

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

Generated by: LCOV version 1.14