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

Generated by: LCOV version 1.14