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 424 0.0 %
Date: 2026-01-13 05:01:51 Functions: 0 10 0.0 %

          Line data    Source code
       1             : #include "fd_solfuzz_private.h"
       2             : #include "../fd_cost_tracker.h"
       3             : #include "fd_txn_harness.h"
       4             : #include "../fd_runtime.h"
       5             : #include "../fd_system_ids.h"
       6             : #include "../fd_runtime_stack.h"
       7             : #include "../program/fd_stake_program.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_sync.h"
      13             : #include "../../log_collector/fd_log_collector.h"
      14             : #include "../../rewards/fd_rewards.h"
      15             : #include "../../types/fd_types.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             : /* Fixed leader schedule hash seed (consistent with solfuzz-agave) */
      32           0 : #define LEADER_SCHEDULE_HASH_SEED 0xDEADFACEUL
      33             : 
      34             : /* Stripped down version of fd_refresh_vote_accounts that simply
      35             :    refreshes the stake delegation amount for each of the vote accounts
      36             :    using the stake delegations cache. */
      37             : static void
      38             : fd_solfuzz_block_refresh_vote_accounts( fd_vote_states_t *       vote_states,
      39             :                                         fd_vote_states_t *       vote_states_prev,
      40             :                                         fd_vote_states_t *       vote_states_prev_prev,
      41             :                                         fd_stake_delegations_t * stake_delegations,
      42           0 :                                         ulong                    epoch ) {
      43           0 :   fd_stake_delegations_iter_t iter_[1];
      44           0 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
      45           0 :        !fd_stake_delegations_iter_done( iter );
      46           0 :        fd_stake_delegations_iter_next( iter ) ) {
      47           0 :     fd_stake_delegation_t * node = fd_stake_delegations_iter_ele( iter );
      48             : 
      49           0 :     fd_pubkey_t * voter_pubkey = &node->vote_account;
      50           0 :     ulong         stake        = node->stake;
      51             : 
      52             :     /* Find the voter in the vote accounts cache and update their
      53             :        delegation amount */
      54           0 :     fd_vote_state_ele_t * vote_state = fd_vote_states_query( vote_states, voter_pubkey );
      55           0 :     if( !vote_state ) continue;
      56             : 
      57           0 :     vote_state->stake     += stake;
      58           0 :     vote_state->stake_t_1 += stake;
      59           0 :     vote_state->stake_t_2 += stake;
      60           0 :   }
      61             : 
      62             :   /* We need to set the stake_t_2 for the vote accounts in the vote
      63             :      states cache.  An important edge case to handle is if the current
      64             :      epoch is less than 2, that means we should use the current stakes
      65             :      because the stake_t_2 field is not yet populated. */
      66           0 :   fd_vote_states_iter_t vs_iter_[1];
      67           0 :   for( fd_vote_states_iter_t * iter = fd_vote_states_iter_init( vs_iter_, vote_states_prev_prev );
      68           0 :        !fd_vote_states_iter_done( iter );
      69           0 :        fd_vote_states_iter_next( iter ) ) {
      70           0 :     fd_vote_state_ele_t * vote_state = fd_vote_states_iter_ele( iter );
      71           0 :     fd_vote_state_ele_t * vote_state_prev_prev = fd_vote_states_query( vote_states_prev_prev, &vote_state->vote_account );
      72           0 :     ulong t_2_stake = !!vote_state_prev_prev ? vote_state_prev_prev->stake : 0UL;
      73           0 :     vote_state->stake_t_2 = epoch>=2UL ? t_2_stake : vote_state->stake;
      74           0 :   }
      75             : 
      76             :   /* Set stake_t_1 for the vote accounts in the vote states cache. */
      77           0 :   for( fd_vote_states_iter_t * iter = fd_vote_states_iter_init( vs_iter_, vote_states_prev );
      78           0 :        !fd_vote_states_iter_done( iter );
      79           0 :        fd_vote_states_iter_next( iter ) ) {
      80           0 :     fd_vote_state_ele_t * vote_state = fd_vote_states_iter_ele( iter );
      81           0 :     fd_vote_state_ele_t * vote_state_prev = fd_vote_states_query( vote_states_prev, &vote_state->vote_account );
      82           0 :     ulong t_1_stake = !!vote_state_prev ? vote_state_prev->stake : 0UL;
      83           0 :     vote_state->stake_t_1 = epoch>=1UL ? t_1_stake : vote_state->stake;
      84           0 :   }
      85           0 : }
      86             : 
      87             : /* Registers a single vote account into the current votes cache.  The
      88             :    entry is derived from the current present account state.  This
      89             :    function also registers a vote timestamp for the vote account. */
      90             : static void
      91             : fd_solfuzz_block_register_vote_account( fd_accdb_user_t *         accdb,
      92             :                                         fd_funk_txn_xid_t const * xid,
      93             :                                         fd_vote_states_t *        vote_states,
      94           0 :                                         fd_pubkey_t *             pubkey ) {
      95           0 :   fd_accdb_ro_t ro[1];
      96           0 :   if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, ro, xid, pubkey ) ) ) return;
      97             : 
      98           0 :   if( !fd_pubkey_eq( fd_accdb_ref_owner( ro ), &fd_solana_vote_program_id ) ||
      99           0 :       fd_accdb_ref_lamports( ro )==0UL ||
     100           0 :       !fd_vsv_is_correct_size_and_initialized( ro->meta ) ) {
     101           0 :     fd_accdb_close_ro( accdb, ro );
     102           0 :     return;
     103           0 :   }
     104             : 
     105           0 :   fd_vote_states_update_from_account(
     106           0 :       vote_states,
     107           0 :       fd_accdb_ref_address( ro ),
     108           0 :       fd_accdb_ref_data_const( ro ),
     109           0 :       fd_accdb_ref_data_sz   ( ro ) );
     110           0 :   fd_accdb_close_ro( accdb, ro );
     111           0 : }
     112             : 
     113             : /* Stores an entry in the stake delegations cache for the given vote
     114             :    account.  Deserializes and uses the present account state to derive
     115             :    delegation information. */
     116             : static void
     117             : fd_solfuzz_block_register_stake_delegation( fd_accdb_user_t *         accdb,
     118             :                                             fd_funk_txn_xid_t const * xid,
     119             :                                             fd_stake_delegations_t *  stake_delegations,
     120           0 :                                             fd_pubkey_t *             pubkey ) {
     121           0 :   fd_accdb_ro_t ro[1];
     122           0 :   if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, ro, xid, pubkey ) ) ) return;
     123             : 
     124           0 :   fd_stake_state_v2_t stake_state;
     125           0 :   if( !fd_pubkey_eq( fd_accdb_ref_owner( ro ), &fd_solana_stake_program_id ) ||
     126           0 :       fd_accdb_ref_lamports( ro )==0UL ||
     127           0 :       0!=fd_stake_get_state( ro->meta, &stake_state ) ||
     128           0 :       !fd_stake_state_v2_is_stake( &stake_state ) ||
     129           0 :       stake_state.inner.stake.stake.delegation.stake==0UL ) {
     130           0 :     fd_accdb_close_ro( accdb, ro );
     131           0 :     return;
     132           0 :   }
     133             : 
     134           0 :   fd_stake_delegations_update(
     135           0 :       stake_delegations,
     136           0 :       pubkey,
     137           0 :       &stake_state.inner.stake.stake.delegation.voter_pubkey,
     138           0 :       stake_state.inner.stake.stake.delegation.stake,
     139           0 :       stake_state.inner.stake.stake.delegation.activation_epoch,
     140           0 :       stake_state.inner.stake.stake.delegation.deactivation_epoch,
     141           0 :       stake_state.inner.stake.stake.credits_observed,
     142           0 :       stake_state.inner.stake.stake.delegation.warmup_cooldown_rate );
     143           0 :   fd_accdb_close_ro( accdb, ro );
     144           0 : }
     145             : 
     146             : /* Common helper method for populating a previous epoch's vote cache. */
     147             : static void
     148             : fd_solfuzz_pb_block_update_prev_epoch_votes_cache( fd_vote_states_t *            vote_states,
     149             :                                                    fd_exec_test_vote_account_t * vote_accounts,
     150             :                                                    pb_size_t                     vote_accounts_cnt,
     151             :                                                    fd_runtime_stack_t *          runtime_stack,
     152             :                                                    fd_spad_t *                   spad,
     153           0 :                                                    uchar                         is_t_1 ) {
     154           0 :   FD_SPAD_FRAME_BEGIN( spad ) {
     155           0 :     for( uint i=0U; i<vote_accounts_cnt; i++ ) {
     156           0 :       fd_exec_test_acct_state_t * vote_account  = &vote_accounts[i].vote_account;
     157           0 :       ulong                       stake         = vote_accounts[i].stake;
     158           0 :       uchar *                     vote_data     = vote_account->data->bytes;
     159           0 :       ulong                       vote_data_len = vote_account->data->size;
     160           0 :       fd_pubkey_t                 vote_address  = {0};
     161           0 :       fd_memcpy( &vote_address, vote_account->address, sizeof(fd_pubkey_t) );
     162             : 
     163             :       /* Try decoding the vote state from the account data. If it isn't
     164             :          decodable, don't try inserting it into the cache. */
     165           0 :       fd_vote_state_versioned_t * res = fd_bincode_decode_spad(
     166           0 :           vote_state_versioned, spad,
     167           0 :           vote_data,
     168           0 :           vote_data_len,
     169           0 :           NULL );
     170           0 :       if( res==NULL ) continue;
     171           0 :       if( res->discriminant==fd_vote_state_versioned_enum_v0_23_5 ) continue;
     172             : 
     173           0 :       fd_vote_states_update_from_account( vote_states, &vote_address, vote_data, vote_data_len );
     174           0 :       fd_vote_state_ele_t * vote_state = fd_vote_states_query( vote_states, &vote_address );
     175           0 :       vote_state->stake     += stake;
     176           0 :       vote_state->stake_t_1 += stake;
     177           0 :       vote_state->stake_t_2 += stake;
     178             : 
     179           0 :       if( !is_t_1 ) continue;
     180             : 
     181             :       /* Update vote credits for T-1 */
     182           0 :       fd_vote_epoch_credits_t * epoch_credits = NULL;
     183           0 :       switch( res->discriminant ) {
     184           0 :         case fd_vote_state_versioned_enum_v0_23_5:
     185           0 :           epoch_credits = res->inner.v0_23_5.epoch_credits;
     186           0 :           break;
     187           0 :         case fd_vote_state_versioned_enum_v1_14_11:
     188           0 :           epoch_credits = res->inner.v1_14_11.epoch_credits;
     189           0 :           break;
     190           0 :         case fd_vote_state_versioned_enum_v3:
     191           0 :           epoch_credits = res->inner.v3.epoch_credits;
     192           0 :           break;
     193           0 :         default:
     194           0 :           __builtin_unreachable();
     195           0 :       }
     196             : 
     197           0 :       fd_vote_state_credits_t * vote_credits = &runtime_stack->stakes.vote_credits[ vote_state->idx ];
     198           0 :       vote_credits->credits_cnt = 0UL;
     199           0 :       for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( epoch_credits );
     200           0 :            !deq_fd_vote_epoch_credits_t_iter_done( epoch_credits, iter );
     201           0 :            iter = deq_fd_vote_epoch_credits_t_iter_next( epoch_credits, iter ) ) {
     202           0 :         fd_vote_epoch_credits_t const * credit_ele = deq_fd_vote_epoch_credits_t_iter_ele_const( epoch_credits, iter );
     203           0 :         vote_credits->epoch[ vote_credits->credits_cnt ]        = (ushort)credit_ele->epoch;
     204           0 :         vote_credits->credits[ vote_credits->credits_cnt ]      = credit_ele->credits;
     205           0 :         vote_credits->prev_credits[ vote_credits->credits_cnt ] = credit_ele->prev_credits;
     206           0 :         vote_credits->credits_cnt++;
     207           0 :       }
     208           0 :     }
     209           0 :   } FD_SPAD_FRAME_END;
     210           0 : }
     211             : 
     212             : static void
     213           0 : fd_solfuzz_pb_block_ctx_destroy( fd_solfuzz_runner_t * runner ) {
     214           0 :   fd_accdb_clear( runner->accdb_admin );
     215           0 :   fd_progcache_clear( runner->progcache_admin );
     216             : 
     217             :   /* In order to check for leaks in the workspace, we need to compact the
     218             :      allocators. Without doing this, empty superblocks may be retained
     219             :      by the fd_alloc instance, which mean we cannot check for leaks. */
     220           0 :   fd_alloc_compact( runner->accdb_admin->funk->alloc );
     221           0 :   fd_alloc_compact( runner->progcache_admin->funk->alloc );
     222           0 : }
     223             : 
     224             : /* Sets up block execution context from an input test case to execute
     225             :    against the runtime.  Returns block_info on success and NULL on
     226             :    failure. */
     227             : static fd_txn_p_t *
     228             : fd_solfuzz_pb_block_ctx_create( fd_solfuzz_runner_t *                runner,
     229             :                                 fd_exec_test_block_context_t const * test_ctx,
     230             :                                 ulong *                              out_txn_cnt,
     231           0 :                                 fd_hash_t *                          poh ) {
     232           0 :   fd_accdb_user_t * accdb = runner->accdb;
     233           0 :   fd_bank_t *       bank  = runner->bank;
     234           0 :   fd_banks_t *      banks = runner->banks;
     235             : 
     236           0 :   fd_runtime_stack_t * runtime_stack = runner->runtime_stack;
     237             : 
     238           0 :   fd_banks_clear_bank( banks, bank, FD_RUNTIME_MAX_VOTE_ACCOUNTS );
     239             : 
     240             :   /* Generate unique ID for funk txn */
     241           0 :   fd_funk_txn_xid_t xid[1] = {{ .ul={ LONG_MAX, LONG_MAX } }};
     242             : 
     243             :   /* Create temporary funk transaction and slot / epoch contexts */
     244           0 :   fd_funk_txn_xid_t parent_xid; fd_funk_txn_xid_set_root( &parent_xid );
     245           0 :   fd_accdb_attach_child( runner->accdb_admin, &parent_xid, xid );
     246           0 :   fd_progcache_txn_attach_child( runner->progcache_admin, &parent_xid, xid );
     247             : 
     248             :   /* Restore feature flags */
     249           0 :   fd_features_t features = {0};
     250           0 :   if( !fd_solfuzz_pb_restore_features( &features, &test_ctx->epoch_ctx.features ) ) {
     251           0 :     return NULL;
     252           0 :   }
     253           0 :   fd_bank_features_set( bank, features );
     254             : 
     255             :   /* Set up slot context */
     256           0 :   ulong slot        = test_ctx->slot_ctx.slot;
     257           0 :   ulong parent_slot = test_ctx->slot_ctx.prev_slot;
     258             : 
     259           0 :   fd_hash_t * bank_hash = fd_bank_bank_hash_modify( bank );
     260           0 :   fd_memcpy( bank_hash, test_ctx->slot_ctx.parent_bank_hash, sizeof(fd_hash_t) );
     261             : 
     262             :   /* All bank mgr stuff here. */
     263             : 
     264           0 :   fd_bank_slot_set( bank, slot );
     265             : 
     266           0 :   fd_bank_parent_slot_set( bank, parent_slot );
     267             : 
     268           0 :   fd_bank_block_height_set( bank, test_ctx->slot_ctx.block_height );
     269             : 
     270           0 :   fd_bank_capitalization_set( bank, test_ctx->slot_ctx.prev_epoch_capitalization );
     271             : 
     272             :   // self.max_tick_height = (self.slot + 1) * self.ticks_per_slot;
     273           0 :   fd_bank_hashes_per_tick_set( bank, test_ctx->epoch_ctx.hashes_per_tick );
     274             : 
     275           0 :   fd_bank_ticks_per_slot_set( bank, test_ctx->epoch_ctx.ticks_per_slot );
     276             : 
     277           0 :   fd_bank_ns_per_slot_set( bank, (fd_w_u128_t) { .ul={ 400000000,0 } } ); // TODO: restore from input
     278             : 
     279           0 :   fd_bank_genesis_creation_time_set( bank, test_ctx->epoch_ctx.genesis_creation_time );
     280             : 
     281           0 :   fd_bank_slots_per_year_set( bank, test_ctx->epoch_ctx.slots_per_year );
     282             : 
     283           0 :   fd_bank_parent_signature_cnt_set( bank, test_ctx->slot_ctx.parent_signature_count );
     284             : 
     285           0 :   fd_fee_rate_governor_t * fee_rate_governor = fd_bank_fee_rate_governor_modify( bank );
     286           0 :   *fee_rate_governor = (fd_fee_rate_governor_t){
     287           0 :     .target_lamports_per_signature = test_ctx->slot_ctx.fee_rate_governor.target_lamports_per_signature,
     288           0 :     .target_signatures_per_slot    = test_ctx->slot_ctx.fee_rate_governor.target_signatures_per_slot,
     289           0 :     .min_lamports_per_signature    = test_ctx->slot_ctx.fee_rate_governor.min_lamports_per_signature,
     290           0 :     .max_lamports_per_signature    = test_ctx->slot_ctx.fee_rate_governor.max_lamports_per_signature,
     291           0 :     .burn_percent                  = (uchar)test_ctx->slot_ctx.fee_rate_governor.burn_percent
     292           0 :   };
     293             :   /* https://github.com/firedancer-io/solfuzz-agave/blob/agave-v3.0.3/src/block.rs#L393-L396 */
     294           0 :   fd_bank_rbh_lamports_per_sig_set( bank, FD_RUNTIME_FEE_STRUCTURE_LAMPORTS_PER_SIGNATURE );
     295             : 
     296           0 :   fd_inflation_t * inflation = fd_bank_inflation_modify( bank );
     297           0 :   *inflation = (fd_inflation_t) {
     298           0 :     .initial         = test_ctx->epoch_ctx.inflation.initial,
     299           0 :     .terminal        = test_ctx->epoch_ctx.inflation.terminal,
     300           0 :     .taper           = test_ctx->epoch_ctx.inflation.taper,
     301           0 :     .foundation      = test_ctx->epoch_ctx.inflation.foundation,
     302           0 :     .foundation_term = test_ctx->epoch_ctx.inflation.foundation_term
     303           0 :   };
     304             : 
     305           0 :   fd_bank_block_height_set( bank, test_ctx->slot_ctx.block_height );
     306             : 
     307             :   /* Initialize the current running epoch stake and vote accounts */
     308             : 
     309           0 :   fd_stake_delegations_t * stake_delegations = fd_banks_stake_delegations_root_query( banks );
     310           0 :   fd_stake_delegations_init( stake_delegations );
     311             : 
     312             :   /* Load in all accounts with > 0 lamports provided in the context. The input expects unique account pubkeys. */
     313           0 :   fd_vote_states_t * vote_states = fd_bank_vote_states_locking_modify( bank );
     314           0 :   for( ushort i=0; i<test_ctx->acct_states_count; i++ ) {
     315           0 :     fd_solfuzz_pb_load_account( runner->runtime, accdb, xid, &test_ctx->acct_states[i], i );
     316             : 
     317             :     /* Update vote accounts cache for epoch T */
     318           0 :     fd_pubkey_t pubkey;
     319           0 :     memcpy( &pubkey, test_ctx->acct_states[i].address, sizeof(fd_pubkey_t) );
     320           0 :     fd_solfuzz_block_register_vote_account(
     321           0 :         accdb,
     322           0 :         xid,
     323           0 :         vote_states,
     324           0 :         &pubkey );
     325             : 
     326             :     /* Update the stake delegations cache for epoch T */
     327           0 :     fd_solfuzz_block_register_stake_delegation( accdb, xid, stake_delegations, &pubkey );
     328           0 :   }
     329             : 
     330             :   /* Zero out vote stakes to avoid leakage across tests */
     331           0 :   fd_vote_states_reset_stakes( vote_states );
     332             : 
     333             :   /* Finish init epoch bank sysvars */
     334           0 :   fd_epoch_schedule_t epoch_schedule_[1];
     335           0 :   fd_epoch_schedule_t * epoch_schedule = fd_sysvar_epoch_schedule_read( accdb, xid, epoch_schedule_ );
     336           0 :   FD_TEST( epoch_schedule );
     337           0 :   fd_bank_epoch_schedule_set( bank, *epoch_schedule );
     338             : 
     339           0 :   fd_rent_t rent[1];
     340           0 :   FD_TEST( fd_sysvar_rent_read( accdb, xid, rent ) );
     341           0 :   fd_bank_rent_set( bank, *rent );
     342             : 
     343             :   /* Current epoch gets updated in process_new_epoch, so use the epoch
     344             :      from the parent slot */
     345           0 :   fd_bank_epoch_set( bank, fd_slot_to_epoch( epoch_schedule, parent_slot, NULL ) );
     346             : 
     347             :   /* Update vote cache for epoch T-1 */
     348           0 :   fd_vote_states_t * vote_states_prev = fd_bank_vote_states_prev_modify( bank );
     349           0 :   fd_solfuzz_pb_block_update_prev_epoch_votes_cache(
     350           0 :       vote_states_prev,
     351           0 :       test_ctx->epoch_ctx.vote_accounts_t_1,
     352           0 :       test_ctx->epoch_ctx.vote_accounts_t_1_count,
     353           0 :       runtime_stack,
     354           0 :       runner->spad,
     355           0 :       1 );
     356             : 
     357             :   /* Update vote cache for epoch T-2 */
     358           0 :   fd_vote_states_t * vote_states_prev_prev = fd_bank_vote_states_prev_prev_modify( bank );
     359           0 :   fd_solfuzz_pb_block_update_prev_epoch_votes_cache(
     360           0 :       vote_states_prev_prev,
     361           0 :       test_ctx->epoch_ctx.vote_accounts_t_2,
     362           0 :       test_ctx->epoch_ctx.vote_accounts_t_2_count,
     363           0 :       runtime_stack,
     364           0 :       runner->spad,
     365           0 :       0 );
     366             : 
     367             :   /* Refresh vote accounts to calculate stake delegations */
     368           0 :   fd_solfuzz_block_refresh_vote_accounts(
     369           0 :     vote_states,
     370           0 :     vote_states_prev,
     371           0 :     vote_states_prev_prev,
     372           0 :     stake_delegations,
     373           0 :     fd_bank_epoch_get( bank ) );
     374           0 :   fd_bank_vote_states_end_locking_modify( bank );
     375             : 
     376             :   /* Update leader schedule */
     377           0 :   fd_runtime_update_leaders( bank, runtime_stack );
     378             : 
     379             :   /* Initialize the blockhash queue and recent blockhashes sysvar from the input blockhash queue */
     380           0 :   ulong blockhash_seed; FD_TEST( fd_rng_secure( &blockhash_seed, sizeof(ulong) ) );
     381           0 :   fd_blockhashes_init( fd_bank_block_hash_queue_modify( bank ), blockhash_seed );
     382             : 
     383             :   /* TODO: We might need to load this in from the input. We also need to
     384             :      size this out for worst case, but this also blows up the memory
     385             :      requirement. */
     386             :   /* Allocate all the memory for the rent fresh accounts list */
     387             : 
     388             :   // Set genesis hash to {0}
     389           0 :   fd_hash_t * genesis_hash = fd_bank_genesis_hash_modify( bank );
     390           0 :   fd_memset( genesis_hash->hash, 0, sizeof(fd_hash_t) );
     391             : 
     392             :   // Use the latest lamports per signature
     393           0 :   uchar __attribute__((aligned(FD_SYSVAR_RECENT_HASHES_ALIGN))) rbh_mem[FD_SYSVAR_RECENT_HASHES_FOOTPRINT];
     394           0 :   fd_recent_block_hashes_t const * rbh = fd_sysvar_recent_hashes_read( accdb, xid, rbh_mem );
     395           0 :   if( rbh && !deq_fd_block_block_hash_entry_t_empty( rbh->hashes ) ) {
     396           0 :     fd_block_block_hash_entry_t const * last = deq_fd_block_block_hash_entry_t_peek_head_const( rbh->hashes );
     397           0 :     if( last && last->fee_calculator.lamports_per_signature!=0UL ) {
     398           0 :       fd_bank_rbh_lamports_per_sig_set( bank, last->fee_calculator.lamports_per_signature );
     399           0 :     }
     400           0 :   }
     401             : 
     402             :   /* Make a new funk transaction since we're done loading in accounts for context */
     403           0 :   fd_funk_txn_xid_t fork_xid = { .ul = { slot, 0UL } };
     404           0 :   fd_accdb_attach_child        ( runner->accdb_admin,     xid, &fork_xid );
     405           0 :   fd_progcache_txn_attach_child( runner->progcache_admin, xid, &fork_xid );
     406           0 :   xid[0] = fork_xid;
     407             : 
     408             :   /* Set the initial lthash from the input since we're in a new Funk txn */
     409           0 :   fd_lthash_value_t * lthash = fd_bank_lthash_locking_modify( bank );
     410           0 :   fd_memcpy( lthash, test_ctx->slot_ctx.parent_lthash, sizeof(fd_lthash_value_t) );
     411           0 :   fd_bank_lthash_end_locking_modify( bank );
     412             : 
     413             :   // Populate blockhash queue and recent blockhashes sysvar
     414           0 :   for( ushort i=0; i<test_ctx->blockhash_queue_count; ++i ) {
     415           0 :     fd_hash_t hash;
     416           0 :     memcpy( &hash, test_ctx->blockhash_queue[i]->bytes, sizeof(fd_hash_t) );
     417           0 :     fd_bank_poh_set( bank, hash );
     418           0 :     fd_sysvar_recent_hashes_update( bank, accdb, xid, NULL ); /* appends an entry */
     419           0 :   }
     420             : 
     421             :   /* Set the poh from the input.  This is the blockhash that will get
     422             :      inserted after. */
     423           0 :   memcpy( poh, test_ctx->slot_ctx.poh, sizeof(fd_hash_t) );
     424             : 
     425             :   /* Restore sysvar cache */
     426           0 :   fd_sysvar_cache_restore_fuzz( bank, accdb, xid );
     427             : 
     428             :   /* Prepare raw transaction pointers and block / microblock infos */
     429           0 :   ulong        txn_cnt  = test_ctx->txns_count;
     430           0 :   fd_txn_p_t * txn_ptrs = fd_spad_alloc( runner->spad, alignof(fd_txn_p_t), txn_cnt * sizeof(fd_txn_p_t) );
     431           0 :   for( ulong i=0UL; i<txn_cnt; i++ ) {
     432           0 :     fd_txn_p_t * txn    = &txn_ptrs[i];
     433           0 :     ulong        msg_sz = fd_solfuzz_pb_txn_serialize( txn->payload, &test_ctx->txns[i] );
     434             : 
     435             :     // Reject any transactions over 1232 bytes
     436           0 :     if( FD_UNLIKELY( msg_sz==ULONG_MAX ) ) {
     437           0 :       return NULL;
     438           0 :     }
     439           0 :     txn->payload_sz = msg_sz;
     440             : 
     441             :     // Reject any transactions that cannot be parsed
     442           0 :     if( FD_UNLIKELY( !fd_txn_parse( txn->payload, msg_sz, TXN( txn ), NULL ) ) ) {
     443           0 :       return NULL;
     444           0 :     }
     445           0 :   }
     446             : 
     447           0 :   *out_txn_cnt = txn_cnt;
     448           0 :   return txn_ptrs;
     449           0 : }
     450             : 
     451             : /* Takes in a list of txn_p_t created from
     452             :    fd_runtime_fuzz_block_ctx_create and executes it against the runtime.
     453             :    Returns the execution result. */
     454             : static int
     455             : fd_solfuzz_block_ctx_exec( fd_solfuzz_runner_t * runner,
     456             :                            fd_txn_p_t *          txn_ptrs,
     457             :                            ulong                 txn_cnt,
     458           0 :                            fd_hash_t *           poh ) {
     459           0 :   int res = 0;
     460             : 
     461             :   // Prepare. Execute. Finalize.
     462           0 :   FD_SPAD_FRAME_BEGIN( runner->spad ) {
     463           0 :     fd_capture_ctx_t * capture_ctx = NULL;
     464             : 
     465           0 :     if( runner->solcap ) {
     466           0 :       void * capture_ctx_mem = fd_spad_alloc( runner->spad, fd_capture_ctx_align(), fd_capture_ctx_footprint() );
     467           0 :       capture_ctx = fd_capture_ctx_join( fd_capture_ctx_new( capture_ctx_mem ) );
     468           0 :       if( FD_UNLIKELY( !capture_ctx ) ) {
     469           0 :         FD_LOG_ERR(( "Failed to initialize capture_ctx" ));
     470           0 :       }
     471             : 
     472           0 :       fd_capture_link_file_t * capture_link_file =
     473           0 :         fd_spad_alloc( runner->spad, alignof(fd_capture_link_file_t), sizeof(fd_capture_link_file_t) );
     474           0 :       if( FD_UNLIKELY( !capture_link_file ) ) {
     475           0 :         FD_LOG_ERR(( "Failed to allocate capture_link_file" ));
     476           0 :       }
     477             : 
     478           0 :       capture_link_file->base.vt = &fd_capture_link_file_vt;
     479             : 
     480           0 :       int solcap_fd = (int)(ulong)runner->solcap_file;
     481           0 :       capture_link_file->fd         = solcap_fd;
     482           0 :       capture_ctx->capture_link      = &capture_link_file->base;
     483           0 :       capture_ctx->capctx_type.file   = capture_link_file;
     484           0 :       capture_ctx->solcap_start_slot = fd_bank_slot_get( runner->bank );
     485             : 
     486           0 :       fd_solcap_writer_init( capture_ctx->capture, solcap_fd );
     487           0 :     }
     488             : 
     489           0 :     fd_funk_txn_xid_t xid = { .ul = { fd_bank_slot_get( runner->bank ), runner->bank->data->idx } };
     490           0 :     fd_rewards_recalculate_partitioned_rewards( runner->banks, runner->bank, runner->accdb, &xid, runner->runtime_stack, capture_ctx );
     491             : 
     492             :     /* Process new epoch may push a new spad frame onto the runtime spad. We should make sure this frame gets
     493             :        cleared (if it was allocated) before executing the block. */
     494           0 :     int is_epoch_boundary = 0;
     495           0 :     fd_runtime_block_execute_prepare( runner->banks, runner->bank, runner->accdb, runner->runtime_stack, capture_ctx, &is_epoch_boundary );
     496             : 
     497             :     /* Sequential transaction execution */
     498           0 :     for( ulong i=0UL; i<txn_cnt; i++ ) {
     499           0 :       fd_txn_p_t * txn = &txn_ptrs[i];
     500             : 
     501             :       /* Execute the transaction against the runtime */
     502           0 :       res = FD_RUNTIME_EXECUTE_SUCCESS;
     503           0 :       fd_txn_in_t  txn_in = { .txn = txn, .bundle.is_bundle = 0 };
     504           0 :       fd_txn_out_t txn_out;
     505           0 :       fd_runtime_t * runtime = runner->runtime;
     506           0 :       fd_log_collector_t log[1];
     507           0 :       runtime->log.log_collector = log;
     508           0 :       runtime->acc_pool = runner->acc_pool;
     509           0 :       fd_solfuzz_txn_ctx_exec( runner, runtime, &txn_in, &res, &txn_out );
     510           0 :       txn_out.err.exec_err = res;
     511             : 
     512           0 :       if( FD_UNLIKELY( !txn_out.err.is_committable ) ) {
     513           0 :         fd_runtime_cancel_txn( runtime, &txn_out );
     514           0 :         return 0;
     515           0 :       }
     516             : 
     517             :       /* Finalize the transaction */
     518           0 :       fd_runtime_commit_txn( runtime, runner->bank, &txn_out );
     519             : 
     520           0 :       if( FD_UNLIKELY( !txn_out.err.is_committable ) ) {
     521           0 :         return 0;
     522           0 :       }
     523             : 
     524           0 :     }
     525             : 
     526             :     /* At this point we want to set the poh.  This is what will get
     527             :        updated in the blockhash queue. */
     528           0 :     fd_bank_poh_set( runner->bank, *poh );
     529             :     /* Finalize the block */
     530           0 :     fd_runtime_block_execute_finalize( runner->bank, runner->accdb, capture_ctx );
     531           0 :   } FD_SPAD_FRAME_END;
     532             : 
     533           0 :   return 1;
     534           0 : }
     535             : 
     536             : /* Canonical (Agave-aligned) schedule hash
     537             :    Unique pubkeys referenced by sched, sorted deterministically
     538             :    Per-rotation indices mapped into sorted-uniq array */
     539             : ulong
     540             : fd_solfuzz_block_hash_epoch_leaders( fd_solfuzz_runner_t *      runner,
     541             :                                      fd_epoch_leaders_t const * leaders,
     542             :                                      ulong                      seed,
     543           0 :                                      uchar                      out[16] ) {
     544             :   /* Single contiguous spad allocation for uniq[] and sched_mapped[] */
     545           0 :   void *buf = fd_spad_alloc(
     546           0 :     runner->spad,
     547           0 :     alignof(pk_with_pos_t),
     548           0 :     leaders->sched_cnt*sizeof(pk_with_pos_t) +
     549           0 :     leaders->sched_cnt*sizeof(uint) );
     550             : 
     551           0 :   pk_with_pos_t * tmp          = (pk_with_pos_t *)buf;
     552           0 :   uint          * sched_mapped = (uint *)( tmp + leaders->sched_cnt );
     553             : 
     554             :   /* Gather all pubkeys and original positions from sched[] (skip invalid) */
     555           0 :   ulong gather_cnt = 0UL;
     556           0 :   for( ulong i=0UL; i<leaders->sched_cnt; i++ ) {
     557           0 :     uint idx = leaders->sched[i];
     558           0 :     if( idx>=leaders->pub_cnt ) { /* invalid slot leader */
     559           0 :       sched_mapped[i] = 0U;       /* prefill invalid mapping */
     560           0 :       continue;
     561           0 :     }
     562           0 :     fd_memcpy( &tmp[gather_cnt].pk, &leaders->pub[idx], sizeof(fd_pubkey_t) );
     563           0 :     tmp[gather_cnt].sched_pos = i;
     564           0 :     gather_cnt++;
     565           0 :   }
     566             : 
     567           0 :   if( gather_cnt==0UL ) {
     568             :     /* No leaders => hash:=0, count:=0 */
     569           0 :     fd_memset( out, 0, sizeof(ulong)*2 );
     570           0 :     return 0UL;
     571           0 :   }
     572             : 
     573             :   /* Sort tmp[] by pubkey, note: comparator relies on first struct member */
     574           0 :   sort_pkpos_inplace( tmp, (ulong)gather_cnt );
     575             : 
     576             :   /* Dedupe and assign indices into sched_mapped[] during single pass */
     577           0 :   ulong uniq_cnt = 0UL;
     578           0 :   for( ulong i=0UL; i<gather_cnt; i++ ) {
     579           0 :     if( i==0UL || memcmp( &tmp[i].pk, &tmp[i-1].pk, sizeof(fd_pubkey_t) )!=0 )
     580           0 :       uniq_cnt++;
     581             :     /* uniq_cnt-1 is index in uniq set */
     582           0 :     sched_mapped[tmp[i].sched_pos] = (uint)(uniq_cnt-1UL);
     583           0 :   }
     584             : 
     585             :   /* Reconstruct contiguous uniq[] for hashing */
     586           0 :   fd_pubkey_t *uniq = fd_spad_alloc( runner->spad,
     587           0 :                                      alignof(fd_pubkey_t),
     588           0 :                                      uniq_cnt*sizeof(fd_pubkey_t) );
     589           0 :   {
     590           0 :     ulong write_pos = 0UL;
     591           0 :     for( ulong i=0UL; i<gather_cnt; i++ ) {
     592           0 :       if( i==0UL || memcmp( &tmp[i].pk, &tmp[i-1].pk, sizeof(fd_pubkey_t) )!=0 )
     593           0 :       fd_memcpy( &uniq[write_pos++], &tmp[i].pk, sizeof(fd_pubkey_t) );
     594           0 :     }
     595           0 :   }
     596             : 
     597             :   /* Hash sorted unique pubkeys */
     598           0 :   ulong h1 = fd_hash( seed, uniq, uniq_cnt * sizeof(fd_pubkey_t) );
     599           0 :   fd_memcpy( out, &h1, sizeof(ulong) );
     600             : 
     601             :   /* Hash mapped indices */
     602           0 :   ulong h2 = fd_hash( seed, sched_mapped, leaders->sched_cnt * sizeof(uint) );
     603           0 :   fd_memcpy( out + sizeof(ulong), &h2, sizeof(ulong) );
     604             : 
     605           0 :   return uniq_cnt;
     606           0 : }
     607             : 
     608             : static void
     609             : fd_solfuzz_pb_build_leader_schedule_effects( fd_solfuzz_runner_t *          runner,
     610             :                                              fd_funk_txn_xid_t const *      xid,
     611           0 :                                              fd_exec_test_block_effects_t * effects ) {
     612             :   /* Read epoch schedule sysvar */
     613           0 :   fd_epoch_schedule_t es_;
     614           0 :   fd_epoch_schedule_t * sched = fd_sysvar_epoch_schedule_read( runner->accdb, xid, &es_ );
     615           0 :   FD_TEST( sched!=NULL );
     616             : 
     617             :   /* We will capture the leader schedule for the current epoch that we
     618             :      are in.  This will capture the leader schedule generated by an
     619             :      epoch boundary if one was crossed. */
     620           0 :   ulong epoch          = fd_bank_epoch_get( runner->bank );
     621           0 :   ulong ls_slot0       = fd_epoch_slot0( sched, epoch );
     622           0 :   ulong slots_in_epoch = fd_epoch_slot_cnt( sched, epoch );
     623             : 
     624           0 :   fd_epoch_leaders_t const * effects_leaders = fd_bank_epoch_leaders_query( runner->bank );
     625             : 
     626             :   /* Fill out effects struct from the Agave epoch info */
     627           0 :   effects->has_leader_schedule               = 1;
     628           0 :   effects->leader_schedule.leaders_epoch     = epoch;
     629           0 :   effects->leader_schedule.leaders_slot0     = ls_slot0;
     630           0 :   effects->leader_schedule.leaders_slot_cnt  = slots_in_epoch;
     631           0 :   effects->leader_schedule.leaders_sched_cnt = slots_in_epoch;
     632           0 :   effects->leader_schedule.leader_pub_cnt    = fd_solfuzz_block_hash_epoch_leaders(
     633           0 :       runner, effects_leaders,
     634           0 :       LEADER_SCHEDULE_HASH_SEED,
     635           0 :       effects->leader_schedule.leader_schedule_hash
     636           0 :   );
     637           0 : }
     638             : 
     639             : ulong
     640             : fd_solfuzz_pb_block_run( fd_solfuzz_runner_t * runner,
     641             :                           void const *         input_,
     642             :                           void **              output_,
     643             :                           void *               output_buf,
     644           0 :                           ulong                output_bufsz ) {
     645           0 :   fd_exec_test_block_context_t const * input  = fd_type_pun_const( input_ );
     646           0 :   fd_exec_test_block_effects_t **      output = fd_type_pun( output_ );
     647             : 
     648           0 :   FD_SPAD_FRAME_BEGIN( runner->spad ) {
     649           0 :     ulong txn_cnt;
     650           0 :     fd_hash_t poh = {0};
     651           0 :     fd_txn_p_t * txn_ptrs = fd_solfuzz_pb_block_ctx_create( runner, input, &txn_cnt, &poh );
     652           0 :     if( txn_ptrs==NULL ) {
     653           0 :       fd_solfuzz_pb_block_ctx_destroy( runner );
     654           0 :       return 0;
     655           0 :     }
     656             : 
     657           0 :     fd_funk_txn_xid_t xid = { .ul = { fd_bank_slot_get( runner->bank ), runner->bank->data->idx } };
     658             : 
     659             :     /* Execute the constructed block against the runtime. */
     660           0 :     int is_committable = fd_solfuzz_block_ctx_exec( runner, txn_ptrs, txn_cnt, &poh );
     661             : 
     662             :     /* Start saving block exec results */
     663           0 :     FD_SCRATCH_ALLOC_INIT( l, output_buf );
     664           0 :     ulong output_end = (ulong)output_buf + output_bufsz;
     665             : 
     666           0 :     fd_exec_test_block_effects_t * effects =
     667           0 :     FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_block_effects_t),
     668           0 :                                 sizeof(fd_exec_test_block_effects_t) );
     669           0 :     if( FD_UNLIKELY( _l > output_end ) ) {
     670           0 :       abort();
     671           0 :     }
     672           0 :     fd_memset( effects, 0, sizeof(fd_exec_test_block_effects_t) );
     673             : 
     674             :     /* Capture error status */
     675           0 :     effects->has_error = !is_committable;
     676             : 
     677             :     /* Capture capitalization */
     678           0 :     effects->slot_capitalization = !effects->has_error ? fd_bank_capitalization_get( runner->bank ) : 0UL;
     679             : 
     680             :     /* Capture hashes */
     681           0 :     fd_hash_t bank_hash = !effects->has_error ? fd_bank_bank_hash_get( runner->bank ) : (fd_hash_t){0};
     682           0 :     fd_memcpy( effects->bank_hash, bank_hash.hash, sizeof(fd_hash_t) );
     683             : 
     684             :     /* Capture cost tracker */
     685           0 :     fd_cost_tracker_t const * cost_tracker = fd_bank_cost_tracker_locking_query( runner->bank );
     686           0 :     effects->has_cost_tracker = 1;
     687           0 :     effects->cost_tracker = (fd_exec_test_cost_tracker_t) {
     688           0 :       .block_cost = cost_tracker ? cost_tracker->block_cost : 0UL,
     689           0 :       .vote_cost  = cost_tracker ? cost_tracker->vote_cost  : 0UL,
     690           0 :     };
     691           0 :     fd_bank_cost_tracker_end_locking_query( runner->bank );
     692             : 
     693             :     /* Effects: build T-epoch (bank epoch), T-stakes ephemeral leaders and report */
     694           0 :     fd_solfuzz_pb_build_leader_schedule_effects( runner, &xid, effects );
     695             : 
     696           0 :     ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
     697           0 :     fd_solfuzz_pb_block_ctx_destroy( runner );
     698             : 
     699           0 :     *output = effects;
     700           0 :     return actual_end - (ulong)output_buf;
     701           0 :   } FD_SPAD_FRAME_END;
     702           0 : }

Generated by: LCOV version 1.14