LCOV - code coverage report
Current view: top level - flamenco/runtime/tests - fd_block_harness.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 312 352 88.6 %
Date: 2026-03-19 18:19:27 Functions: 9 9 100.0 %

          Line data    Source code
       1             : #include "fd_solfuzz_private.h"
       2             : #include "../fd_cost_tracker.h"
       3             : #include "fd_txn_harness.h"
       4             : #include "../fd_runtime.h"
       5             : #include "../fd_system_ids.h"
       6             : #include "../fd_runtime_stack.h"
       7             : #include "../../stakes/fd_stakes.h"
       8             : #include "../program/vote/fd_vote_state_versioned.h"
       9             : #include "../sysvar/fd_sysvar_epoch_schedule.h"
      10             : #include "../sysvar/fd_sysvar_rent.h"
      11             : #include "../sysvar/fd_sysvar_recent_hashes.h"
      12             : #include "../../accdb/fd_accdb_admin_v1.h"
      13             : #include "../../accdb/fd_accdb_impl_v1.h"
      14             : #include "../../accdb/fd_accdb_sync.h"
      15             : #include "../../progcache/fd_progcache_admin.h"
      16             : #include "../../log_collector/fd_log_collector.h"
      17             : #include "../../rewards/fd_rewards.h"
      18             : #include "../../types/fd_types.h"
      19             : #include "generated/block.pb.h"
      20             : #include "../../capture/fd_capture_ctx.h"
      21             : #include "../../capture/fd_solcap_writer.h"
      22             : 
      23             : /* Templatized leader schedule sort helper functions */
      24             : typedef struct {
      25             :   fd_pubkey_t pk;
      26             :   ulong       sched_pos; /* track original position in sched[] */
      27             : } pk_with_pos_t;
      28             : 
      29             : #define SORT_NAME        sort_pkpos
      30       18538 : #define SORT_KEY_T       pk_with_pos_t
      31        9269 : #define SORT_BEFORE(a,b) (memcmp(&(a).pk, &(b).pk, sizeof(fd_pubkey_t))<0)
      32             : #include "../../../util/tmpl/fd_sort.c"  /* generates templatized sort_pkpos_*() APIs */
      33             : 
      34             : /* Fixed leader schedule hash seed (consistent with solfuzz-agave) */
      35         299 : #define LEADER_SCHEDULE_HASH_SEED 0xDEADFACEUL
      36             : 
      37             : /* Registers a single vote account into the current votes cache.  The
      38             :    entry is derived from the current present account state.  This
      39             :    function also registers a vote timestamp for the vote account. */
      40             : static void
      41             : fd_solfuzz_block_register_vote_account( fd_top_votes_t *          top_votes,
      42             :                                         fd_accdb_user_t *         accdb,
      43             :                                         fd_funk_txn_xid_t const * xid,
      44        6863 :                                         fd_pubkey_t *             pubkey ) {
      45        6863 :   fd_accdb_ro_t ro[1];
      46        6863 :   if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, ro, xid, pubkey ) ) ) return;
      47             : 
      48        6863 :   if( !fd_pubkey_eq( fd_accdb_ref_owner( ro ), &fd_solana_vote_program_id ) ||
      49        6863 :       fd_accdb_ref_lamports( ro )==0UL ||
      50        6863 :       !fd_vsv_is_correct_size_and_initialized( ro->meta ) ) {
      51        6564 :     fd_accdb_close_ro( accdb, ro );
      52        6564 :     return;
      53        6564 :   }
      54             : 
      55         299 :   fd_vote_block_timestamp_t vote_block_timestamp = fd_vsv_get_vote_block_timestamp( fd_account_data( ro->meta ), ro->meta->dlen );
      56         299 :   fd_top_votes_update( top_votes, pubkey, vote_block_timestamp.slot, vote_block_timestamp.timestamp );
      57             : 
      58         299 :   fd_accdb_close_ro( accdb, ro );
      59         299 : }
      60             : 
      61             : static void
      62             : fd_solfuzz_block_update_prev_epoch_stakes( fd_top_votes_t *                   top_votes,
      63             :                                            fd_vote_stakes_t *                 vote_stakes,
      64             :                                            fd_exec_test_prev_vote_account_t * vote_accounts,
      65             :                                            pb_size_t                          vote_accounts_cnt,
      66         598 :                                            uchar                              is_t_1 ) {
      67         598 :   if( FD_UNLIKELY( !vote_accounts ) ) return;
      68             : 
      69        1196 :   for( uint i=0U; i<vote_accounts_cnt; i++ ) {
      70         598 :     fd_pubkey_t vote_pubkey = FD_LOAD( fd_pubkey_t, &vote_accounts[i].address );
      71         598 :     fd_pubkey_t node_pubkey = FD_LOAD( fd_pubkey_t, &vote_accounts[i].node_pubkey );
      72         598 :     ulong       stake       = vote_accounts[i].stake;
      73             :     /* TODO: uchar commission = (uchar)vote_accounts[i].commission; */
      74             : 
      75         598 :     if( is_t_1 ) {
      76         299 :       fd_vote_stakes_root_insert_key( vote_stakes, &vote_pubkey, &node_pubkey, stake, 0 );
      77         299 :     } else {
      78         299 :       fd_vote_stakes_root_update_meta( vote_stakes, &vote_pubkey, &node_pubkey, stake, 0 );
      79         299 :       fd_top_votes_insert( top_votes, &vote_pubkey, &node_pubkey, stake, 0, 0 );
      80         299 :     }
      81         598 :   }
      82         598 : }
      83             : 
      84             : /* Stores an entry in the stake delegations cache for the given vote
      85             :    account.  Deserializes and uses the present account state to derive
      86             :    delegation information. */
      87             : static void
      88             : fd_solfuzz_block_register_stake_delegation( fd_accdb_user_t *         accdb,
      89             :                                             fd_funk_txn_xid_t const * xid,
      90             :                                             fd_stake_delegations_t *  stake_delegations,
      91        6862 :                                             fd_pubkey_t *             pubkey ) {
      92        6862 :   fd_accdb_ro_t ro[1];
      93        6862 :   if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, ro, xid, pubkey ) ) ) return;
      94             : 
      95        6862 :   fd_stake_state_v2_t stake_state;
      96        6862 :   if( !fd_pubkey_eq( fd_accdb_ref_owner( ro ), &fd_solana_stake_program_id ) ||
      97        6862 :       fd_accdb_ref_lamports( ro )==0UL ||
      98        6862 :       0!=fd_stakes_get_state( ro->meta, &stake_state ) ||
      99        6862 :       !fd_stake_state_v2_is_stake( &stake_state ) ||
     100        6862 :       stake_state.inner.stake.stake.delegation.stake==0UL ) {
     101        6560 :     fd_accdb_close_ro( accdb, ro );
     102        6560 :     return;
     103        6560 :   }
     104             : 
     105         302 :   fd_stake_delegations_root_update(
     106         302 :       stake_delegations,
     107         302 :       pubkey,
     108         302 :       &stake_state.inner.stake.stake.delegation.voter_pubkey,
     109         302 :       stake_state.inner.stake.stake.delegation.stake,
     110         302 :       stake_state.inner.stake.stake.delegation.activation_epoch,
     111         302 :       stake_state.inner.stake.stake.delegation.deactivation_epoch,
     112         302 :       stake_state.inner.stake.stake.credits_observed,
     113         302 :       stake_state.inner.stake.stake.delegation.warmup_cooldown_rate );
     114         302 :   fd_accdb_close_ro( accdb, ro );
     115         302 : }
     116             : 
     117             : static void
     118         299 : fd_solfuzz_pb_block_ctx_destroy( fd_solfuzz_runner_t * runner ) {
     119             :   /* Release the stake delegations fork allocated in ctx_create */
     120         299 :   if( runner->bank->data->stake_delegations_fork_id!=USHORT_MAX ) {
     121         299 :     fd_stake_delegations_t * sd = fd_stake_delegations_join( fd_banks_get_stake_delegations( runner->banks->data ) );
     122         299 :     fd_stake_delegations_evict_fork( sd, runner->bank->data->stake_delegations_fork_id );
     123         299 :     runner->bank->data->stake_delegations_fork_id = USHORT_MAX;
     124         299 :   }
     125             : 
     126         299 :   fd_accdb_v1_clear( runner->accdb_admin );
     127         299 :   fd_progcache_clear( runner->progcache->join );
     128             : 
     129             :   /* In order to check for leaks in the workspace, we need to compact the
     130             :      allocators. Without doing this, empty superblocks may be retained
     131             :      by the fd_alloc instance, which mean we cannot check for leaks. */
     132         299 :   fd_alloc_compact( fd_accdb_user_v1_funk( runner->accdb )->alloc );
     133         299 :   fd_alloc_compact( runner->progcache->join->alloc );
     134         299 : }
     135             : 
     136             : /* Sets up block execution context from an input test case to execute
     137             :    against the runtime.  Returns block_info on success and NULL on
     138             :    failure. */
     139             : static fd_txn_p_t *
     140             : fd_solfuzz_pb_block_ctx_create( fd_solfuzz_runner_t *                runner,
     141             :                                 fd_exec_test_block_context_t const * test_ctx,
     142             :                                 ulong *                              out_txn_cnt,
     143         298 :                                 fd_hash_t *                          poh ) {
     144         298 :   fd_accdb_user_t * accdb = runner->accdb;
     145         298 :   fd_bank_t *       bank  = runner->bank;
     146         298 :   fd_banks_t *      banks = runner->banks;
     147             : 
     148         298 :   fd_runtime_stack_t * runtime_stack = runner->runtime_stack;
     149             : 
     150             :   /* Must match fd_banks_footprint max_vote_accounts (2048) to avoid buffer overrun
     151             :      when fd_vote_stakes_new reinitializes and epoch boundary inserts from vote_ele_map */
     152         298 :   fd_banks_clear_bank( banks, bank, 2048UL );
     153             : 
     154             :   /* Generate unique ID for funk txn */
     155         298 :   fd_funk_txn_xid_t xid[1] = {{ .ul={ 0UL, 0UL } }};
     156             : 
     157             :   /* Create temporary funk transaction and slot / epoch contexts */
     158         298 :   fd_funk_txn_xid_t parent_xid; fd_funk_txn_xid_set_root( &parent_xid );
     159         298 :   fd_accdb_attach_child( runner->accdb_admin, &parent_xid, xid );
     160         298 :   fd_progcache_txn_attach_child( runner->progcache->join, &parent_xid, xid );
     161             : 
     162             :   /* Initialize bank from input block bank */
     163         298 :   FD_TEST( test_ctx->has_bank );
     164         298 :   fd_exec_test_block_bank_t const * block_bank = &test_ctx->bank;
     165             : 
     166             :   /* Slot */
     167         298 :   ulong slot = block_bank->slot;
     168         298 :   fd_bank_slot_set( bank, slot );
     169             : 
     170             :   /* Blockhash queue */
     171         298 :   fd_solfuzz_pb_restore_blockhash_queue( bank, block_bank->blockhash_queue, block_bank->blockhash_queue_count );
     172             : 
     173             :   /* RBH lamports per signature. In the Agave harness this is set inside
     174             :      the fee rate governor itself. */
     175         298 :   fd_bank_rbh_lamports_per_sig_set( runner->bank, block_bank->rbh_lamports_per_signature );
     176             : 
     177             :   /* Fee rate governor */
     178         298 :   FD_TEST( block_bank->has_fee_rate_governor );
     179         298 :   fd_solfuzz_pb_restore_fee_rate_governor( bank, &block_bank->fee_rate_governor );
     180             : 
     181             :   /* Parent slot */
     182         298 :   ulong parent_slot = block_bank->parent_slot;
     183         298 :   fd_bank_parent_slot_set( bank, parent_slot );
     184             : 
     185             :   /* Capitalization */
     186         298 :   fd_bank_capitalization_set( bank, block_bank->capitalization );
     187             : 
     188             :   /* Inflation */
     189         298 :   FD_TEST( block_bank->has_inflation );
     190         298 :   fd_inflation_t inflation = {
     191         298 :     .initial         = block_bank->inflation.initial,
     192         298 :     .terminal        = block_bank->inflation.terminal,
     193         298 :     .taper           = block_bank->inflation.taper,
     194         298 :     .foundation      = block_bank->inflation.foundation,
     195         298 :     .foundation_term = block_bank->inflation.foundation_term,
     196         298 :   };
     197         298 :   fd_bank_inflation_set( bank, inflation );
     198             : 
     199             :   /* Block height */
     200         298 :   fd_bank_block_height_set( bank, block_bank->block_height );
     201             : 
     202             :   /* POH (set right before finalize since we don't fuzz POH calculation) */
     203         298 :   fd_memcpy( poh, block_bank->poh, sizeof(fd_hash_t) );
     204             : 
     205             :   /* Bank hash (parent bank hash because current bank hash gets computed
     206             :      after the block executes) */
     207         298 :   fd_hash_t * bank_hash = fd_bank_bank_hash_modify( bank );
     208         298 :   fd_memcpy( bank_hash, block_bank->parent_bank_hash, sizeof(fd_hash_t) );
     209             : 
     210             :   /* Parent signature count */
     211         298 :   fd_bank_parent_signature_cnt_set( bank, block_bank->parent_signature_count );
     212             : 
     213             :   /* Epoch schedule */
     214         298 :   FD_TEST( block_bank->has_epoch_schedule );
     215         298 :   fd_solfuzz_pb_restore_epoch_schedule( bank, &block_bank->epoch_schedule );
     216             : 
     217             :   /* Rent */
     218         298 :   FD_TEST( block_bank->has_rent );
     219         298 :   fd_solfuzz_pb_restore_rent( bank, &block_bank->rent );
     220             : 
     221             :   /* Feature set */
     222         298 :   FD_TEST( block_bank->has_features );
     223         298 :   fd_exec_test_feature_set_t const * feature_set = &block_bank->features;
     224         298 :   fd_features_t * features_bm = fd_bank_features_modify( bank );
     225         298 :   FD_TEST( fd_solfuzz_pb_restore_features( features_bm, feature_set ) );
     226             : 
     227             :   /* Total epoch stake (derived from T-1 vote accounts) */
     228         298 :   ulong total_epoch_stake = 0UL;
     229         597 :   for( uint i=0U; i<block_bank->vote_accounts_t_1_count; i++ ) {
     230         299 :     total_epoch_stake += block_bank->vote_accounts_t_1[i].stake;
     231         299 :   }
     232         298 :   fd_bank_total_epoch_stake_set( bank, total_epoch_stake );
     233             : 
     234             :   /* Using default configuration of 64 ticks per slot
     235             :      https://github.com/anza-xyz/solana-sdk/blob/time-utils%40v3.0.0/time-utils/src/lib.rs#L18-L27 */
     236         298 :   uint128 ns_per_slot = FD_LOAD(uint128, block_bank->ns_per_slot );
     237         298 :   fd_bank_ns_per_slot_set( bank, (fd_w_u128_t){ .ud = ns_per_slot } );
     238         298 :   fd_bank_ticks_per_slot_set( bank, 64UL );
     239         298 :   fd_bank_slots_per_year_set( runner->bank, (double)SECONDS_PER_YEAR * 1e9 / (double)ns_per_slot );
     240         298 :   fd_bank_hashes_per_tick_set( bank, (slot+1UL)*64UL );
     241             : 
     242             :   /* Load in accounts, populate stake delegations and vote accounts */
     243         298 :   fd_stake_delegations_t * stake_delegations = fd_banks_stake_delegations_root_query( banks );
     244         298 :   fd_stake_delegations_init( stake_delegations );
     245             : 
     246         298 :   bank->data->stake_delegations_fork_id = fd_stake_delegations_new_fork( stake_delegations );
     247             : 
     248         298 :   fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes_locking_modify( bank );
     249         298 :   bank->data->vote_stakes_fork_id = fd_vote_stakes_get_root_idx( vote_stakes );
     250             : 
     251         298 :   fd_top_votes_t * top_votes = fd_bank_top_votes_modify( bank );
     252         298 :   fd_top_votes_init( top_votes );
     253             : 
     254             :   /* Cap number of vote accounts at FD_RUNTIME_EXPECTED_VOTE_ACCOUNTS */
     255         298 :   FD_TEST( block_bank->vote_accounts_t_1_count<=FD_RUNTIME_EXPECTED_VOTE_ACCOUNTS );
     256         298 :   FD_TEST( block_bank->vote_accounts_t_2_count<=FD_RUNTIME_EXPECTED_VOTE_ACCOUNTS );
     257             : 
     258             :   /* Update vote cache for epoch T-1 */
     259         298 :   fd_solfuzz_block_update_prev_epoch_stakes(
     260         298 :       top_votes,
     261         298 :       vote_stakes,
     262         298 :       block_bank->vote_accounts_t_1,
     263         298 :       block_bank->vote_accounts_t_1_count,
     264         298 :       1
     265         298 :   );
     266             : 
     267             :   /* Update vote cache for epoch T-2 */
     268         298 :   fd_solfuzz_block_update_prev_epoch_stakes(
     269         298 :       top_votes,
     270         298 :       vote_stakes,
     271         298 :       block_bank->vote_accounts_t_2,
     272         298 :       block_bank->vote_accounts_t_2_count,
     273         298 :       0
     274         298 :   );
     275             : 
     276        7158 :   for( ushort i=0; i<test_ctx->acct_states_count; i++ ) {
     277        6860 :     fd_solfuzz_pb_load_account( runner->runtime, accdb, xid, &test_ctx->acct_states[i], i );
     278             : 
     279             :     /* Update vote accounts cache for epoch T */
     280        6860 :     fd_pubkey_t pubkey;
     281        6860 :     memcpy( &pubkey, test_ctx->acct_states[i].address, sizeof(fd_pubkey_t) );
     282        6860 :     fd_solfuzz_block_register_vote_account(
     283        6860 :         top_votes,
     284        6860 :         accdb,
     285        6860 :         xid,
     286        6860 :         &pubkey );
     287             : 
     288             :     /* Update the stake delegations cache for epoch T */
     289        6860 :     fd_solfuzz_block_register_stake_delegation( accdb, xid, stake_delegations, &pubkey );
     290        6860 :   }
     291             : 
     292             :   /* Current epoch gets updated in process_new_epoch, so use the epoch
     293             :      from the parent slot */
     294         298 :   fd_bank_epoch_set( bank, fd_slot_to_epoch( fd_bank_epoch_schedule_query( bank ), parent_slot, NULL ) );
     295             : 
     296             :   /* Finalize root fork.  Required before epoch boundary processing which
     297             :      may call fd_vote_stakes_advance_root.  See fd_vote_stakes.h. */
     298             : 
     299         298 :   ulong chain_cnt = fd_vote_rewards_map_chain_cnt_est( runtime_stack->expected_vote_accounts );
     300         298 :   FD_TEST( fd_vote_rewards_map_join( fd_vote_rewards_map_new( runtime_stack->stakes.vote_map_mem, chain_cnt, 999 ) ) );
     301             : 
     302             :   /* Populate vote_ele and vote_ele_map for partitioned epoch rewards.
     303             :      Use epoch_credits from the proto if available (captured at epoch
     304             :      boundary time), otherwise fall back to the vote account in funk. */
     305         298 :   fd_vote_rewards_map_t * vote_ele_map = fd_type_pun( runtime_stack->stakes.vote_map_mem );
     306         597 :   for( uint i=0U; i<block_bank->vote_accounts_t_1_count; i++ ) {
     307         299 :     fd_exec_test_prev_vote_account_t const * prev_vote_accs = &block_bank->vote_accounts_t_1[i];
     308         299 :     fd_pubkey_t                              vote_pubkey    = FD_LOAD( fd_pubkey_t, prev_vote_accs->address );
     309             : 
     310         299 :     fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[i];
     311         299 :     fd_memcpy( vote_ele->pubkey.uc, &vote_pubkey, sizeof(fd_pubkey_t) );
     312         299 :     vote_ele->commission = (uchar)prev_vote_accs->commission;
     313             : 
     314         299 :     FD_TEST( prev_vote_accs->epoch_credits_count<=FD_EPOCH_CREDITS_MAX );
     315         299 :     runtime_stack->stakes.epoch_credits[i].cnt = prev_vote_accs->epoch_credits_count;
     316        2130 :     for( ulong j=0UL; j<prev_vote_accs->epoch_credits_count; j++ ) {
     317        1831 :       runtime_stack->stakes.epoch_credits[i].epoch[j]        = (ushort)prev_vote_accs->epoch_credits[j].epoch;
     318        1831 :       runtime_stack->stakes.epoch_credits[i].credits[j]      = prev_vote_accs->epoch_credits[j].credits;
     319        1831 :       runtime_stack->stakes.epoch_credits[i].prev_credits[j] = prev_vote_accs->epoch_credits[j].prev_credits;
     320        1831 :     }
     321             : 
     322         299 :     fd_vote_rewards_map_idx_insert( vote_ele_map, i, runtime_stack->stakes.vote_ele );
     323         299 :   }
     324             : 
     325         298 :   fd_bank_vote_stakes_end_locking_modify( bank );
     326             : 
     327             :   /* Update leader schedule */
     328         298 :   fd_runtime_update_leaders( bank, runtime_stack );
     329             : 
     330             :   /* Make a new funk transaction since we're done loading in accounts for context */
     331         298 :   fd_funk_txn_xid_t fork_xid = { .ul = { slot, 0UL } };
     332         298 :   fd_accdb_attach_child        ( runner->accdb_admin,     xid, &fork_xid );
     333         298 :   fd_progcache_txn_attach_child( runner->progcache->join, xid, &fork_xid );
     334         298 :   xid[0] = fork_xid;
     335             : 
     336             :   /* Set the initial lthash from the input since we're in a new Funk txn */
     337         298 :   fd_lthash_value_t * lthash = fd_bank_lthash_locking_modify( bank );
     338         298 :   fd_memcpy( lthash, block_bank->parent_lt_hash, sizeof(fd_lthash_value_t) );
     339         298 :   fd_bank_lthash_end_locking_modify( bank );
     340             : 
     341             :   /* Restore sysvar cache */
     342         298 :   fd_sysvar_cache_restore_fuzz( bank, accdb, xid );
     343             : 
     344             :   /* Prepare raw transaction pointers and block / microblock infos */
     345         298 :   ulong        txn_cnt  = test_ctx->txns_count;
     346         298 :   fd_txn_p_t * txn_ptrs = fd_spad_alloc( runner->spad, alignof(fd_txn_p_t), txn_cnt * sizeof(fd_txn_p_t) );
     347         597 :   for( ulong i=0UL; i<txn_cnt; i++ ) {
     348         299 :     fd_txn_p_t * txn    = &txn_ptrs[i];
     349         299 :     ulong        msg_sz = fd_solfuzz_pb_txn_serialize( txn->payload, &test_ctx->txns[i] );
     350             : 
     351             :     // Reject any transactions over 1232 bytes
     352         299 :     if( FD_UNLIKELY( msg_sz==ULONG_MAX ) ) {
     353           0 :       return NULL;
     354           0 :     }
     355         299 :     txn->payload_sz = msg_sz;
     356             : 
     357             :     // Reject any transactions that cannot be parsed
     358         299 :     if( FD_UNLIKELY( !fd_txn_parse( txn->payload, msg_sz, TXN( txn ), NULL ) ) ) {
     359           0 :       return NULL;
     360           0 :     }
     361         299 :   }
     362             : 
     363         298 :   *out_txn_cnt = txn_cnt;
     364         298 :   return txn_ptrs;
     365         298 : }
     366             : 
     367             : /* Takes in a list of txn_p_t created from
     368             :    fd_runtime_fuzz_block_ctx_create and executes it against the runtime.
     369             :    Returns the execution result. */
     370             : static int
     371             : fd_solfuzz_block_ctx_exec( fd_solfuzz_runner_t * runner,
     372             :                            fd_txn_p_t *          txn_ptrs,
     373             :                            ulong                 txn_cnt,
     374         299 :                            fd_hash_t *           poh ) {
     375         299 :   int res = 0;
     376             : 
     377             :   // Prepare. Execute. Finalize.
     378         299 :   FD_SPAD_FRAME_BEGIN( runner->spad ) {
     379         299 :     fd_capture_ctx_t * capture_ctx = NULL;
     380             : 
     381         299 :     if( runner->solcap ) {
     382           0 :       void * capture_ctx_mem = fd_spad_alloc( runner->spad, fd_capture_ctx_align(), fd_capture_ctx_footprint() );
     383           0 :       capture_ctx = fd_capture_ctx_join( fd_capture_ctx_new( capture_ctx_mem ) );
     384           0 :       if( FD_UNLIKELY( !capture_ctx ) ) {
     385           0 :         FD_LOG_ERR(( "Failed to initialize capture_ctx" ));
     386           0 :       }
     387             : 
     388           0 :       fd_capture_link_file_t * capture_link_file =
     389           0 :         fd_spad_alloc( runner->spad, alignof(fd_capture_link_file_t), sizeof(fd_capture_link_file_t) );
     390           0 :       if( FD_UNLIKELY( !capture_link_file ) ) {
     391           0 :         FD_LOG_ERR(( "Failed to allocate capture_link_file" ));
     392           0 :       }
     393             : 
     394           0 :       capture_link_file->base.vt = &fd_capture_link_file_vt;
     395             : 
     396           0 :       int solcap_fd = (int)(ulong)runner->solcap_file;
     397           0 :       capture_link_file->fd          = solcap_fd;
     398           0 :       capture_ctx->capture_link      = &capture_link_file->base;
     399           0 :       capture_ctx->capctx_type.file  = capture_link_file;
     400           0 :       capture_ctx->solcap_start_slot = fd_bank_slot_get( runner->bank );
     401           0 :       capture_ctx->capture_solcap    = 1;
     402             : 
     403           0 :       fd_solcap_writer_init( capture_ctx->capture, solcap_fd );
     404           0 :     }
     405             : 
     406             :     /* TODO: Make sure this is able to work with booting up inside
     407             :        the partitioned epoch rewards distribution phase. */
     408         299 :     fd_funk_txn_xid_t xid = { .ul = { fd_bank_slot_get( runner->bank ), runner->bank->data->idx } };
     409         299 :     fd_rewards_recalculate_partitioned_rewards( runner->banks, runner->bank, runner->accdb, &xid, runner->runtime_stack, capture_ctx );
     410             : 
     411             :     /* Process new epoch may push a new spad frame onto the runtime spad. We should make sure this frame gets
     412             :        cleared (if it was allocated) before executing the block. */
     413         299 :     int is_epoch_boundary = 0;
     414         299 :     fd_runtime_block_execute_prepare( runner->banks, runner->bank, runner->accdb, runner->runtime_stack, capture_ctx, &is_epoch_boundary );
     415             : 
     416             :     /* Sequential transaction execution */
     417         598 :     for( ulong i=0UL; i<txn_cnt; i++ ) {
     418         299 :       fd_txn_p_t * txn = &txn_ptrs[i];
     419             : 
     420             :       /* Execute the transaction against the runtime */
     421         299 :       res = FD_RUNTIME_EXECUTE_SUCCESS;
     422         299 :       fd_txn_in_t  txn_in = { .txn = txn, .bundle.is_bundle = 0 };
     423         299 :       fd_txn_out_t txn_out;
     424         299 :       fd_runtime_t * runtime = runner->runtime;
     425         299 :       fd_log_collector_t log[1];
     426         299 :       runtime->log.log_collector = log;
     427         299 :       runtime->acc_pool = runner->acc_pool;
     428         299 :       fd_solfuzz_txn_ctx_exec( runner, runtime, &txn_in, &res, &txn_out );
     429         299 :       txn_out.err.exec_err = res;
     430             : 
     431         299 :       if( FD_UNLIKELY( !txn_out.err.is_committable ) ) {
     432           0 :         fd_runtime_cancel_txn( runtime, &txn_out );
     433           0 :         return 0;
     434           0 :       }
     435             : 
     436             :       /* Finalize the transaction */
     437         299 :       fd_runtime_commit_txn( runtime, runner->bank, &txn_out );
     438             : 
     439         299 :       if( FD_UNLIKELY( !txn_out.err.is_committable ) ) {
     440           0 :         return 0;
     441           0 :       }
     442             : 
     443         299 :     }
     444             : 
     445             :     /* At this point we want to set the poh.  This is what will get
     446             :        updated in the blockhash queue. */
     447         299 :     fd_bank_poh_set( runner->bank, *poh );
     448             :     /* Finalize the block */
     449         299 :     fd_runtime_block_execute_finalize( runner->bank, runner->accdb, capture_ctx );
     450         299 :   } FD_SPAD_FRAME_END;
     451             : 
     452         299 :   return 1;
     453         299 : }
     454             : 
     455             : /* Canonical (Agave-aligned) schedule hash
     456             :    Unique pubkeys referenced by sched, sorted deterministically
     457             :    Per-rotation indices mapped into sorted-uniq array */
     458             : ulong
     459             : fd_solfuzz_block_hash_epoch_leaders( fd_solfuzz_runner_t *      runner,
     460             :                                      fd_epoch_leaders_t const * leaders,
     461             :                                      ulong                      seed,
     462         299 :                                      uchar                      out[16] ) {
     463             :   /* Single contiguous spad allocation for uniq[] and sched_mapped[] */
     464         299 :   void *buf = fd_spad_alloc(
     465         299 :     runner->spad,
     466         299 :     alignof(pk_with_pos_t),
     467         299 :     leaders->sched_cnt*sizeof(pk_with_pos_t) +
     468         299 :     leaders->sched_cnt*sizeof(uint) );
     469             : 
     470         299 :   pk_with_pos_t * tmp          = (pk_with_pos_t *)buf;
     471         299 :   uint          * sched_mapped = (uint *)( tmp + leaders->sched_cnt );
     472             : 
     473             :   /* Gather all pubkeys and original positions from sched[] (skip invalid) */
     474         299 :   ulong gather_cnt = 0UL;
     475        9867 :   for( ulong i=0UL; i<leaders->sched_cnt; i++ ) {
     476        9568 :     uint idx = leaders->sched[i];
     477        9568 :     if( idx>=leaders->pub_cnt ) { /* invalid slot leader */
     478           0 :       sched_mapped[i] = 0U;       /* prefill invalid mapping */
     479           0 :       continue;
     480           0 :     }
     481        9568 :     fd_memcpy( &tmp[gather_cnt].pk, &leaders->pub[idx], sizeof(fd_pubkey_t) );
     482        9568 :     tmp[gather_cnt].sched_pos = i;
     483        9568 :     gather_cnt++;
     484        9568 :   }
     485             : 
     486         299 :   if( gather_cnt==0UL ) {
     487             :     /* No leaders => hash:=0, count:=0 */
     488           0 :     fd_memset( out, 0, sizeof(ulong)*2 );
     489           0 :     return 0UL;
     490           0 :   }
     491             : 
     492             :   /* Sort tmp[] by pubkey, note: comparator relies on first struct member */
     493         299 :   sort_pkpos_inplace( tmp, (ulong)gather_cnt );
     494             : 
     495             :   /* Dedupe and assign indices into sched_mapped[] during single pass */
     496         299 :   ulong uniq_cnt = 0UL;
     497        9867 :   for( ulong i=0UL; i<gather_cnt; i++ ) {
     498        9568 :     if( i==0UL || memcmp( &tmp[i].pk, &tmp[i-1].pk, sizeof(fd_pubkey_t) )!=0 )
     499         299 :       uniq_cnt++;
     500             :     /* uniq_cnt-1 is index in uniq set */
     501        9568 :     sched_mapped[tmp[i].sched_pos] = (uint)(uniq_cnt-1UL);
     502        9568 :   }
     503             : 
     504             :   /* Reconstruct contiguous uniq[] for hashing */
     505         299 :   fd_pubkey_t *uniq = fd_spad_alloc( runner->spad,
     506         299 :                                      alignof(fd_pubkey_t),
     507         299 :                                      uniq_cnt*sizeof(fd_pubkey_t) );
     508         299 :   {
     509         299 :     ulong write_pos = 0UL;
     510        9867 :     for( ulong i=0UL; i<gather_cnt; i++ ) {
     511        9568 :       if( i==0UL || memcmp( &tmp[i].pk, &tmp[i-1].pk, sizeof(fd_pubkey_t) )!=0 )
     512         299 :       fd_memcpy( &uniq[write_pos++], &tmp[i].pk, sizeof(fd_pubkey_t) );
     513        9568 :     }
     514         299 :   }
     515             : 
     516             :   /* Hash sorted unique pubkeys */
     517         299 :   ulong h1 = fd_hash( seed, uniq, uniq_cnt * sizeof(fd_pubkey_t) );
     518         299 :   fd_memcpy( out, &h1, sizeof(ulong) );
     519             : 
     520             :   /* Hash mapped indices */
     521         299 :   ulong h2 = fd_hash( seed, sched_mapped, leaders->sched_cnt * sizeof(uint) );
     522         299 :   fd_memcpy( out + sizeof(ulong), &h2, sizeof(ulong) );
     523             : 
     524         299 :   return uniq_cnt;
     525         299 : }
     526             : 
     527             : static void
     528             : fd_solfuzz_pb_build_leader_schedule_effects( fd_solfuzz_runner_t *          runner,
     529         299 :                                              fd_exec_test_block_effects_t * effects ) {
     530             :   /* Read epoch schedule sysvar */
     531         299 :   fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( runner->bank );
     532         299 :   FD_TEST( epoch_schedule );
     533             : 
     534             :   /* We will capture the leader schedule for the current epoch that we
     535             :      are in.  This will capture the leader schedule generated by an
     536             :      epoch boundary if one was crossed. */
     537         299 :   ulong epoch          = fd_bank_epoch_get( runner->bank );
     538         299 :   ulong ls_slot0       = fd_epoch_slot0( epoch_schedule, epoch );
     539         299 :   ulong slots_in_epoch = fd_epoch_slot_cnt( epoch_schedule, epoch );
     540             : 
     541         299 :   fd_epoch_leaders_t const * effects_leaders = fd_bank_epoch_leaders_query( runner->bank );
     542             : 
     543             :   /* Fill out effects struct from the Agave epoch info */
     544         299 :   effects->has_leader_schedule               = 1;
     545         299 :   effects->leader_schedule.leaders_epoch     = epoch;
     546         299 :   effects->leader_schedule.leaders_slot0     = ls_slot0;
     547         299 :   effects->leader_schedule.leaders_slot_cnt  = slots_in_epoch;
     548         299 :   effects->leader_schedule.leaders_sched_cnt = slots_in_epoch;
     549         299 :   effects->leader_schedule.leader_pub_cnt    = fd_solfuzz_block_hash_epoch_leaders(
     550         299 :       runner, effects_leaders,
     551         299 :       LEADER_SCHEDULE_HASH_SEED,
     552         299 :       effects->leader_schedule.leader_schedule_hash
     553         299 :   );
     554         299 : }
     555             : 
     556             : ulong
     557             : fd_solfuzz_pb_block_run( fd_solfuzz_runner_t * runner,
     558             :                           void const *         input_,
     559             :                           void **              output_,
     560             :                           void *               output_buf,
     561         298 :                           ulong                output_bufsz ) {
     562         298 :   fd_exec_test_block_context_t const * input  = fd_type_pun_const( input_ );
     563         298 :   fd_exec_test_block_effects_t **      output = fd_type_pun( output_ );
     564             : 
     565         298 :   FD_SPAD_FRAME_BEGIN( runner->spad ) {
     566         298 :     ulong txn_cnt;
     567         298 :     fd_hash_t poh = {0};
     568         298 :     fd_txn_p_t * txn_ptrs = fd_solfuzz_pb_block_ctx_create( runner, input, &txn_cnt, &poh );
     569         298 :     if( txn_ptrs==NULL ) {
     570           0 :       fd_solfuzz_pb_block_ctx_destroy( runner );
     571           0 :       return 0;
     572           0 :     }
     573             : 
     574             :     /* Execute the constructed block against the runtime. */
     575         298 :     int is_committable = fd_solfuzz_block_ctx_exec( runner, txn_ptrs, txn_cnt, &poh );
     576             : 
     577             :     /* Start saving block exec results */
     578         298 :     FD_SCRATCH_ALLOC_INIT( l, output_buf );
     579         298 :     ulong output_end = (ulong)output_buf + output_bufsz;
     580             : 
     581         298 :     fd_exec_test_block_effects_t * effects =
     582         298 :     FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_block_effects_t),
     583         298 :                                 sizeof(fd_exec_test_block_effects_t) );
     584         298 :     if( FD_UNLIKELY( _l > output_end ) ) {
     585           0 :       abort();
     586           0 :     }
     587         298 :     fd_memset( effects, 0, sizeof(fd_exec_test_block_effects_t) );
     588             : 
     589             :     /* Capture error status */
     590         298 :     effects->has_error = !is_committable;
     591             : 
     592             :     /* Capture capitalization */
     593 >1844*10^16 :     effects->slot_capitalization = !effects->has_error ? fd_bank_capitalization_get( runner->bank ) : 0UL;
     594             : 
     595             :     /* Capture hashes */
     596 >1844*10^16 :     fd_hash_t bank_hash = !effects->has_error ? fd_bank_bank_hash_get( runner->bank ) : (fd_hash_t){0};
     597         298 :     fd_memcpy( effects->bank_hash, bank_hash.hash, sizeof(fd_hash_t) );
     598             : 
     599             :     /* Capture cost tracker */
     600         298 :     fd_cost_tracker_t const * cost_tracker = fd_bank_cost_tracker_locking_query( runner->bank );
     601         298 :     effects->has_cost_tracker = 1;
     602         298 :     effects->cost_tracker = (fd_exec_test_cost_tracker_t) {
     603 >1844*10^16 :       .block_cost = cost_tracker ? cost_tracker->block_cost : 0UL,
     604 >1844*10^16 :       .vote_cost  = cost_tracker ? cost_tracker->vote_cost  : 0UL,
     605         298 :     };
     606         298 :     fd_bank_cost_tracker_end_locking_query( runner->bank );
     607             : 
     608             :     /* Effects: build T-epoch (bank epoch), T-stakes ephemeral leaders and report */
     609         298 :     fd_solfuzz_pb_build_leader_schedule_effects( runner, effects );
     610             : 
     611         298 :     ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
     612         298 :     fd_solfuzz_pb_block_ctx_destroy( runner );
     613             : 
     614         298 :     *output = effects;
     615         298 :     return actual_end - (ulong)output_buf;
     616         298 :   } FD_SPAD_FRAME_END;
     617           0 : }

Generated by: LCOV version 1.14