LCOV - code coverage report
Current view: top level - flamenco/runtime - fd_runtime.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 2545 0.0 %
Date: 2025-03-20 12:08:36 Functions: 0 76 0.0 %

          Line data    Source code
       1             : #include "fd_runtime.h"
       2             : #include "context/fd_exec_epoch_ctx.h"
       3             : #include "fd_acc_mgr.h"
       4             : #include "fd_runtime_err.h"
       5             : #include "fd_runtime_init.h"
       6             : #include "fd_pubkey_utils.h"
       7             : 
       8             : #include "fd_executor.h"
       9             : #include "fd_cost_tracker.h"
      10             : #include "fd_hashes.h"
      11             : #include "fd_txncache.h"
      12             : #include "sysvar/fd_sysvar_cache.h"
      13             : #include "sysvar/fd_sysvar_clock.h"
      14             : #include "sysvar/fd_sysvar_epoch_schedule.h"
      15             : #include "sysvar/fd_sysvar_recent_hashes.h"
      16             : #include "sysvar/fd_sysvar_stake_history.h"
      17             : #include "sysvar/fd_sysvar.h"
      18             : #include "../../ballet/base58/fd_base58.h"
      19             : #include "../../ballet/txn/fd_txn.h"
      20             : #include "../../ballet/bmtree/fd_bmtree.h"
      21             : 
      22             : #include "../stakes/fd_stakes.h"
      23             : #include "../rewards/fd_rewards.h"
      24             : 
      25             : #include "context/fd_exec_txn_ctx.h"
      26             : #include "context/fd_exec_instr_ctx.h"
      27             : #include "info/fd_microblock_batch_info.h"
      28             : #include "info/fd_microblock_info.h"
      29             : 
      30             : #include "program/fd_stake_program.h"
      31             : #include "program/fd_builtin_programs.h"
      32             : #include "program/fd_system_program.h"
      33             : #include "program/fd_vote_program.h"
      34             : #include "program/fd_bpf_program_util.h"
      35             : #include "program/fd_bpf_loader_program.h"
      36             : #include "program/fd_compute_budget_program.h"
      37             : 
      38             : #include "sysvar/fd_sysvar_clock.h"
      39             : #include "sysvar/fd_sysvar_fees.h"
      40             : #include "sysvar/fd_sysvar_last_restart_slot.h"
      41             : #include "sysvar/fd_sysvar_recent_hashes.h"
      42             : #include "sysvar/fd_sysvar_rent.h"
      43             : #include "sysvar/fd_sysvar_slot_hashes.h"
      44             : #include "sysvar/fd_sysvar_slot_history.h"
      45             : 
      46             : #include "tests/fd_dump_pb.h"
      47             : 
      48             : #include "../nanopb/pb_decode.h"
      49             : #include "../nanopb/pb_encode.h"
      50             : #include "../types/fd_solana_block.pb.h"
      51             : 
      52             : #include "fd_system_ids.h"
      53             : #include "../vm/fd_vm.h"
      54             : #include "fd_blockstore.h"
      55             : #include "../../disco/pack/fd_pack.h"
      56             : #include "../fd_rwlock.h"
      57             : 
      58             : #include <stdio.h>
      59             : #include <ctype.h>
      60             : #include <unistd.h>
      61             : #include <sys/stat.h>
      62             : #include <sys/types.h>
      63             : #include <errno.h>
      64             : #include <fcntl.h>
      65             : 
      66             : /******************************************************************************/
      67             : /* Public Runtime Helpers                                                     */
      68             : /******************************************************************************/
      69             : 
      70             : /*
      71             :    https://github.com/anza-xyz/agave/blob/v2.1.1/runtime/src/bank.rs#L1254-L1258
      72             :    https://github.com/anza-xyz/agave/blob/v2.1.1/runtime/src/bank.rs#L1749
      73             :  */
      74             : int
      75             : fd_runtime_compute_max_tick_height( ulong   ticks_per_slot,
      76             :                                     ulong   slot,
      77           0 :                                     ulong * out_max_tick_height /* out */ ) {
      78           0 :   ulong max_tick_height = 0UL;
      79           0 :   if( FD_LIKELY( ticks_per_slot > 0UL ) ) {
      80           0 :     ulong next_slot = fd_ulong_sat_add( slot, 1UL );
      81           0 :     if( FD_UNLIKELY( next_slot == slot ) ) {
      82           0 :       FD_LOG_WARNING(( "max tick height addition overflowed slot %lu ticks_per_slot %lu", slot, ticks_per_slot ));
      83           0 :       return FD_RUNTIME_EXECUTE_GENERIC_ERR;
      84           0 :     }
      85           0 :     if( FD_UNLIKELY( ULONG_MAX / ticks_per_slot < next_slot ) ) {
      86           0 :       FD_LOG_WARNING(( "max tick height multiplication overflowed slot %lu ticks_per_slot %lu", slot, ticks_per_slot ));
      87           0 :       return FD_RUNTIME_EXECUTE_GENERIC_ERR;
      88           0 :     }
      89           0 :     max_tick_height = fd_ulong_sat_mul( next_slot, ticks_per_slot );
      90           0 :   }
      91           0 :   *out_max_tick_height = max_tick_height;
      92           0 :   return FD_RUNTIME_EXECUTE_SUCCESS;
      93           0 : }
      94             : 
      95             : void
      96             : fd_runtime_register_new_fresh_account( fd_exec_slot_ctx_t * slot_ctx,
      97           0 :                                        fd_pubkey_t const  * pubkey ) {
      98             : 
      99             :   /* Insert the new account into the partition */
     100           0 :   ulong partition = fd_rent_key_to_partition( pubkey, slot_ctx->acc_mgr->part_width, slot_ctx->acc_mgr->slots_per_epoch );
     101             : 
     102           0 :   fd_rent_fresh_accounts_partition_t_mapnode_t partition_key[1] = {0};
     103           0 :   partition_key->elem.partition = partition;
     104           0 :   fd_rent_fresh_accounts_partition_t_mapnode_t * partition_node = fd_rent_fresh_accounts_partition_t_map_find(
     105           0 :     slot_ctx->rent_fresh_accounts.partitions_pool,
     106           0 :     slot_ctx->rent_fresh_accounts.partitions_root,
     107           0 :     partition_key );
     108           0 :   if( FD_UNLIKELY( partition_node == NULL ) ) {
     109           0 :     FD_LOG_ERR(( "fd_rent_fresh_accounts_partition_t_map_find failed" ));
     110           0 :   }
     111           0 :   if( FD_UNLIKELY( partition_node->elem.accounts_pool == NULL ) ) {
     112           0 :     FD_LOG_ERR(( "node->elem.accounts_pool == NULL" ));
     113           0 :   }
     114           0 :   if( FD_UNLIKELY( fd_pubkey_node_t_map_free( partition_node->elem.accounts_pool ) == 0UL ) ) {
     115           0 :     FD_LOG_ERR(( "rent_fresh_accounts_partition full - increase the partition size" ));
     116           0 :   }
     117             : 
     118           0 :   fd_pubkey_node_t_mapnode_t account_key;
     119           0 :   fd_memcpy( &account_key.elem.pubkey, pubkey, FD_PUBKEY_FOOTPRINT );
     120           0 :   fd_pubkey_node_t_mapnode_t * account_node = fd_pubkey_node_t_map_find(
     121           0 :     partition_node->elem.accounts_pool,
     122           0 :     partition_node->elem.accounts_root,
     123           0 :     &account_key
     124           0 :    );
     125           0 :   if ( FD_LIKELY( account_node != NULL ) ) {
     126           0 :    return;
     127           0 :   }
     128             : 
     129           0 :   fd_pubkey_node_t_mapnode_t * new_account_node = fd_pubkey_node_t_map_acquire( partition_node->elem.accounts_pool );
     130           0 :   if( FD_UNLIKELY( new_account_node == NULL ) ) {
     131           0 :     FD_LOG_ERR(( "new_account_node == NULL" ));
     132           0 :   }
     133           0 :   fd_memcpy( &new_account_node->elem.pubkey, pubkey, FD_PUBKEY_FOOTPRINT );
     134           0 :   fd_pubkey_node_t_map_insert(
     135           0 :     partition_node->elem.accounts_pool,
     136           0 :     &partition_node->elem.accounts_root,
     137           0 :     new_account_node
     138           0 :   );
     139             : 
     140           0 :   slot_ctx->rent_fresh_accounts.total_count++;
     141           0 : }
     142             : 
     143             : void
     144             : fd_runtime_repartition_fresh_account_partitions( fd_exec_slot_ctx_t * slot_ctx,
     145           0 :                                                  fd_spad_t *          runtime_spad ) {
     146           0 :   FD_SPAD_FRAME_BEGIN( runtime_spad ) {
     147             : 
     148             :     /* Collect all the dirty pubkeys into one list */
     149           0 :     ulong dirty_pubkeys_cnt = 0UL;
     150           0 :     fd_pubkey_t * dirty_pubkeys = fd_spad_alloc(
     151           0 :       runtime_spad,
     152           0 :       FD_PUBKEY_ALIGN,
     153           0 :       FD_PUBKEY_FOOTPRINT * slot_ctx->rent_fresh_accounts.total_count );
     154           0 :     if( FD_UNLIKELY( !dirty_pubkeys ) ) {
     155           0 :       FD_LOG_ERR(( "fd_spad_alloc failed" ));
     156           0 :     }
     157           0 :     for( fd_rent_fresh_accounts_partition_t_mapnode_t * partition_node = fd_rent_fresh_accounts_partition_t_map_minimum(
     158           0 :             slot_ctx->rent_fresh_accounts.partitions_pool,
     159           0 :             slot_ctx->rent_fresh_accounts.partitions_root
     160           0 :           );
     161           0 :           partition_node;
     162           0 :           partition_node = fd_rent_fresh_accounts_partition_t_map_successor(
     163           0 :             slot_ctx->rent_fresh_accounts.partitions_pool,
     164           0 :             partition_node
     165           0 :            ) ) {
     166           0 :       fd_pubkey_node_t_mapnode_t * next_account_node;
     167           0 :       for( fd_pubkey_node_t_mapnode_t * account_node = fd_pubkey_node_t_map_minimum(
     168           0 :             partition_node->elem.accounts_pool,
     169           0 :             partition_node->elem.accounts_root
     170           0 :           );
     171           0 :           account_node;
     172           0 :           account_node = next_account_node ) {
     173           0 :         next_account_node = fd_pubkey_node_t_map_successor(
     174           0 :           partition_node->elem.accounts_pool,
     175           0 :           account_node );
     176           0 :         fd_memcpy( &dirty_pubkeys[dirty_pubkeys_cnt++], &account_node->elem.pubkey, FD_PUBKEY_FOOTPRINT );
     177             : 
     178           0 :         fd_pubkey_node_t_mapnode_t * removed_node = fd_pubkey_node_t_map_remove(
     179           0 :           partition_node->elem.accounts_pool,
     180           0 :           &partition_node->elem.accounts_root,
     181           0 :           account_node );
     182           0 :         fd_pubkey_node_t_map_release( partition_node->elem.accounts_pool, removed_node );
     183           0 :       }
     184           0 :     }
     185             : 
     186             :     /* Register each new account, which will insert it into a new partition */
     187           0 :     for( ulong i = 0UL; i < dirty_pubkeys_cnt; i++ ) {
     188           0 :       fd_runtime_register_new_fresh_account( slot_ctx, &dirty_pubkeys[i] );
     189           0 :     }
     190             : 
     191           0 :   } FD_SPAD_FRAME_END;
     192           0 : }
     193             : 
     194             : void
     195             : fd_runtime_update_slots_per_epoch( fd_exec_slot_ctx_t * slot_ctx,
     196             :                                    ulong                slots_per_epoch,
     197           0 :                                    fd_spad_t *          runtime_spad ) {
     198           0 :   if( FD_LIKELY( slots_per_epoch == slot_ctx->acc_mgr->slots_per_epoch ) ) {
     199           0 :     return;
     200           0 :   }
     201             : 
     202           0 :   fd_acc_mgr_set_slots_per_epoch( slot_ctx, slots_per_epoch );
     203             : 
     204           0 :   fd_runtime_repartition_fresh_account_partitions( slot_ctx, runtime_spad );
     205           0 : }
     206             : 
     207             : void
     208             : fd_runtime_update_leaders( fd_exec_slot_ctx_t * slot_ctx,
     209             :                            ulong                slot,
     210           0 :                            fd_spad_t *          runtime_spad ) {
     211             : 
     212           0 :   FD_SPAD_FRAME_BEGIN( runtime_spad ) {
     213             : 
     214           0 :   fd_epoch_schedule_t schedule = slot_ctx->epoch_ctx->epoch_bank.epoch_schedule;
     215             : 
     216           0 :   FD_LOG_INFO(( "schedule->slots_per_epoch = %lu", schedule.slots_per_epoch ));
     217           0 :   FD_LOG_INFO(( "schedule->leader_schedule_slot_offset = %lu", schedule.leader_schedule_slot_offset ));
     218           0 :   FD_LOG_INFO(( "schedule->warmup = %d", schedule.warmup ));
     219           0 :   FD_LOG_INFO(( "schedule->first_normal_epoch = %lu", schedule.first_normal_epoch ));
     220           0 :   FD_LOG_INFO(( "schedule->first_normal_slot = %lu", schedule.first_normal_slot ));
     221             : 
     222           0 :   fd_vote_accounts_t const * epoch_vaccs = &slot_ctx->slot_bank.epoch_stakes;
     223             : 
     224           0 :   ulong epoch    = fd_slot_to_epoch( &schedule, slot, NULL );
     225           0 :   ulong slot0    = fd_epoch_slot0( &schedule, epoch );
     226           0 :   ulong slot_cnt = fd_epoch_slot_cnt( &schedule, epoch );
     227             : 
     228           0 :   fd_runtime_update_slots_per_epoch( slot_ctx, fd_epoch_slot_cnt( &schedule, epoch ), runtime_spad );
     229             : 
     230           0 :   ulong               vote_acc_cnt  = fd_vote_accounts_pair_t_map_size( epoch_vaccs->vote_accounts_pool, epoch_vaccs->vote_accounts_root );
     231           0 :   fd_stake_weight_t * epoch_weights = fd_spad_alloc( runtime_spad, alignof(fd_stake_weight_t), vote_acc_cnt * sizeof(fd_stake_weight_t) );
     232           0 :   if( FD_UNLIKELY( !epoch_weights ) ) {
     233           0 :     FD_LOG_ERR(( "fd_spad_alloc() failed" ));
     234           0 :   }
     235             : 
     236           0 :   ulong stake_weight_cnt = fd_stake_weights_by_node( epoch_vaccs, epoch_weights, runtime_spad );
     237             : 
     238           0 :   if( FD_UNLIKELY( stake_weight_cnt == ULONG_MAX ) ) {
     239           0 :     FD_LOG_ERR(( "fd_stake_weights_by_node() failed" ));
     240           0 :   }
     241             : 
     242             :   /* Derive leader schedule */
     243             : 
     244           0 :   FD_LOG_INFO(( "stake_weight_cnt=%lu slot_cnt=%lu", stake_weight_cnt, slot_cnt ));
     245           0 :   ulong epoch_leaders_footprint = fd_epoch_leaders_footprint( stake_weight_cnt, slot_cnt );
     246           0 :   FD_LOG_INFO(( "epoch_leaders_footprint=%lu", epoch_leaders_footprint ));
     247           0 :   if( FD_LIKELY( epoch_leaders_footprint ) ) {
     248           0 :     if( FD_UNLIKELY( stake_weight_cnt>MAX_PUB_CNT ) ) {
     249           0 :       FD_LOG_ERR(( "Stake weight count exceeded max" ));
     250           0 :     }
     251           0 :     if( FD_UNLIKELY( slot_cnt>MAX_SLOTS_CNT ) ) {
     252           0 :       FD_LOG_ERR(( "Slot count exceeeded max" ));
     253           0 :     }
     254             : 
     255           0 :     void *               epoch_leaders_mem = fd_exec_epoch_ctx_leaders( slot_ctx->epoch_ctx );
     256           0 :     fd_epoch_leaders_t * leaders           = fd_epoch_leaders_join( fd_epoch_leaders_new( epoch_leaders_mem,
     257           0 :                                                                                           epoch,
     258           0 :                                                                                           slot0,
     259           0 :                                                                                           slot_cnt,
     260           0 :                                                                                           stake_weight_cnt,
     261           0 :                                                                                           epoch_weights,
     262           0 :                                                                                           0UL ) );
     263           0 :     if( FD_UNLIKELY( !leaders ) ) {
     264           0 :       FD_LOG_ERR(( "Unable to init and join fd_epoch_leaders" ));
     265           0 :     }
     266           0 :   }
     267             : 
     268           0 :   } FD_SPAD_FRAME_END;
     269           0 : }
     270             : 
     271             : /* Loads the sysvar cache. Expects acc_mgr, funk_txn to be non-NULL and valid. */
     272             : int
     273             : fd_runtime_sysvar_cache_load( fd_exec_slot_ctx_t * slot_ctx,
     274           0 :                               fd_spad_t *          runtime_spad ) {
     275           0 :   if( FD_UNLIKELY( !slot_ctx->acc_mgr ) ) {
     276           0 :     return -1;
     277           0 :   }
     278             : 
     279           0 :   fd_sysvar_cache_restore( slot_ctx->sysvar_cache,
     280           0 :                            slot_ctx->acc_mgr,
     281           0 :                            slot_ctx->funk_txn,
     282           0 :                            runtime_spad,
     283           0 :                            slot_ctx->runtime_wksp );
     284             : 
     285           0 :   return FD_RUNTIME_EXECUTE_SUCCESS;
     286           0 : }
     287             : 
     288             : /******************************************************************************/
     289             : /* Various Private Runtime Helpers                                            */
     290             : /******************************************************************************/
     291             : 
     292             : 
     293             : /* fee to be deposited should be > 0
     294             :    Returns 0 if validation succeeds
     295             :    Returns the amount to burn(==fee) on failure */
     296             : static ulong
     297             : fd_runtime_validate_fee_collector( fd_exec_slot_ctx_t const * slot_ctx,
     298             :                                    fd_txn_account_t const *  collector,
     299           0 :                                    ulong                     fee ) {
     300           0 :   if( FD_UNLIKELY( fee<=0UL ) ) {
     301           0 :     FD_LOG_ERR(( "expected fee(%lu) to be >0UL", fee ));
     302           0 :   }
     303             : 
     304           0 :   if( FD_UNLIKELY( memcmp( collector->const_meta->info.owner, fd_solana_system_program_id.key, sizeof(collector->const_meta->info.owner) ) ) ) {
     305           0 :     FD_BASE58_ENCODE_32_BYTES( collector->pubkey->key, _out_key );
     306           0 :     FD_LOG_WARNING(( "cannot pay a non-system-program owned account (%s)", _out_key ));
     307           0 :     return fee;
     308           0 :   }
     309             : 
     310             :   /* https://github.com/anza-xyz/agave/blob/v1.18.23/runtime/src/bank/fee_distribution.rs#L111
     311             :      https://github.com/anza-xyz/agave/blob/v1.18.23/runtime/src/accounts/account_rent_state.rs#L39
     312             :      In agave's fee deposit code, rent state transition check logic is as follows:
     313             :      The transition is NOT allowed iff
     314             :      === BEGIN
     315             :      the post deposit account is rent paying AND the pre deposit account is not rent paying
     316             :      OR
     317             :      the post deposit account is rent paying AND the pre deposit account is rent paying AND !(post_data_size == pre_data_size && post_lamports <= pre_lamports)
     318             :      === END
     319             :      post_data_size == pre_data_size is always true during fee deposit.
     320             :      However, post_lamports > pre_lamports because we are paying a >0 amount.
     321             :      So, the above reduces down to
     322             :      === BEGIN
     323             :      the post deposit account is rent paying AND the pre deposit account is not rent paying
     324             :      OR
     325             :      the post deposit account is rent paying AND the pre deposit account is rent paying AND TRUE
     326             :      === END
     327             :      This is equivalent to checking that the post deposit account is rent paying.
     328             :      An account is rent paying if the post deposit balance is >0 AND it's not rent exempt.
     329             :      We already know that the post deposit balance is >0 because we are paying a >0 amount.
     330             :      So TLDR we just check if the account is rent exempt.
     331             :    */
     332           0 :   ulong minbal = fd_rent_exempt_minimum_balance( (fd_rent_t const *)fd_sysvar_cache_rent( slot_ctx->sysvar_cache ), collector->const_meta->dlen );
     333           0 :   if( FD_UNLIKELY( collector->const_meta->info.lamports + fee < minbal ) ) {
     334           0 :     FD_BASE58_ENCODE_32_BYTES( collector->pubkey->key, _out_key );
     335           0 :     FD_LOG_WARNING(("cannot pay a rent paying account (%s)", _out_key ));
     336           0 :     return fee;
     337           0 :   }
     338             : 
     339           0 :   return 0UL;
     340           0 : }
     341             : 
     342             : 
     343             : static int
     344           0 : fd_runtime_run_incinerator( fd_exec_slot_ctx_t * slot_ctx ) {
     345           0 :   FD_TXN_ACCOUNT_DECL( rec );
     346             : 
     347           0 :   int err = fd_acc_mgr_modify( slot_ctx->acc_mgr, slot_ctx->funk_txn, &fd_sysvar_incinerator_id, 0, 0UL, rec );
     348           0 :   if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
     349             :     // TODO: not really an error! This is fine!
     350           0 :     return -1;
     351           0 :   }
     352             : 
     353           0 :   slot_ctx->slot_bank.capitalization = fd_ulong_sat_sub( slot_ctx->slot_bank.capitalization, rec->const_meta->info.lamports );
     354           0 :   rec->meta->info.lamports           = 0UL;
     355             : 
     356           0 :   return 0;
     357           0 : }
     358             : 
     359             : /* Yes, this is a real function that exists in Solana. Yes, I am ashamed I have had to replicate it. */
     360             : // https://github.com/firedancer-io/solana/blob/d8292b427adf8367d87068a3a88f6fd3ed8916a5/runtime/src/bank.rs#L5618
     361             : static ulong
     362           0 : fd_runtime_slot_count_in_two_day( ulong ticks_per_slot ) {
     363           0 :   return 2UL * FD_SYSVAR_CLOCK_DEFAULT_TICKS_PER_SECOND * 86400UL /* seconds per day */ / ticks_per_slot;
     364           0 : }
     365             : 
     366             : // https://github.com/firedancer-io/solana/blob/d8292b427adf8367d87068a3a88f6fd3ed8916a5/runtime/src/bank.rs#L5594
     367             : static int
     368           0 : fd_runtime_use_multi_epoch_collection( fd_exec_slot_ctx_t const * slot_ctx, ulong slot ) {
     369           0 :   fd_epoch_bank_t const * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
     370           0 :   fd_epoch_schedule_t const * schedule = &epoch_bank->epoch_schedule;
     371             : 
     372           0 :   ulong off;
     373           0 :   ulong epoch = fd_slot_to_epoch( schedule, slot, &off );
     374           0 :   ulong slots_per_normal_epoch = fd_epoch_slot_cnt( schedule, schedule->first_normal_epoch );
     375             : 
     376           0 :   ulong slot_count_in_two_day = fd_runtime_slot_count_in_two_day( epoch_bank->ticks_per_slot );
     377             : 
     378           0 :   int use_multi_epoch_collection = ( epoch >= schedule->first_normal_epoch )
     379           0 :       && ( slots_per_normal_epoch < slot_count_in_two_day );
     380             : 
     381           0 :   return use_multi_epoch_collection;
     382           0 : }
     383             : 
     384             : FD_FN_UNUSED static ulong
     385           0 : fd_runtime_num_rent_partitions( fd_exec_slot_ctx_t const * slot_ctx, ulong slot ) {
     386           0 :   fd_epoch_bank_t const * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
     387           0 :   fd_epoch_schedule_t const * schedule = &epoch_bank->epoch_schedule;
     388           0 : 
     389           0 :   ulong off;
     390           0 :   ulong epoch = fd_slot_to_epoch( schedule, slot, &off );
     391           0 :   ulong slots_per_epoch = fd_epoch_slot_cnt( schedule, epoch );
     392           0 : 
     393           0 :   ulong slot_count_in_two_day = fd_runtime_slot_count_in_two_day( epoch_bank->ticks_per_slot );
     394           0 : 
     395           0 :   int use_multi_epoch_collection = fd_runtime_use_multi_epoch_collection( slot_ctx, slot );
     396           0 : 
     397           0 :   if( use_multi_epoch_collection ) {
     398           0 :     ulong epochs_in_cycle = slot_count_in_two_day / slots_per_epoch;
     399           0 :     return slots_per_epoch * epochs_in_cycle;
     400           0 :   } else {
     401           0 :     return slots_per_epoch;
     402           0 :   }
     403           0 : }
     404             : 
     405             : // https://github.com/anza-xyz/agave/blob/2bdcc838c18d262637524274cbb2275824eb97b8/accounts-db/src/accounts_partition.rs#L30
     406             : static ulong
     407           0 : fd_runtime_get_rent_partition( fd_exec_slot_ctx_t const * slot_ctx, ulong slot ) {
     408           0 :   int use_multi_epoch_collection = fd_runtime_use_multi_epoch_collection( slot_ctx, slot );
     409             : 
     410           0 :   fd_epoch_bank_t const * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
     411           0 :   fd_epoch_schedule_t const * schedule = &epoch_bank->epoch_schedule;
     412             : 
     413           0 :   ulong off;
     414           0 :   ulong epoch = fd_slot_to_epoch( schedule, slot, &off );
     415           0 :   ulong slot_count_per_epoch = fd_epoch_slot_cnt( schedule, epoch );
     416           0 :   ulong slot_count_in_two_day = fd_runtime_slot_count_in_two_day( epoch_bank->ticks_per_slot );
     417             : 
     418           0 :   ulong base_epoch;
     419           0 :   ulong epoch_count_in_cycle;
     420           0 :   if( use_multi_epoch_collection ) {
     421           0 :     base_epoch = schedule->first_normal_epoch;
     422           0 :     epoch_count_in_cycle = slot_count_in_two_day / slot_count_per_epoch;
     423           0 :   } else {
     424           0 :     base_epoch = 0;
     425           0 :     epoch_count_in_cycle = 1;
     426           0 :   }
     427             : 
     428           0 :   ulong epoch_offset = epoch - base_epoch;
     429           0 :   ulong epoch_index_in_cycle = epoch_offset % epoch_count_in_cycle;
     430           0 :   return off + ( epoch_index_in_cycle * slot_count_per_epoch );
     431           0 : }
     432             : 
     433             : static void
     434             : fd_runtime_update_rent_epoch_account( fd_exec_slot_ctx_t * slot_ctx,
     435           0 :                                       fd_pubkey_t        * pubkey ) {
     436           0 :   FD_TXN_ACCOUNT_DECL( rec );
     437           0 :   int err = fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, pubkey, rec );
     438             : 
     439             :   /* If the account has been deleted, skip it */
     440           0 :   if( err==FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) {
     441           0 :     return;
     442           0 :   }
     443           0 :   if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) {
     444           0 :     FD_LOG_WARNING(( "fd_runtime_update_rent_epoch: fd_acc_mgr_view failed (%d)", err ));
     445           0 :     return;
     446           0 :   }
     447             : 
     448             :   /* If the account's rent epoch is correct, don't update it */
     449           0 :   if( rec->const_meta->info.rent_epoch == FD_RENT_EXEMPT_RENT_EPOCH ) {
     450           0 :     return;
     451           0 :   }
     452             : 
     453             :   /* Otherwise, update the rent epoch field */
     454           0 :   err = fd_acc_mgr_modify( slot_ctx->acc_mgr, slot_ctx->funk_txn, pubkey, 0, 0UL, rec );
     455           0 :   if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
     456           0 :     FD_LOG_WARNING(( "fd_runtime_update_rent_epoch: fd_acc_mgr_modify failed (%d)", err ));
     457           0 :     return;
     458           0 :   }
     459           0 :   rec->meta->info.rent_epoch = FD_RENT_EXEMPT_RENT_EPOCH;
     460           0 : }
     461             : 
     462             : /* Emulate collecting rent from accounts. Since every account is rent exempt, all we have to do
     463             :    is set all rent_epoch fields to ULONG_MAX. By induction, we only need to do this for newly created accounts.
     464             : 
     465             :    The slight complication is that we need to do this at the appropiate time - taking into account
     466             :    the rent partition the account would have fallen into.
     467             : 
     468             :    This code is super hacky, but will be removed soon when disable_partitioned_rent_collection is activated.
     469             :    After that, we will not update any rent_epoch fields.
     470             : 
     471             :    https://github.com/anza-xyz/agave/blob/v2.1.14/runtime/src/bank.rs#L2921 */
     472             : static void
     473           0 : fd_runtime_update_rent_epoch( fd_exec_slot_ctx_t * slot_ctx ) {
     474           0 :   if( FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, disable_partitioned_rent_collection ) ) {
     475           0 :     return;
     476           0 :   }
     477             : 
     478           0 :   ulong slot0 = slot_ctx->slot_bank.prev_slot;
     479           0 :   ulong slot1 = slot_ctx->slot_bank.slot;
     480             : 
     481             :   /* Accomodate skipped slots */
     482           0 :   for( ulong s = slot0 + 1; s <= slot1; ++s ) {
     483             :     /* Look up the accounts in the partition */
     484           0 :     fd_rent_fresh_accounts_partition_t_mapnode_t key = {0};
     485           0 :     key.elem.partition = fd_runtime_get_rent_partition( slot_ctx, s );
     486           0 :     fd_rent_fresh_accounts_partition_t_mapnode_t * partition_node = fd_rent_fresh_accounts_partition_t_map_find(
     487           0 :       slot_ctx->rent_fresh_accounts.partitions_pool,
     488           0 :       slot_ctx->rent_fresh_accounts.partitions_root,
     489           0 :       &key
     490           0 :     );
     491           0 :     if( FD_LIKELY( partition_node == NULL ) ) {
     492           0 :       FD_LOG_WARNING(( "fresh account partition not found for slot %lu", s ));
     493           0 :       continue;
     494           0 :     }
     495             : 
     496             :     /* Set the rent epoch field of each account to ULONG_MAX, if it wasn't already.
     497             :        Clear the partition as we are iterating over it. */
     498           0 :     fd_pubkey_node_t_mapnode_t * next_account_node;
     499           0 :     for( fd_pubkey_node_t_mapnode_t * account_node = fd_pubkey_node_t_map_minimum(
     500           0 :             partition_node->elem.accounts_pool,
     501           0 :             partition_node->elem.accounts_root
     502           0 :           );
     503           0 :           account_node;
     504           0 :           account_node = next_account_node ) {
     505           0 :       next_account_node = fd_pubkey_node_t_map_successor(
     506           0 :         partition_node->elem.accounts_pool,
     507           0 :         account_node );
     508             : 
     509           0 :       fd_runtime_update_rent_epoch_account( slot_ctx, fd_type_pun( &account_node->elem.pubkey ) );
     510             : 
     511           0 :       fd_pubkey_node_t_mapnode_t * removed_node = fd_pubkey_node_t_map_remove(
     512           0 :         partition_node->elem.accounts_pool,
     513           0 :         &partition_node->elem.accounts_root,
     514           0 :         account_node );
     515           0 :       fd_pubkey_node_t_map_release( partition_node->elem.accounts_pool, removed_node );
     516           0 :       slot_ctx->rent_fresh_accounts.total_count -= 1UL;
     517           0 :     }
     518           0 :   }
     519           0 : }
     520             : 
     521             : static void
     522           0 : fd_runtime_freeze( fd_exec_slot_ctx_t * slot_ctx, fd_spad_t * runtime_spad ) {
     523             : 
     524           0 :   fd_runtime_update_rent_epoch( slot_ctx );
     525             : 
     526           0 :   fd_sysvar_recent_hashes_update( slot_ctx, runtime_spad );
     527             : 
     528           0 :   if( !FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, disable_fees_sysvar) )
     529           0 :     fd_sysvar_fees_update(slot_ctx);
     530             : 
     531           0 :   ulong fees = 0UL;
     532           0 :   ulong burn = 0UL;
     533           0 :   if( FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, reward_full_priority_fee ) ) {
     534           0 :     ulong half_fee = slot_ctx->slot_bank.collected_execution_fees / 2;
     535           0 :     fees = fd_ulong_sat_add( slot_ctx->slot_bank.collected_priority_fees, slot_ctx->slot_bank.collected_execution_fees - half_fee );
     536           0 :     burn = half_fee;
     537           0 :   } else {
     538           0 :     ulong total_fees = fd_ulong_sat_add( slot_ctx->slot_bank.collected_execution_fees, slot_ctx->slot_bank.collected_priority_fees );
     539           0 :     ulong half_fee = total_fees / 2;
     540           0 :     fees = total_fees - half_fee;
     541           0 :     burn = half_fee;
     542           0 :   }
     543           0 :   if( FD_LIKELY( fees ) ) {
     544             :     // Look at collect_fees... I think this was where I saw the fee payout..
     545           0 :     FD_TXN_ACCOUNT_DECL( rec );
     546             : 
     547           0 :     do {
     548             :       /* do_create=1 because we might wanna pay fees to a leader
     549             :          account that we've purged due to 0 balance. */
     550           0 :       fd_pubkey_t const * leader = fd_epoch_leaders_get( fd_exec_epoch_ctx_leaders( slot_ctx->epoch_ctx ), slot_ctx->slot_bank.slot );
     551           0 :       int err = fd_acc_mgr_modify( slot_ctx->acc_mgr, slot_ctx->funk_txn, leader, 1, 0UL, rec );
     552           0 :       if( FD_UNLIKELY(err) ) {
     553           0 :         FD_LOG_WARNING(("fd_runtime_freeze: fd_acc_mgr_modify for leader (%s) failed (%d)", FD_BASE58_ENC_32_ALLOCA( leader ), err));
     554           0 :         burn = fd_ulong_sat_add( burn, fees );
     555           0 :         break;
     556           0 :       }
     557             : 
     558           0 :       if ( FD_LIKELY( FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, validate_fee_collector_account ) ) ) {
     559           0 :         ulong _burn;
     560           0 :         if( FD_UNLIKELY( _burn=fd_runtime_validate_fee_collector( slot_ctx, rec, fees ) ) ) {
     561           0 :           if( FD_UNLIKELY( _burn!=fees ) ) {
     562           0 :             FD_LOG_ERR(( "expected _burn(%lu)==fees(%lu)", _burn, fees ));
     563           0 :           }
     564           0 :           burn = fd_ulong_sat_add( burn, fees );
     565           0 :           FD_LOG_WARNING(("fd_runtime_freeze: burned %lu", fees ));
     566           0 :           break;
     567           0 :         }
     568           0 :       }
     569             : 
     570           0 :       rec->meta->info.lamports += fees;
     571           0 :       rec->meta->slot = slot_ctx->slot_bank.slot;
     572             : 
     573           0 :       slot_ctx->block_rewards.collected_fees = fees;
     574           0 :       slot_ctx->block_rewards.post_balance = rec->meta->info.lamports;
     575           0 :       memcpy( slot_ctx->block_rewards.leader.uc, leader->uc, sizeof(fd_hash_t) );
     576           0 :     } while(0);
     577             : 
     578           0 :     ulong old = slot_ctx->slot_bank.capitalization;
     579           0 :     slot_ctx->slot_bank.capitalization = fd_ulong_sat_sub( slot_ctx->slot_bank.capitalization, burn);
     580           0 :     FD_LOG_DEBUG(( "fd_runtime_freeze: burn %lu, capitalization %lu->%lu ", burn, old, slot_ctx->slot_bank.capitalization));
     581             : 
     582           0 :     slot_ctx->slot_bank.collected_execution_fees = 0;
     583           0 :     slot_ctx->slot_bank.collected_priority_fees = 0;
     584           0 :   }
     585             : 
     586           0 :   fd_runtime_run_incinerator( slot_ctx );
     587             : 
     588           0 :   FD_LOG_DEBUG(( "fd_runtime_freeze: capitalization %lu ", slot_ctx->slot_bank.capitalization));
     589           0 :   slot_ctx->slot_bank.collected_rent = 0;
     590           0 : }
     591             : 
     592           0 : #define FD_RENT_EXEMPT (-1L)
     593             : 
     594             : static long
     595             : fd_runtime_get_rent_due( fd_epoch_schedule_t const * schedule,
     596             :                          fd_rent_t const *           rent,
     597             :                          double                      slots_per_year,
     598             :                          fd_account_meta_t *         acc,
     599           0 :                          ulong                       epoch ) {
     600             : 
     601           0 :   fd_solana_account_meta_t *info = &acc->info;
     602             : 
     603             :   /* Nothing due if account is rent-exempt
     604             :      https://github.com/anza-xyz/agave/blob/v2.0.10/sdk/src/rent_collector.rs#L90 */
     605           0 :   ulong min_balance = fd_rent_exempt_minimum_balance( rent, acc->dlen );
     606           0 :   if( info->lamports>=min_balance ) {
     607           0 :     return FD_RENT_EXEMPT;
     608           0 :   }
     609             : 
     610             :   /* Count the number of slots that have passed since last collection. This
     611             :      inlines the agave function get_slots_in_peohc
     612             :      https://github.com/anza-xyz/agave/blob/v2.0.10/sdk/src/rent_collector.rs#L93-L98 */
     613           0 :   ulong slots_elapsed = 0UL;
     614           0 :   if( FD_UNLIKELY( info->rent_epoch<schedule->first_normal_epoch ) ) {
     615             :     /* Count the slots before the first normal epoch separately */
     616           0 :     for( ulong i=info->rent_epoch; i<schedule->first_normal_epoch && i<=epoch; i++ ) {
     617           0 :       slots_elapsed += fd_epoch_slot_cnt( schedule, i+1UL );
     618           0 :     }
     619           0 :     slots_elapsed += fd_ulong_sat_sub( epoch+1UL, schedule->first_normal_epoch ) * schedule->slots_per_epoch;
     620           0 :   }
     621             :   // slots_elapsed should remain 0 if rent_epoch is greater than epoch
     622           0 :   else if( info->rent_epoch<=epoch ) {
     623           0 :     slots_elapsed = (epoch - info->rent_epoch + 1UL) * schedule->slots_per_epoch;
     624           0 :   }
     625             :   /* Consensus-critical use of doubles :( */
     626             : 
     627           0 :   double years_elapsed;
     628           0 :   if( FD_LIKELY( slots_per_year!=0.0 ) ) {
     629           0 :     years_elapsed = (double)slots_elapsed / slots_per_year;
     630           0 :   } else {
     631           0 :     years_elapsed = 0.0;
     632           0 :   }
     633             : 
     634           0 :   ulong lamports_per_year = rent->lamports_per_uint8_year * (acc->dlen + 128UL);
     635             :   /* https://github.com/anza-xyz/agave/blob/d2124a995f89e33c54f41da76bfd5b0bd5820898/sdk/src/rent_collector.rs#L108 */
     636             :   /* https://github.com/anza-xyz/agave/blob/d2124a995f89e33c54f41da76bfd5b0bd5820898/sdk/program/src/rent.rs#L95 */
     637           0 :   return (long)fd_rust_cast_double_to_ulong(years_elapsed * (double)lamports_per_year);
     638           0 : }
     639             : 
     640             : /* https://github.com/anza-xyz/agave/blob/v2.0.10/sdk/src/rent_collector.rs#L117-149 */
     641             : /* Collect rent from an account. Returns the amount of rent collected. */
     642             : static ulong
     643             : fd_runtime_collect_from_existing_account( ulong                       slot,
     644             :                                           fd_epoch_schedule_t const * schedule,
     645             :                                           fd_rent_t const *           rent,
     646             :                                           double                      slots_per_year,
     647             :                                           fd_account_meta_t *         acc,
     648             :                                           fd_pubkey_t const *         pubkey,
     649           0 :                                           ulong                       epoch ) {
     650           0 :   ulong collected_rent = 0UL;
     651           0 :   #define NO_RENT_COLLECTION_NOW (-1)
     652           0 :   #define EXEMPT                 (-2)
     653           0 :   #define COLLECT_RENT           (-3)
     654             : 
     655             :   /* An account must be hashed regardless of if rent is collected from it. */
     656           0 :   acc->slot = slot;
     657             : 
     658             :   /* Inlining calculate_rent_result
     659             :      https://github.com/anza-xyz/agave/blob/v2.0.10/sdk/src/rent_collector.rs#L153-184 */
     660           0 :   int calculate_rent_result = COLLECT_RENT;
     661             : 
     662             :   /* RentResult::NoRentCollectionNow */
     663           0 :   if( FD_LIKELY( acc->info.rent_epoch==FD_RENT_EXEMPT_RENT_EPOCH || acc->info.rent_epoch>epoch ) ) {
     664           0 :     calculate_rent_result = NO_RENT_COLLECTION_NOW;
     665           0 :     goto rent_calculation;
     666           0 :   }
     667             :   /* RentResult::Exempt */
     668             :   /* Inlining should_collect_rent() */
     669           0 :   int should_collect_rent = !( acc->info.executable ||
     670           0 :                                !memcmp( pubkey, &fd_sysvar_incinerator_id, sizeof(fd_pubkey_t) ) );
     671           0 :   if( !should_collect_rent ) {
     672           0 :     calculate_rent_result = EXEMPT;
     673           0 :     goto rent_calculation;
     674           0 :   }
     675             : 
     676             :   /* https://github.com/anza-xyz/agave/blob/v2.0.10/sdk/src/rent_collector.rs#L167-180 */
     677           0 :   long rent_due = fd_runtime_get_rent_due( schedule,
     678           0 :                                            rent,
     679           0 :                                            slots_per_year,
     680           0 :                                            acc,
     681           0 :                                            epoch );
     682           0 :   if( rent_due==FD_RENT_EXEMPT ) {
     683           0 :     calculate_rent_result = EXEMPT;
     684           0 :   } else if( rent_due==0L ) {
     685           0 :     calculate_rent_result = NO_RENT_COLLECTION_NOW;
     686           0 :   } else {
     687           0 :     calculate_rent_result = COLLECT_RENT;
     688           0 :   }
     689             : 
     690           0 :   rent_calculation:
     691           0 :   switch( calculate_rent_result ) {
     692           0 :     case EXEMPT:
     693           0 :       acc->info.rent_epoch = FD_RENT_EXEMPT_RENT_EPOCH;
     694           0 :       break;
     695           0 :     case NO_RENT_COLLECTION_NOW:
     696           0 :       break;
     697           0 :     case COLLECT_RENT:
     698           0 :       if( FD_UNLIKELY( (ulong)rent_due>=acc->info.lamports ) ) {
     699             :         /* Reclaim account */
     700           0 :         collected_rent += (ulong)acc->info.lamports;
     701           0 :         acc->info.lamports                  = 0UL;
     702           0 :         acc->dlen                           = 0UL;
     703           0 :         fd_memset( acc->info.owner, 0, sizeof(acc->info.owner) );
     704           0 :       } else {
     705           0 :         collected_rent += (ulong)rent_due;
     706           0 :         acc->info.lamports                 -= (ulong)rent_due;
     707           0 :         acc->info.rent_epoch                = epoch+1UL;
     708           0 :       }
     709           0 :   }
     710             : 
     711           0 :   return collected_rent;
     712             : 
     713           0 :   #undef NO_RENT_COLLECTION_NOW
     714           0 :   #undef EXEMPT
     715           0 :   #undef COLLECT_RENT
     716           0 : }
     717             : 
     718             : 
     719             : /* fd_runtime_collect_rent_from_account performs rent collection duties.
     720             :    Although the Solana runtime prevents the creation of new accounts
     721             :    that are subject to rent, some older accounts are still undergo the
     722             :    rent collection process.  Updates the account's 'rent_epoch' if
     723             :    needed. Returns the amount of rent collected. */
     724             : /* https://github.com/anza-xyz/agave/blob/v2.0.10/svm/src/account_loader.rs#L71-96 */
     725             : ulong
     726             : fd_runtime_collect_rent_from_account( ulong                       slot,
     727             :                                       fd_epoch_schedule_t const * schedule,
     728             :                                       fd_rent_t const *           rent,
     729             :                                       double                      slots_per_year,
     730             :                                       fd_features_t *             features,
     731             :                                       fd_account_meta_t *         acc,
     732             :                                       fd_pubkey_t const *         key,
     733           0 :                                       ulong                       epoch ) {
     734             : 
     735           0 :   if( !FD_FEATURE_ACTIVE( slot, *features, disable_rent_fees_collection ) ) {
     736           0 :     return fd_runtime_collect_from_existing_account( slot,
     737           0 :                                                      schedule,
     738           0 :                                                      rent,
     739           0 :                                                      slots_per_year,
     740           0 :                                                      acc,
     741           0 :                                                      key,
     742           0 :                                                      epoch );
     743           0 :   } else {
     744           0 :     if( FD_UNLIKELY( acc->info.rent_epoch!=FD_RENT_EXEMPT_RENT_EPOCH &&
     745           0 :                      fd_runtime_get_rent_due( schedule,
     746           0 :                                               rent,
     747           0 :                                               slots_per_year,
     748           0 :                                               acc,
     749           0 :                                               epoch )==FD_RENT_EXEMPT ) ) {
     750           0 :       acc->info.rent_epoch = ULONG_MAX;
     751           0 :     }
     752           0 :   }
     753           0 :   return 0UL;
     754           0 : }
     755             : 
     756             : #undef FD_RENT_EXEMPT
     757             : 
     758             : void
     759             : fd_runtime_write_transaction_status( fd_capture_ctx_t * capture_ctx,
     760             :                                      fd_exec_slot_ctx_t * slot_ctx,
     761             :                                      fd_exec_txn_ctx_t * txn_ctx,
     762           0 :                                      int exec_txn_err) {
     763             :   /* Look up solana-side transaction status details */
     764           0 :   fd_blockstore_t * blockstore = slot_ctx->blockstore;
     765           0 :   uchar * sig = (uchar *)txn_ctx->_txn_raw->raw + txn_ctx->txn_descriptor->signature_off;
     766           0 :   fd_txn_map_t * txn_map_entry = fd_blockstore_txn_query( blockstore, sig );
     767           0 :   if( FD_LIKELY( txn_map_entry != NULL ) ) {
     768           0 :     void * meta = fd_wksp_laddr_fast( fd_blockstore_wksp( blockstore ), txn_map_entry->meta_gaddr );
     769             : 
     770           0 :     fd_solblock_TransactionStatusMeta txn_status = {0};
     771             :     /* Need to handle case for ledgers where transaction status is not available.
     772             :         This case will be handled in fd_solcap_diff. */
     773           0 :     ulong fd_cus_consumed     = txn_ctx->compute_unit_limit - txn_ctx->compute_meter;
     774           0 :     ulong solana_cus_consumed = ULONG_MAX;
     775           0 :     ulong solana_txn_err      = ULONG_MAX;
     776           0 :     if( FD_LIKELY( meta != NULL ) ) {
     777           0 :       pb_istream_t stream = pb_istream_from_buffer( meta, txn_map_entry->meta_sz );
     778           0 :       if ( pb_decode( &stream, fd_solblock_TransactionStatusMeta_fields, &txn_status ) == false ) {
     779           0 :         FD_LOG_WARNING(("no txn_status decoding found sig=%s (%s)", FD_BASE58_ENC_64_ALLOCA( sig ), PB_GET_ERROR(&stream)));
     780           0 :       }
     781           0 :       if ( txn_status.has_compute_units_consumed ) {
     782           0 :         solana_cus_consumed = txn_status.compute_units_consumed;
     783           0 :       }
     784           0 :       if ( txn_status.has_err ) {
     785           0 :         solana_txn_err = txn_status.err.err->bytes[0];
     786           0 :       }
     787             : 
     788           0 :       fd_solcap_Transaction txn = {
     789           0 :         .slot            = slot_ctx->slot_bank.slot,
     790           0 :         .fd_txn_err      = exec_txn_err,
     791           0 :         .fd_custom_err   = txn_ctx->custom_err,
     792           0 :         .solana_txn_err  = solana_txn_err,
     793           0 :         .fd_cus_used     = fd_cus_consumed,
     794           0 :         .solana_cus_used = solana_cus_consumed,
     795           0 :         .instr_err_idx = txn_ctx->instr_err_idx == INT_MAX ? -1 : txn_ctx->instr_err_idx,
     796           0 :       };
     797           0 :       memcpy( txn.txn_sig, sig, sizeof(fd_signature_t) );
     798             : 
     799           0 :       fd_exec_instr_ctx_t const * failed_instr = txn_ctx->failed_instr;
     800           0 :       if( failed_instr ) {
     801           0 :         FD_TEST( failed_instr->depth < 4 );
     802           0 :         txn.instr_err               = failed_instr->instr_err;
     803           0 :         txn.failed_instr_path_count = failed_instr->depth + 1;
     804           0 :         for( long j = failed_instr->depth; j>=0L; j-- ) {
     805           0 :           txn.failed_instr_path[j] = failed_instr->index;
     806           0 :           failed_instr             = failed_instr->parent;
     807           0 :         }
     808           0 :       }
     809             : 
     810           0 :       fd_solcap_write_transaction2( capture_ctx->capture, &txn );
     811           0 :     }
     812           0 :   }
     813           0 : }
     814             : 
     815             : static bool
     816           0 : encode_return_data( pb_ostream_t *stream, const pb_field_t *field, void * const *arg ) {
     817           0 :   fd_exec_txn_ctx_t * txn_ctx = (fd_exec_txn_ctx_t *)(*arg);
     818           0 :   pb_encode_tag_for_field(stream, field);
     819           0 :   pb_encode_string(stream, txn_ctx->return_data.data, txn_ctx->return_data.len );
     820           0 :   return 1;
     821           0 : }
     822             : 
     823             : static ulong
     824           0 : fd_txn_copy_meta( fd_exec_txn_ctx_t * txn_ctx, uchar * dest, ulong dest_sz ) {
     825           0 :   fd_solblock_TransactionStatusMeta txn_status = {0};
     826             : 
     827           0 :   txn_status.has_fee = 1;
     828           0 :   txn_status.fee = txn_ctx->execution_fee + txn_ctx->priority_fee;
     829             : 
     830           0 :   txn_status.has_compute_units_consumed = 1;
     831           0 :   txn_status.compute_units_consumed = txn_ctx->compute_unit_limit - txn_ctx->compute_meter;
     832             : 
     833           0 :   ulong readonly_cnt = 0;
     834           0 :   ulong writable_cnt = 0;
     835           0 :   if( txn_ctx->txn_descriptor->transaction_version == FD_TXN_V0 ) {
     836           0 :     fd_txn_acct_addr_lut_t const * addr_luts = fd_txn_get_address_tables_const( txn_ctx->txn_descriptor );
     837           0 :     for( ulong i = 0; i < txn_ctx->txn_descriptor->addr_table_lookup_cnt; i++ ) {
     838           0 :       fd_txn_acct_addr_lut_t const * addr_lut = &addr_luts[i];
     839           0 :       readonly_cnt += addr_lut->readonly_cnt;
     840           0 :       writable_cnt += addr_lut->writable_cnt;
     841           0 :     }
     842           0 :   }
     843             : 
     844           0 :   typedef PB_BYTES_ARRAY_T(32) my_ba_t;
     845           0 :   typedef union { my_ba_t my; pb_bytes_array_t normal; } union_ba_t;
     846           0 :   union_ba_t writable_ba[writable_cnt];
     847           0 :   pb_bytes_array_t * writable_baptr[writable_cnt];
     848           0 :   txn_status.loaded_writable_addresses_count = (uint)writable_cnt;
     849           0 :   txn_status.loaded_writable_addresses = writable_baptr;
     850           0 :   ulong idx2 = txn_ctx->txn_descriptor->acct_addr_cnt;
     851           0 :   for (ulong idx = 0; idx < writable_cnt; idx++) {
     852           0 :     pb_bytes_array_t * ba = writable_baptr[ idx ] = &writable_ba[ idx ].normal;
     853           0 :     ba->size = 32;
     854           0 :     fd_memcpy(ba->bytes, &txn_ctx->account_keys[idx2++], 32);
     855           0 :   }
     856             : 
     857           0 :   union_ba_t readonly_ba[readonly_cnt];
     858           0 :   pb_bytes_array_t * readonly_baptr[readonly_cnt];
     859           0 :   txn_status.loaded_readonly_addresses_count = (uint)readonly_cnt;
     860           0 :   txn_status.loaded_readonly_addresses = readonly_baptr;
     861           0 :   for (ulong idx = 0; idx < readonly_cnt; idx++) {
     862           0 :     pb_bytes_array_t * ba = readonly_baptr[ idx ] = &readonly_ba[ idx ].normal;
     863           0 :     ba->size = 32;
     864           0 :     fd_memcpy(ba->bytes, &txn_ctx->account_keys[idx2++], 32);
     865           0 :   }
     866           0 :   ulong acct_cnt = txn_ctx->accounts_cnt;
     867           0 :   FD_TEST(acct_cnt == idx2);
     868             : 
     869           0 :   txn_status.pre_balances_count = txn_status.post_balances_count = (pb_size_t)acct_cnt;
     870           0 :   uint64_t pre_balances[acct_cnt];
     871           0 :   txn_status.pre_balances = pre_balances;
     872           0 :   uint64_t post_balances[acct_cnt];
     873           0 :   txn_status.post_balances = post_balances;
     874             : 
     875           0 :   for (ulong idx = 0; idx < acct_cnt; idx++) {
     876           0 :     fd_txn_account_t const * acct = &txn_ctx->accounts[idx];
     877           0 :     ulong                    pre  = ( acct->starting_lamports == ULONG_MAX ? 0UL : acct->starting_lamports );
     878             : 
     879           0 :     pre_balances[idx]  = pre;
     880           0 :     post_balances[idx] = ( acct->meta ? acct->meta->info.lamports :
     881           0 :                            ( acct->orig_meta ? acct->orig_meta->info.lamports : pre ) );
     882           0 :   }
     883             : 
     884           0 :   if( txn_ctx->return_data.len ) {
     885           0 :     txn_status.has_return_data = 1;
     886           0 :     txn_status.return_data.has_program_id = 1;
     887           0 :     fd_memcpy( txn_status.return_data.program_id, txn_ctx->return_data.program_id.uc, 32U );
     888           0 :     pb_callback_t data = { .funcs.encode = encode_return_data, .arg = txn_ctx };
     889           0 :     txn_status.return_data.data = data;
     890           0 :   }
     891             : 
     892           0 :   union {
     893           0 :     pb_bytes_array_t arr;
     894           0 :     uchar space[64];
     895           0 :   } errarr;
     896           0 :   pb_byte_t * errptr = errarr.arr.bytes;
     897           0 :   if( txn_ctx->custom_err != UINT_MAX ) {
     898           0 :     *(uint*)errptr = 8 /* Instruction error */;
     899           0 :     errptr += sizeof(uint);
     900           0 :     *errptr = (uchar)txn_ctx->instr_err_idx;
     901           0 :     errptr += 1;
     902           0 :     *(int*)errptr = FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
     903           0 :     errptr += sizeof(int);
     904           0 :     *(uint*)errptr = txn_ctx->custom_err;
     905           0 :     errptr += sizeof(uint);
     906           0 :     errarr.arr.size = (uint)(errptr - errarr.arr.bytes);
     907           0 :     txn_status.has_err = 1;
     908           0 :     txn_status.err.err = &errarr.arr;
     909           0 :   } else if( txn_ctx->exec_err ) {
     910           0 :     switch( txn_ctx->exec_err_kind ) {
     911           0 :       case FD_EXECUTOR_ERR_KIND_SYSCALL:
     912           0 :         break;
     913           0 :       case FD_EXECUTOR_ERR_KIND_INSTR:
     914           0 :         *(uint*)errptr = 8 /* Instruction error */;
     915           0 :         errptr += sizeof(uint);
     916           0 :         *errptr = (uchar)txn_ctx->instr_err_idx;
     917           0 :         errptr += 1;
     918           0 :         *(int*)errptr = txn_ctx->exec_err;
     919           0 :         errptr += sizeof(int);
     920           0 :         errarr.arr.size = (uint)(errptr - errarr.arr.bytes);
     921           0 :         txn_status.has_err = 1;
     922           0 :         txn_status.err.err = &errarr.arr;
     923           0 :         break;
     924           0 :       case FD_EXECUTOR_ERR_KIND_EBPF:
     925           0 :         break;
     926           0 :     }
     927           0 :   }
     928             : 
     929           0 :   if( dest == NULL ) {
     930           0 :     size_t sz = 0;
     931           0 :     bool r = pb_get_encoded_size( &sz, fd_solblock_TransactionStatusMeta_fields, &txn_status );
     932           0 :     if( !r ) {
     933           0 :       FD_LOG_WARNING(( "pb_get_encoded_size failed" ));
     934           0 :       return 0;
     935           0 :     }
     936           0 :     return sz + txn_ctx->log_collector.buf_sz;
     937           0 :   }
     938             : 
     939           0 :   pb_ostream_t stream = pb_ostream_from_buffer( dest, dest_sz );
     940           0 :   bool r = pb_encode( &stream, fd_solblock_TransactionStatusMeta_fields, &txn_status );
     941           0 :   if( !r ) {
     942           0 :     FD_LOG_WARNING(( "pb_encode failed" ));
     943           0 :     return 0;
     944           0 :   }
     945           0 :   pb_write( &stream, txn_ctx->log_collector.buf, txn_ctx->log_collector.buf_sz );
     946           0 :   return stream.bytes_written;
     947           0 : }
     948             : 
     949             : /* fd_runtime_finalize_txns_update_blockstore_meta() updates transaction metadata
     950             :    after execution.
     951             : 
     952             :    Execution recording is controlled by slot_ctx->enable_exec_recording, and this
     953             :    function does nothing if execution recording is off.  The following comments
     954             :    only apply when execution recording is on.
     955             : 
     956             :    Transaction metadata includes execution result (success/error), balance changes,
     957             :    transaction logs, ...  All this info is not part of consensus but can be retrieved,
     958             :    for instace, via RPC getTransaction.  Firedancer stores txn meta in the blockstore,
     959             :    in the same binary format as Agave, protobuf TransactionStatusMeta. */
     960             : static void
     961             : fd_runtime_finalize_txns_update_blockstore_meta( fd_exec_slot_ctx_t *         slot_ctx,
     962             :                                                  fd_execute_txn_task_info_t * task_info,
     963           0 :                                                  ulong                        txn_cnt ) {
     964             :   /* Nothing to do if execution recording is off */
     965           0 :   if( !slot_ctx->enable_exec_recording ) {
     966           0 :     return;
     967           0 :   }
     968             : 
     969           0 :   fd_blockstore_t * blockstore      = slot_ctx->blockstore;
     970           0 :   fd_wksp_t * blockstore_wksp       = fd_blockstore_wksp( blockstore );
     971           0 :   fd_alloc_t * blockstore_alloc     = fd_blockstore_alloc( blockstore );
     972           0 :   fd_txn_map_t * txn_map = fd_blockstore_txn_map( blockstore );
     973             : 
     974             :   /* Get the total size of all logs */
     975           0 :   ulong tot_meta_sz = 2*sizeof(ulong);
     976           0 :   for( ulong txn_idx = 0; txn_idx < txn_cnt; txn_idx++ ) {
     977             :     /* Prebalance compensation */
     978           0 :     fd_exec_txn_ctx_t * txn_ctx = task_info[txn_idx].txn_ctx;
     979           0 :     txn_ctx->accounts[0].starting_lamports += (txn_ctx->execution_fee + txn_ctx->priority_fee);
     980             :     /* Get the size without the copy */
     981           0 :     tot_meta_sz += fd_txn_copy_meta( txn_ctx, NULL, 0 );
     982           0 :   }
     983           0 :   uchar * cur_laddr = fd_alloc_malloc( blockstore_alloc, 1, tot_meta_sz );
     984           0 :   if( cur_laddr == NULL ) {
     985           0 :     return;
     986           0 :   }
     987           0 :   uchar * const end_laddr = cur_laddr + tot_meta_sz;
     988             : 
     989             :   /* Link to previous allocation */
     990           0 :   ((ulong*)cur_laddr)[0] = slot_ctx->txns_meta_gaddr;
     991           0 :   ((ulong*)cur_laddr)[1] = slot_ctx->txns_meta_sz;
     992           0 :   slot_ctx->txns_meta_gaddr = fd_wksp_gaddr_fast( blockstore_wksp, cur_laddr );
     993           0 :   slot_ctx->txns_meta_sz    = tot_meta_sz;
     994           0 :   cur_laddr += 2*sizeof(ulong);
     995             : 
     996           0 :   for( ulong txn_idx = 0; txn_idx < txn_cnt; txn_idx++ ) {
     997           0 :     fd_exec_txn_ctx_t * txn_ctx = task_info[txn_idx].txn_ctx;
     998           0 :     ulong meta_sz = fd_txn_copy_meta( txn_ctx, cur_laddr, (size_t)(end_laddr - cur_laddr) );
     999           0 :     if( meta_sz ) {
    1000           0 :       ulong  meta_gaddr = fd_wksp_gaddr_fast( blockstore_wksp, cur_laddr );
    1001             : 
    1002             :       /* Update all the signatures */
    1003           0 :       char const * sig_p = (char const *)txn_ctx->_txn_raw->raw + txn_ctx->txn_descriptor->signature_off;
    1004           0 :       fd_txn_key_t sig;
    1005           0 :       for( uchar i=0U; i<txn_ctx->txn_descriptor->signature_cnt; i++ ) {
    1006           0 :         fd_memcpy( &sig, sig_p, sizeof(fd_txn_key_t) );
    1007           0 :         fd_txn_map_t * txn_map_entry = fd_txn_map_query( txn_map, &sig, NULL );
    1008           0 :         if( FD_LIKELY( txn_map_entry ) ) {
    1009           0 :           txn_map_entry->meta_gaddr = meta_gaddr;
    1010           0 :           txn_map_entry->meta_sz    = meta_sz;
    1011           0 :         }
    1012           0 :         sig_p += FD_ED25519_SIG_SZ;
    1013           0 :       }
    1014             : 
    1015           0 :       cur_laddr += meta_sz;
    1016           0 :     }
    1017           0 :     fd_log_collector_delete( &txn_ctx->log_collector );
    1018           0 :   }
    1019             : 
    1020           0 :   FD_TEST( cur_laddr == end_laddr );
    1021           0 : }
    1022             : 
    1023             : /******************************************************************************/
    1024             : /* Block-Level Execution Preparation/Finalization                             */
    1025             : /******************************************************************************/
    1026             : 
    1027             : static int
    1028             : fd_runtime_block_sysvar_update_pre_execute( fd_exec_slot_ctx_t * slot_ctx,
    1029           0 :                                             fd_spad_t *          runtime_spad ) {
    1030             :   // let (fee_rate_governor, fee_components_time_us) = measure_us!(
    1031             :   //     FeeRateGovernor::new_derived(&parent.fee_rate_governor, parent.signature_count())
    1032             :   // );
    1033             :   /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1312-L1314 */
    1034           0 :   fd_sysvar_fees_new_derived( slot_ctx,
    1035           0 :                               slot_ctx->slot_bank.fee_rate_governor,
    1036           0 :                               slot_ctx->slot_bank.parent_signature_cnt );
    1037             : 
    1038             :   // TODO: move all these out to a fd_sysvar_update() call...
    1039           0 :   long clock_update_time      = -fd_log_wallclock();
    1040           0 :   fd_sysvar_clock_update( slot_ctx, runtime_spad );
    1041           0 :   clock_update_time          += fd_log_wallclock();
    1042           0 :   double clock_update_time_ms = (double)clock_update_time * 1e-6;
    1043           0 :   FD_LOG_INFO(( "clock updated - slot: %lu, elapsed: %6.6f ms", slot_ctx->slot_bank.slot, clock_update_time_ms ));
    1044           0 :   if( !FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, disable_fees_sysvar ) ) {
    1045           0 :     fd_sysvar_fees_update(slot_ctx);
    1046           0 :   }
    1047             :   // It has to go into the current txn previous info but is not in slot 0
    1048           0 :   if( slot_ctx->slot_bank.slot != 0 ) {
    1049           0 :     fd_sysvar_slot_hashes_update( slot_ctx, runtime_spad );
    1050           0 :   }
    1051           0 :   fd_sysvar_last_restart_slot_update( slot_ctx );
    1052             : 
    1053           0 :   return 0;
    1054           0 : }
    1055             : 
    1056             : int
    1057             : fd_runtime_microblock_verify_ticks( fd_exec_slot_ctx_t *        slot_ctx,
    1058             :                                     ulong                       slot,
    1059             :                                     fd_microblock_hdr_t const * hdr,
    1060             :                                     bool               slot_complete,
    1061             :                                     ulong              tick_height,
    1062             :                                     ulong              max_tick_height,
    1063           0 :                                     ulong              hashes_per_tick ) {
    1064           0 :   ulong invalid_tick_hash_count = 0UL;
    1065           0 :   ulong has_trailing_entry      = 0UL;
    1066             : 
    1067             :   /*
    1068             :     In order to mimic the order of checks in Agave,
    1069             :     we cache the results of some checks but do not immediately return
    1070             :     an error.
    1071             :   */
    1072           0 :   fd_block_map_query_t quer[1];
    1073           0 :   int err = fd_block_map_prepare( slot_ctx->blockstore->block_map, &slot, NULL, quer, FD_MAP_FLAG_BLOCKING );
    1074           0 :   fd_block_info_t * query = fd_block_map_query_ele( quer );
    1075           0 :   if( FD_UNLIKELY( err || query->slot != slot ) ) {
    1076           0 :     FD_LOG_ERR(( "fd_runtime_microblock_verify_ticks: fd_block_map_prepare on %lu failed", slot ));
    1077           0 :   }
    1078             : 
    1079           0 :   query->tick_hash_count_accum = fd_ulong_sat_add( query->tick_hash_count_accum, hdr->hash_cnt );
    1080           0 :   if( hdr->txn_cnt == 0UL ) {
    1081           0 :     query->ticks_consumed++;
    1082           0 :     if( FD_LIKELY( hashes_per_tick > 1UL ) ) {
    1083           0 :       if( FD_UNLIKELY( query->tick_hash_count_accum != hashes_per_tick ) ) {
    1084           0 :         FD_LOG_WARNING(( "tick_hash_count %lu hashes_per_tick %lu tick_count %lu", query->tick_hash_count_accum, hashes_per_tick, query->ticks_consumed ));
    1085           0 :         invalid_tick_hash_count = 1U;
    1086           0 :       }
    1087           0 :     }
    1088           0 :     query->tick_hash_count_accum = 0UL;
    1089           0 :   } else {
    1090             :     /* This wasn't a tick entry, but it's the last entry. */
    1091           0 :     if( FD_UNLIKELY( slot_complete ) ) {
    1092           0 :       FD_LOG_WARNING(( "last has %lu transactions expects 0", hdr->txn_cnt ));
    1093           0 :       has_trailing_entry = 1U;
    1094           0 :     }
    1095           0 :   }
    1096             : 
    1097           0 :   ulong next_tick_height = tick_height + query->ticks_consumed;
    1098           0 :   fd_block_map_publish( quer );
    1099             : 
    1100           0 :   if( FD_UNLIKELY( next_tick_height > max_tick_height ) ) {
    1101           0 :     FD_LOG_WARNING(( "Too many ticks tick_height %lu max_tick_height %lu hashes_per_tick %lu tick_count %lu", tick_height, max_tick_height, hashes_per_tick, query->ticks_consumed ));
    1102           0 :     return FD_BLOCK_ERR_TOO_MANY_TICKS;
    1103           0 :   }
    1104           0 :   if( FD_UNLIKELY( slot_complete && next_tick_height < max_tick_height ) ) {
    1105           0 :     FD_LOG_WARNING(( "Too few ticks" ));
    1106           0 :     return FD_BLOCK_ERR_TOO_FEW_TICKS;
    1107           0 :   }
    1108           0 :   if( FD_UNLIKELY( slot_complete && has_trailing_entry ) ) {
    1109           0 :     FD_LOG_WARNING(( "Did not end with a tick" ));
    1110           0 :     return FD_BLOCK_ERR_TRAILING_ENTRY;
    1111           0 :   }
    1112             : 
    1113             :   /* Not returning FD_BLOCK_ERR_INVALID_LAST_TICK because we assume the
    1114             :      slot is full. */
    1115             : 
    1116             :   /* Don't care about low power hashing or no hashing. */
    1117           0 :   if( FD_LIKELY( hashes_per_tick > 1UL ) ) {
    1118           0 :     if( FD_UNLIKELY( invalid_tick_hash_count ) ) {
    1119           0 :       FD_LOG_WARNING(( "Tick with invalid number of hashes found" ));
    1120           0 :       return FD_BLOCK_ERR_INVALID_TICK_HASH_COUNT;
    1121           0 :     }
    1122           0 :   }
    1123           0 :   return FD_BLOCK_OK;
    1124           0 : }
    1125             : 
    1126             : /* A streaming version of this by batch is implemented in batch_verify_ticks.
    1127             :    This block_verify_ticks should only used for offline replay. */
    1128             : ulong
    1129             : fd_runtime_block_verify_ticks( fd_blockstore_t * blockstore,
    1130             :                                ulong             slot,
    1131             :                                uchar *           block_data,
    1132             :                                ulong             block_data_sz,
    1133             :                                ulong             tick_height,
    1134             :                                ulong             max_tick_height,
    1135           0 :                                ulong             hashes_per_tick ) {
    1136           0 :   ulong tick_count              = 0UL;
    1137           0 :   ulong tick_hash_count         = 0UL;
    1138           0 :   ulong has_trailing_entry      = 0UL;
    1139           0 :   uchar invalid_tick_hash_count = 0U;
    1140             :   /*
    1141             :     Iterate over microblocks/entries to
    1142             :     (1) count the number of ticks
    1143             :     (2) check whether the last entry is a tick
    1144             :     (3) check whether ticks align with hashes per tick
    1145             : 
    1146             :     This precomputes everything we need in a single loop over the array.
    1147             :     In order to mimic the order of checks in Agave,
    1148             :     we cache the results of some checks but do not immediately return
    1149             :     an error.
    1150             :    */
    1151           0 :   ulong slot_complete_idx = FD_SHRED_IDX_NULL;
    1152           0 :   fd_block_set_t data_complete_idxs[FD_SHRED_MAX_PER_SLOT / sizeof(ulong)];
    1153           0 :   int err = FD_MAP_ERR_AGAIN;
    1154           0 :   while( err == FD_MAP_ERR_AGAIN ) {
    1155           0 :     fd_block_map_query_t quer[1] = {0};
    1156           0 :     err = fd_block_map_query_try( blockstore->block_map, &slot, NULL, quer, 0 );
    1157           0 :     fd_block_info_t * query = fd_block_map_query_ele( quer );
    1158           0 :     if( FD_UNLIKELY( err == FD_MAP_ERR_AGAIN ) )continue;
    1159           0 :     if( FD_UNLIKELY( err == FD_MAP_ERR_KEY ) ) FD_LOG_ERR(( "fd_runtime_block_verify_ticks: fd_block_map_query_try failed" ));
    1160           0 :     slot_complete_idx = query->slot_complete_idx;
    1161           0 :     fd_memcpy( data_complete_idxs, query->data_complete_idxs, sizeof(data_complete_idxs) );
    1162           0 :     err = fd_block_map_query_test( quer );
    1163           0 :   }
    1164             : 
    1165           0 :   uint   batch_cnt = 0;
    1166           0 :   ulong  batch_idx = 0;
    1167           0 :   while ( batch_idx <= slot_complete_idx ) {
    1168           0 :     batch_cnt++;
    1169           0 :     ulong batch_sz = 0;
    1170           0 :     uint  end_idx  = (uint)fd_block_set_const_iter_next( data_complete_idxs, batch_idx - 1 );
    1171           0 :     FD_TEST( fd_blockstore_slice_query( blockstore, slot, (uint) batch_idx, end_idx, block_data_sz, block_data, &batch_sz ) == FD_BLOCKSTORE_SUCCESS );
    1172           0 :     ulong micro_cnt = FD_LOAD( ulong, block_data );
    1173           0 :     ulong off       = sizeof(ulong);
    1174           0 :     for( ulong i = 0UL; i < micro_cnt; i++ ){
    1175           0 :       fd_microblock_hdr_t const * hdr = fd_type_pun_const( ( block_data + off ) );
    1176           0 :       off += sizeof(fd_microblock_hdr_t);
    1177           0 :       tick_hash_count = fd_ulong_sat_add( tick_hash_count, hdr->hash_cnt );
    1178           0 :       if( hdr->txn_cnt == 0UL ){
    1179           0 :         tick_count++;
    1180           0 :         if( FD_LIKELY( hashes_per_tick > 1UL ) ) {
    1181           0 :           if( FD_UNLIKELY( tick_hash_count != hashes_per_tick ) ) {
    1182           0 :             FD_LOG_WARNING(( "tick_hash_count %lu hashes_per_tick %lu tick_count %lu i %lu micro_cnt %lu", tick_hash_count, hashes_per_tick, tick_count, i, micro_cnt ));
    1183           0 :             invalid_tick_hash_count = 1U;
    1184           0 :           }
    1185           0 :         }
    1186           0 :         tick_hash_count = 0UL;
    1187           0 :         continue;
    1188           0 :       }
    1189             :       /* This wasn't a tick entry, but it's the last entry. */
    1190           0 :       if( FD_UNLIKELY( i == micro_cnt - 1UL ) ) {
    1191           0 :         has_trailing_entry = batch_cnt;
    1192           0 :       }
    1193             : 
    1194             :       /* seek past txns */
    1195           0 :       uchar txn[FD_TXN_MAX_SZ];
    1196           0 :       for( ulong j = 0; j < hdr->txn_cnt; j++ ) {
    1197           0 :         ulong pay_sz = 0;
    1198           0 :         ulong txn_sz = fd_txn_parse_core( block_data + off, fd_ulong_min( batch_sz - off, FD_TXN_MTU ), txn, NULL, &pay_sz );
    1199           0 :         if( FD_UNLIKELY( !pay_sz ) ) FD_LOG_ERR(( "failed to parse transaction %lu in microblock %lu in slot %lu", j, i, slot ) );
    1200           0 :         if( FD_UNLIKELY( !txn_sz || txn_sz > FD_TXN_MTU )) FD_LOG_ERR(( "failed to parse transaction %lu in microblock %lu in slot %lu. txn size: %lu", j, i, slot, txn_sz ));
    1201           0 :         off += pay_sz;
    1202           0 :       }
    1203           0 :     }
    1204             :     /* advance batch iterator */
    1205           0 :     if( FD_UNLIKELY( batch_cnt == 1 ) ){ /* first batch */
    1206           0 :       batch_idx = fd_block_set_const_iter_init( data_complete_idxs ) + 1;
    1207           0 :     } else {
    1208           0 :       batch_idx = fd_block_set_const_iter_next( data_complete_idxs, batch_idx - 1 ) + 1;
    1209           0 :     }
    1210           0 :   }
    1211             : 
    1212           0 :   ulong next_tick_height = tick_height + tick_count;
    1213           0 :   if( FD_UNLIKELY( next_tick_height > max_tick_height ) ) {
    1214           0 :     FD_LOG_WARNING(( "Too many ticks tick_height %lu max_tick_height %lu hashes_per_tick %lu tick_count %lu", tick_height, max_tick_height, hashes_per_tick, tick_count ));
    1215           0 :     FD_LOG_WARNING(( "Too many ticks" ));
    1216           0 :     return FD_BLOCK_ERR_TOO_MANY_TICKS;
    1217           0 :   }
    1218           0 :   if( FD_UNLIKELY( next_tick_height < max_tick_height ) ) {
    1219           0 :     FD_LOG_WARNING(( "Too few ticks" ));
    1220           0 :     return FD_BLOCK_ERR_TOO_FEW_TICKS;
    1221           0 :   }
    1222           0 :   if( FD_UNLIKELY( has_trailing_entry == batch_cnt ) ) {
    1223           0 :     FD_LOG_WARNING(( "Did not end with a tick" ));
    1224           0 :     return FD_BLOCK_ERR_TRAILING_ENTRY;
    1225           0 :   }
    1226             : 
    1227             :   /* Not returning FD_BLOCK_ERR_INVALID_LAST_TICK because we assume the
    1228             :      slot is full. */
    1229             : 
    1230             :   /* Don't care about low power hashing or no hashing. */
    1231           0 :   if( FD_LIKELY( hashes_per_tick > 1UL ) ) {
    1232           0 :     if( FD_UNLIKELY( invalid_tick_hash_count ) ) {
    1233           0 :       FD_LOG_WARNING(( "Tick with invalid number of hashes found" ));
    1234           0 :       return FD_BLOCK_ERR_INVALID_TICK_HASH_COUNT;
    1235           0 :     }
    1236           0 :   }
    1237             : 
    1238           0 :   return FD_BLOCK_OK;
    1239           0 : }
    1240             : 
    1241             : void
    1242           0 : fd_runtime_poh_verify( fd_poh_verifier_t * poh_info ) {
    1243             : 
    1244           0 :   fd_hash_t working_hash = *(poh_info->in_poh_hash);
    1245           0 :   fd_hash_t init_hash    = working_hash;
    1246             : 
    1247           0 :   fd_microblock_hdr_t const * hdr = poh_info->microblock.hdr;
    1248           0 :   ulong               microblk_sz = poh_info->microblk_max_sz;
    1249             : 
    1250           0 :   if( !hdr->txn_cnt ){
    1251           0 :     fd_poh_append( &working_hash, hdr->hash_cnt );
    1252           0 :   } else { /* not a tick, regular microblock */
    1253           0 :     if( hdr->hash_cnt ){
    1254           0 :       fd_poh_append( &working_hash, hdr->hash_cnt - 1 );
    1255           0 :     }
    1256             : 
    1257           0 :     ulong leaf_cnt_max = FD_TXN_ACTUAL_SIG_MAX * hdr->txn_cnt;
    1258             : 
    1259           0 :     FD_SPAD_FRAME_BEGIN( poh_info->spad ) {
    1260           0 :       uchar *               commit = fd_spad_alloc( poh_info->spad, FD_WBMTREE32_ALIGN, fd_wbmtree32_footprint(leaf_cnt_max) );
    1261           0 :       fd_wbmtree32_leaf_t * leafs  = fd_spad_alloc( poh_info->spad, alignof(fd_wbmtree32_leaf_t), sizeof(fd_wbmtree32_leaf_t) * leaf_cnt_max );
    1262           0 :       fd_wbmtree32_t *      tree   = fd_wbmtree32_init( commit, leaf_cnt_max );
    1263           0 :       fd_wbmtree32_leaf_t * l      = &leafs[0];
    1264             : 
    1265             :       /* Loop across transactions */
    1266           0 :       ulong leaf_cnt = 0UL;
    1267           0 :       ulong off      = sizeof(fd_microblock_hdr_t);
    1268           0 :       for( ulong txn_idx=0UL; txn_idx<hdr->txn_cnt; txn_idx++ ) {
    1269           0 :         fd_txn_p_t txn_p;
    1270           0 :         ulong pay_sz = 0UL;
    1271           0 :         ulong txn_sz = fd_txn_parse_core( poh_info->microblock.raw + off,
    1272           0 :                                           fd_ulong_min( FD_TXN_MTU, microblk_sz - off ),
    1273           0 :                                           TXN(&txn_p),
    1274           0 :                                           NULL,
    1275           0 :                                           &pay_sz );
    1276           0 :         if( FD_UNLIKELY( !pay_sz || !txn_sz || txn_sz > FD_TXN_MTU )  ) {
    1277           0 :           FD_LOG_ERR(( "failed to parse transaction %lu in replay", txn_idx ));
    1278           0 :         }
    1279             : 
    1280             :         /* Loop across signatures */
    1281           0 :         fd_txn_t const *         txn  = (fd_txn_t const *) txn_p._;
    1282           0 :         fd_ed25519_sig_t const * sigs = (fd_ed25519_sig_t const *)fd_type_pun((poh_info->microblock.raw + off) + (ulong)txn->signature_off);
    1283           0 :         for( ulong j=0UL; j<txn->signature_cnt; j++ ) {
    1284           0 :           l->data     = (uchar *)&sigs[j];
    1285           0 :           l->data_len = sizeof(fd_ed25519_sig_t);
    1286           0 :           l++;
    1287           0 :           leaf_cnt++;
    1288           0 :         }
    1289           0 :         off += pay_sz;
    1290           0 :       }
    1291             : 
    1292           0 :       uchar * mbuf = fd_spad_alloc( poh_info->spad, 1UL, leaf_cnt * (sizeof(fd_ed25519_sig_t) + 1) );
    1293           0 :       fd_wbmtree32_append( tree, leafs, leaf_cnt, mbuf );
    1294           0 :       uchar * root = fd_wbmtree32_fini( tree );
    1295           0 :       fd_poh_mixin( &working_hash, root );
    1296           0 :     } FD_SPAD_FRAME_END;
    1297           0 :   }
    1298             : 
    1299           0 :   if( FD_UNLIKELY( memcmp(hdr->hash, working_hash.hash, sizeof(fd_hash_t)) ) ) {
    1300           0 :     FD_LOG_WARNING(( "poh mismatch (bank: %s, entry: %s, INIT: %s )", FD_BASE58_ENC_32_ALLOCA( working_hash.hash ), FD_BASE58_ENC_32_ALLOCA( hdr->hash ), FD_BASE58_ENC_32_ALLOCA( init_hash.hash ) ));
    1301           0 :     poh_info->success = -1;
    1302           0 :   }
    1303           0 : }
    1304             : 
    1305             : int
    1306             : fd_runtime_block_execute_prepare( fd_exec_slot_ctx_t * slot_ctx,
    1307           0 :                                   fd_spad_t *          runtime_spad ) {
    1308             : 
    1309           0 :   if( slot_ctx->blockstore && slot_ctx->slot_bank.slot != 0UL ) {
    1310           0 :     fd_blockstore_block_height_update( slot_ctx->blockstore,
    1311           0 :                                        slot_ctx->slot_bank.slot,
    1312           0 :                                        slot_ctx->slot_bank.block_height );
    1313           0 :   }
    1314             : 
    1315           0 :   slot_ctx->slot_bank.collected_execution_fees = 0UL;
    1316           0 :   slot_ctx->slot_bank.collected_priority_fees  = 0UL;
    1317           0 :   slot_ctx->slot_bank.collected_rent           = 0UL;
    1318           0 :   slot_ctx->signature_cnt                      = 0UL;
    1319           0 :   slot_ctx->txn_count                          = 0UL;
    1320           0 :   slot_ctx->nonvote_txn_count                  = 0UL;
    1321           0 :   slot_ctx->failed_txn_count                   = 0UL;
    1322           0 :   slot_ctx->nonvote_failed_txn_count           = 0UL;
    1323           0 :   slot_ctx->total_compute_units_used           = 0UL;
    1324             : 
    1325           0 :   fd_funk_start_write( slot_ctx->acc_mgr->funk );
    1326           0 :   int result = fd_runtime_block_sysvar_update_pre_execute( slot_ctx, runtime_spad );
    1327           0 :   fd_funk_end_write( slot_ctx->acc_mgr->funk );
    1328           0 :   if( FD_UNLIKELY( result != 0 ) ) {
    1329           0 :     FD_LOG_WARNING(("updating sysvars failed"));
    1330           0 :     return result;
    1331           0 :   }
    1332             : 
    1333             :   /* Load sysvars into cache */
    1334           0 :   if( FD_UNLIKELY( result = fd_runtime_sysvar_cache_load( slot_ctx, runtime_spad ) ) ) {
    1335             :     /* non-zero error */
    1336           0 :     return result;
    1337           0 :   }
    1338             : 
    1339           0 :   return FD_RUNTIME_EXECUTE_SUCCESS;
    1340           0 : }
    1341             : 
    1342             : int
    1343             : fd_runtime_block_execute_finalize_tpool( fd_exec_slot_ctx_t            * slot_ctx,
    1344             :                                          fd_capture_ctx_t              * capture_ctx,
    1345             :                                          fd_runtime_block_info_t const * block_info,
    1346             :                                          fd_tpool_t                    * tpool,
    1347           0 :                                          fd_spad_t                     * runtime_spad ) {
    1348             : 
    1349           0 :   fd_funk_start_write( slot_ctx->acc_mgr->funk );
    1350             : 
    1351           0 :   fd_sysvar_slot_history_update( slot_ctx, runtime_spad );
    1352             : 
    1353             :   /* This slot is now "frozen" and can't be changed anymore. */
    1354           0 :   fd_runtime_freeze( slot_ctx, runtime_spad );
    1355             : 
    1356           0 :   int result = fd_bpf_scan_and_create_bpf_program_cache_entry_tpool( slot_ctx, slot_ctx->funk_txn, tpool, runtime_spad );
    1357           0 :   if( FD_UNLIKELY( result ) ) {
    1358           0 :     FD_LOG_WARNING(( "update bpf program cache failed" ));
    1359           0 :     fd_funk_end_write( slot_ctx->acc_mgr->funk );
    1360           0 :     return result;
    1361           0 :   }
    1362             : 
    1363           0 :   result = fd_update_hash_bank_tpool( slot_ctx,
    1364           0 :                                       capture_ctx,
    1365           0 :                                       &slot_ctx->slot_bank.banks_hash,
    1366           0 :                                       block_info->signature_cnt,
    1367           0 :                                       tpool,
    1368           0 :                                       runtime_spad );
    1369             : 
    1370           0 :   if( FD_UNLIKELY( result!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
    1371           0 :     FD_LOG_WARNING(( "hashing bank failed" ));
    1372           0 :     fd_funk_end_write( slot_ctx->acc_mgr->funk );
    1373           0 :     return result;
    1374           0 :   }
    1375             : 
    1376             :   /* We don't want to save the epoch bank at the end of every slot because it
    1377             :      should only be changing at the epoch boundary. */
    1378             : 
    1379           0 :   result = fd_runtime_save_slot_bank( slot_ctx );
    1380           0 :   if( FD_UNLIKELY( result!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
    1381           0 :     FD_LOG_WARNING(( "failed to save slot bank" ));
    1382           0 :     fd_funk_end_write( slot_ctx->acc_mgr->funk );
    1383           0 :     return result;
    1384           0 :   }
    1385             : 
    1386           0 :   fd_funk_end_write( slot_ctx->acc_mgr->funk );
    1387             : 
    1388           0 :   slot_ctx->total_compute_units_requested = 0UL;
    1389             : 
    1390           0 :   return FD_RUNTIME_EXECUTE_SUCCESS;
    1391           0 : }
    1392             : 
    1393             : /******************************************************************************/
    1394             : /* Transaction Level Execution Management                                     */
    1395             : /******************************************************************************/
    1396             : 
    1397             : /* fd_runtime_prepare_txns_start is responsible for setting up the task infos,
    1398             :    the slot_ctx, and for setting up the accessed accounts. */
    1399             : 
    1400             : int
    1401             : fd_runtime_prepare_txns_start( fd_exec_slot_ctx_t *         slot_ctx,
    1402             :                                fd_execute_txn_task_info_t * task_info,
    1403             :                                fd_txn_p_t *                 txns,
    1404             :                                ulong                        txn_cnt,
    1405           0 :                                fd_spad_t *                  runtime_spad ) {
    1406           0 :   int res = 0;
    1407             :   /* Loop across transactions */
    1408           0 :   for( ulong txn_idx = 0UL; txn_idx < txn_cnt; txn_idx++ ) {
    1409           0 :     fd_txn_p_t * txn = &txns[txn_idx];
    1410             : 
    1411             :     /* Allocate/setup transaction context and task infos */
    1412           0 :     task_info[txn_idx].txn_ctx      = fd_spad_alloc( runtime_spad, FD_EXEC_TXN_CTX_ALIGN, FD_EXEC_TXN_CTX_FOOTPRINT );
    1413           0 :     fd_exec_txn_ctx_t * txn_ctx     = task_info[txn_idx].txn_ctx;
    1414           0 :     task_info[txn_idx].exec_res     = 0;
    1415           0 :     task_info[txn_idx].txn          = txn;
    1416           0 :     fd_txn_t const * txn_descriptor = (fd_txn_t const *) txn->_;
    1417             : 
    1418           0 :     fd_rawtxn_b_t raw_txn = { .raw = txn->payload, .txn_sz = (ushort)txn->payload_sz };
    1419             : 
    1420           0 :     int err = fd_execute_txn_prepare_start( slot_ctx,
    1421           0 :                                             txn_ctx,
    1422           0 :                                             txn_descriptor,
    1423           0 :                                             &raw_txn,
    1424           0 :                                             runtime_spad );
    1425           0 :     if( FD_UNLIKELY( err ) ) {
    1426           0 :       task_info[txn_idx].exec_res = err;
    1427           0 :       txn->flags                  = 0U;
    1428           0 :       res |= err;
    1429           0 :     }
    1430           0 :   }
    1431             : 
    1432           0 :   return res;
    1433           0 : }
    1434             : 
    1435             : /* fd_runtime_pre_execute_check is responsible for conducting many of the
    1436             :    transaction sanitization checks. */
    1437             : 
    1438             : void
    1439           0 : fd_runtime_pre_execute_check( fd_execute_txn_task_info_t * task_info ) {
    1440           0 :   if( FD_UNLIKELY( !( task_info->txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS ) ) ) {
    1441           0 :     return;
    1442           0 :   }
    1443             : 
    1444           0 :   fd_exec_txn_ctx_t * txn_ctx = task_info->txn_ctx;
    1445           0 :   fd_executor_setup_borrowed_accounts_for_txn( txn_ctx );
    1446             : 
    1447           0 :   int err;
    1448             : 
    1449             :   /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/sdk/src/transaction/sanitized.rs#L263-L275
    1450             :      TODO: Agave's precompile verification is done at the slot level, before batching and executing transactions. This logic should probably
    1451             :      be moved in the future. The Agave call heirarchy looks something like this:
    1452             :             process_single_slot
    1453             :                    v
    1454             :             confirm_full_slot
    1455             :                    v
    1456             :             confirm_slot_entries --------->
    1457             :                    v                      v
    1458             :             verify_transaction      process_entries
    1459             :                    v                      v
    1460             :             verify_precompiles      process_batches
    1461             :                                           v
    1462             :                                          ...
    1463             :                                           v
    1464             :                               load_and_execute_transactions
    1465             :                                           v
    1466             :                                          ...
    1467             :                                           v
    1468             :                                     load_accounts --> load_transaction_accounts
    1469             :                                           v
    1470             :                               general transaction execution
    1471             : 
    1472             :   */
    1473           0 :   if( !FD_FEATURE_ACTIVE_( txn_ctx->slot, txn_ctx->features, move_precompile_verification_to_svm ) ) {
    1474           0 :     err = fd_executor_verify_precompiles( txn_ctx );
    1475           0 :     if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
    1476           0 :       task_info->txn->flags = 0U;
    1477           0 :       task_info->exec_res   = err;
    1478           0 :       return;
    1479           0 :     }
    1480           0 :   }
    1481             : 
    1482             :   /* Post-sanitization checks. Called from `prepare_sanitized_batch()` which, for now, only is used
    1483             :      to lock the accounts and perform a couple basic validations.
    1484             :      https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L118 */
    1485           0 :   err = fd_executor_validate_account_locks( txn_ctx );
    1486           0 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
    1487           0 :     task_info->txn->flags = 0U;
    1488           0 :     task_info->exec_res   = err;
    1489           0 :     return;
    1490           0 :   }
    1491             : 
    1492             :   /* `load_and_execute_transactions()` -> `check_transactions()`
    1493             :      https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/runtime/src/bank.rs#L3667-L3672 */
    1494           0 :   err = fd_executor_check_transactions( txn_ctx );
    1495           0 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
    1496           0 :     task_info->txn->flags = 0U;
    1497           0 :     task_info->exec_res = err;
    1498           0 :     return;
    1499           0 :   }
    1500             : 
    1501             :   /* `load_and_execute_sanitized_transactions()` -> `validate_fees()` -> `validate_transaction_fee_payer()`
    1502             :      https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/svm/src/transaction_processor.rs#L236-L249 */
    1503           0 :   err = fd_executor_validate_transaction_fee_payer( txn_ctx );
    1504           0 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
    1505           0 :     task_info->txn->flags = 0U;
    1506           0 :     task_info->exec_res = err;
    1507           0 :     return;
    1508           0 :   }
    1509             : 
    1510             :   /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/svm/src/transaction_processor.rs#L284-L296 */
    1511           0 :   err = fd_executor_load_transaction_accounts( txn_ctx );
    1512           0 :   if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
    1513           0 :     if( FD_FEATURE_ACTIVE( txn_ctx->slot, txn_ctx->features, enable_transaction_loading_failure_fees ) ) {
    1514             :       /* Regardless of whether transaction accounts were loaded successfully, the transaction is
    1515             :          included in the block and transaction fees are collected.
    1516             :          https://github.com/anza-xyz/agave/blob/v2.1.6/svm/src/transaction_processor.rs#L341-L357 */
    1517           0 :       task_info->txn->flags |= FD_TXN_P_FLAGS_FEES_ONLY;
    1518           0 :       task_info->exec_res    = err;
    1519           0 :     } else {
    1520           0 :       task_info->txn->flags = 0U;
    1521           0 :       task_info->exec_res   = err;
    1522           0 :     }
    1523           0 :   }
    1524             : 
    1525             :   /*
    1526             :      The fee payer and the nonce account will be stored and hashed so
    1527             :      long as the transaction landed on chain, or, in Agave terminology,
    1528             :      the transaction was processed.
    1529             :      https://github.com/anza-xyz/agave/blob/v2.1.1/runtime/src/account_saver.rs#L72
    1530             : 
    1531             :      A transaction lands on chain in one of two ways:
    1532             :      (1) Passed fee validation and loaded accounts.
    1533             :      (2) Passed fee validation and failed to load accounts and the enable_transaction_loading_failure_fees feature is enabled as per
    1534             :          SIMD-0082 https://github.com/anza-xyz/feature-gate-tracker/issues/52
    1535             : 
    1536             :      So, at this point, the transaction is committable.
    1537             :    */
    1538           0 : }
    1539             : 
    1540             : /* fd_runtime_finalize_txn is a helper used by the non-tpool transaction
    1541             :    executor to finalize borrowed account changes back into funk. It also
    1542             :    handles txncache insertion and updates to the vote/stake cache. */
    1543             : 
    1544             : static int
    1545             : fd_runtime_finalize_txn( fd_exec_slot_ctx_t *         slot_ctx,
    1546             :                          fd_capture_ctx_t *           capture_ctx,
    1547           0 :                          fd_execute_txn_task_info_t * task_info ) {
    1548             :   /* TODO: Allocations should probably not be made out of the exec_spad in this
    1549             :      function. If they are, the size of the data needs to be accounted for in
    1550             :      the calculation of the bound of the spad as defined in fd_runtime.h. */
    1551             : 
    1552           0 :   if( FD_UNLIKELY( !( task_info->txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS ) ) ) {
    1553           0 :     return -1;
    1554           0 :   }
    1555             : 
    1556             :   /* Store transaction info including logs */
    1557           0 :   fd_runtime_finalize_txns_update_blockstore_meta( slot_ctx, task_info, 1UL );
    1558             : 
    1559             :   /* Collect fees */
    1560           0 :   FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->slot_bank.collected_execution_fees, task_info->txn_ctx->execution_fee  );
    1561           0 :   FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->slot_bank.collected_priority_fees,  task_info->txn_ctx->priority_fee   );
    1562           0 :   FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->slot_bank.collected_rent,           task_info->txn_ctx->collected_rent );
    1563             : 
    1564           0 :   fd_exec_txn_ctx_t * txn_ctx      = task_info->txn_ctx;
    1565           0 :   int                 exec_txn_err = task_info->exec_res;
    1566             : 
    1567             :   /* For ledgers that contain txn status, decode and write out for solcap */
    1568           0 :   if( capture_ctx != NULL && capture_ctx->capture && capture_ctx->capture_txns ) {
    1569             :     // TODO: probably need to get rid of this lock or special case it to not use funk's lock.
    1570           0 :     fd_funk_start_write( slot_ctx->acc_mgr->funk );
    1571           0 :     fd_runtime_write_transaction_status( capture_ctx, slot_ctx, txn_ctx, exec_txn_err );
    1572           0 :     fd_funk_end_write( slot_ctx->acc_mgr->funk );
    1573           0 :   }
    1574           0 :   FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->signature_cnt, txn_ctx->txn_descriptor->signature_cnt );
    1575             : 
    1576             :   // if( slot_ctx->status_cache ) {
    1577             :   //   fd_txncache_insert_t status_insert = {0};
    1578             :   //   uchar                result        = exec_txn_err == 0 ? 1 : 0;
    1579             : 
    1580             :   //   fd_txncache_insert_t * curr_insert = &status_insert;
    1581             :   //   curr_insert->blockhash = ((uchar *)txn_ctx->_txn_raw->raw + txn_ctx->txn_descriptor->recent_blockhash_off);
    1582             :   //   curr_insert->slot      = slot_ctx->slot_bank.slot;
    1583             :   //   fd_hash_t * hash       = &txn_ctx->blake_txn_msg_hash;
    1584             :   //   curr_insert->txnhash   = hash->uc;
    1585             :   //   curr_insert->result    = &result;
    1586             :   //   if( FD_UNLIKELY( !fd_txncache_insert_batch( slot_ctx->status_cache, &status_insert, 1UL ) ) ) {
    1587             :   //     FD_LOG_ERR(( "Status cache is full, this should not be possible" ));
    1588             :   //   }
    1589             :   // }
    1590             : 
    1591           0 :   if( FD_UNLIKELY( exec_txn_err ) ) {
    1592             : 
    1593             :     /* Save the fee_payer. Everything but the fee balance should be reset.
    1594             :        TODO: an optimization here could be to use a dirty flag in the
    1595             :        borrowed account. If the borrowed account data has been changed in
    1596             :        any way, then the full account can be rolled back as it is done now.
    1597             :        However, most of the time the account data is not changed, and only
    1598             :        the lamport balance has to change. */
    1599           0 :     fd_txn_account_t * acct = fd_txn_account_init( &txn_ctx->accounts[0] );
    1600             : 
    1601           0 :     fd_acc_mgr_view( txn_ctx->acc_mgr, txn_ctx->funk_txn, &txn_ctx->account_keys[0], acct );
    1602           0 :     memcpy( acct->pubkey->key, &txn_ctx->account_keys[0], sizeof(fd_pubkey_t) );
    1603             : 
    1604           0 :     void * acct_data = fd_spad_alloc( txn_ctx->spad, FD_ACCOUNT_REC_ALIGN, FD_ACC_TOT_SZ_MAX );
    1605           0 :     fd_txn_account_make_mutable( acct, acct_data );
    1606           0 :     acct->meta->info.lamports -= (txn_ctx->execution_fee + txn_ctx->priority_fee);
    1607             : 
    1608           0 :     fd_acc_mgr_save_non_tpool( slot_ctx->acc_mgr, slot_ctx->funk_txn, &txn_ctx->accounts[0] );
    1609             : 
    1610           0 :     if( txn_ctx->nonce_account_idx_in_txn != ULONG_MAX ) {
    1611           0 :       if( FD_LIKELY( txn_ctx->nonce_account_advanced ) ) {
    1612           0 :         fd_acc_mgr_save_non_tpool( slot_ctx->acc_mgr, slot_ctx->funk_txn, &txn_ctx->accounts[ txn_ctx->nonce_account_idx_in_txn ] );
    1613           0 :       } else {
    1614           0 :         fd_acc_mgr_save_non_tpool( slot_ctx->acc_mgr, slot_ctx->funk_txn, &txn_ctx->rollback_nonce_account[ 0 ] );
    1615           0 :       }
    1616           0 :     }
    1617           0 :   } else {
    1618             : 
    1619           0 :     int dirty_vote_acc  = txn_ctx->dirty_vote_acc;
    1620           0 :     int dirty_stake_acc = txn_ctx->dirty_stake_acc;
    1621             : 
    1622           0 :     for( ulong i=0UL; i<txn_ctx->accounts_cnt; i++ ) {
    1623             :       /* We are only interested in saving writable accounts and the fee
    1624             :          payer account. */
    1625           0 :       if( !fd_txn_account_is_writable_idx( txn_ctx, (int)i ) && i!=FD_FEE_PAYER_TXN_IDX ) {
    1626           0 :         continue;
    1627           0 :       }
    1628             : 
    1629           0 :       fd_txn_account_t * acc_rec = &txn_ctx->accounts[i];
    1630             : 
    1631           0 :       if( dirty_vote_acc && 0==memcmp( acc_rec->const_meta->info.owner, &fd_solana_vote_program_id, sizeof(fd_pubkey_t) ) ) {
    1632             :         /* lock for inserting/modifying vote accounts in slot ctx. */
    1633           0 :         fd_funk_start_write( slot_ctx->acc_mgr->funk );
    1634           0 :         fd_vote_store_account( slot_ctx, acc_rec );
    1635           0 :         FD_SPAD_FRAME_BEGIN( txn_ctx->spad ) {
    1636           0 :           fd_bincode_decode_ctx_t decode_vsv = {
    1637           0 :             .data    = acc_rec->const_data,
    1638           0 :             .dataend = acc_rec->const_data + acc_rec->const_meta->dlen,
    1639           0 :           };
    1640             : 
    1641           0 :           ulong total_sz = 0UL;
    1642           0 :           int err = fd_vote_state_versioned_decode_footprint( &decode_vsv, &total_sz );
    1643           0 :           if( FD_UNLIKELY( err ) ) {
    1644           0 :             FD_LOG_WARNING(( "failed to decode vote state versioned" ));
    1645           0 :             continue;
    1646           0 :           }
    1647             : 
    1648           0 :           uchar * mem = fd_spad_alloc( txn_ctx->spad, 8UL, total_sz );
    1649           0 :           if( FD_UNLIKELY( !mem ) ) {
    1650           0 :             FD_LOG_ERR(( "Unable to allocate memory for vote state versioned" ));
    1651           0 :           }
    1652             : 
    1653           0 :           fd_vote_state_versioned_decode( mem, &decode_vsv );
    1654           0 :           fd_vote_state_versioned_t * vsv = (fd_vote_state_versioned_t *)mem;
    1655             : 
    1656           0 :           fd_vote_block_timestamp_t const * ts = NULL;
    1657           0 :           switch( vsv->discriminant ) {
    1658           0 :           case fd_vote_state_versioned_enum_v0_23_5:
    1659           0 :             ts = &vsv->inner.v0_23_5.last_timestamp;
    1660           0 :             break;
    1661           0 :           case fd_vote_state_versioned_enum_v1_14_11:
    1662           0 :             ts = &vsv->inner.v1_14_11.last_timestamp;
    1663           0 :             break;
    1664           0 :           case fd_vote_state_versioned_enum_current:
    1665           0 :             ts = &vsv->inner.current.last_timestamp;
    1666           0 :             break;
    1667           0 :           default:
    1668           0 :             __builtin_unreachable();
    1669           0 :           }
    1670             : 
    1671           0 :           fd_vote_record_timestamp_vote_with_slot( slot_ctx,
    1672           0 :                                                    acc_rec->pubkey,
    1673           0 :                                                    ts->timestamp,
    1674           0 :                                                    ts->slot );
    1675           0 :         } FD_SPAD_FRAME_END;
    1676           0 :         fd_funk_end_write( slot_ctx->acc_mgr->funk );
    1677           0 :       }
    1678             : 
    1679           0 :       if( dirty_stake_acc && 0==memcmp( acc_rec->const_meta->info.owner, &fd_solana_stake_program_id, sizeof(fd_pubkey_t) ) ) {
    1680             :         // TODO: does this correctly handle stake account close?
    1681           0 :         fd_funk_start_write( slot_ctx->acc_mgr->funk );
    1682           0 :         fd_store_stake_delegation( slot_ctx, acc_rec );
    1683           0 :         fd_funk_end_write( slot_ctx->acc_mgr->funk );
    1684           0 :       }
    1685             : 
    1686           0 :       fd_acc_mgr_save_non_tpool( slot_ctx->acc_mgr, slot_ctx->funk_txn, &txn_ctx->accounts[i] );
    1687           0 :       int fresh_account = acc_rec->meta &&
    1688           0 :          acc_rec->meta->info.lamports && acc_rec->meta->info.rent_epoch != FD_RENT_EXEMPT_RENT_EPOCH;
    1689           0 :       if( FD_UNLIKELY( fresh_account ) ) {
    1690           0 :         fd_runtime_register_new_fresh_account( slot_ctx, txn_ctx->accounts[0].pubkey );
    1691           0 :       }
    1692           0 :     }
    1693           0 :   }
    1694             : 
    1695           0 :   int is_vote = fd_txn_is_simple_vote_transaction( txn_ctx->txn_descriptor,
    1696           0 :                                                  txn_ctx->_txn_raw->raw );
    1697           0 :   if( !is_vote ){
    1698           0 :     FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->nonvote_txn_count, 1 );
    1699           0 :     if( FD_UNLIKELY( exec_txn_err ) ){
    1700           0 :       FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->nonvote_failed_txn_count, 1 );
    1701           0 :     }
    1702           0 :   } else {
    1703           0 :     if( FD_UNLIKELY( exec_txn_err ) ){
    1704           0 :       FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->failed_txn_count, 1 );
    1705           0 :     }
    1706           0 :   }
    1707           0 :   FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->total_compute_units_used, txn_ctx->compute_unit_limit - txn_ctx->compute_meter );
    1708             : 
    1709           0 :   return 0;
    1710           0 : }
    1711             : 
    1712             : /* fd_runtime_prepare_and_execute_txn is the main entrypoint into the executor
    1713             :    tile. At this point, the slot and epoch context should NOT be changed.
    1714             :    NOTE: The executor tile doesn't exist yet. */
    1715             : 
    1716             : static int
    1717             : fd_runtime_prepare_and_execute_txn( fd_exec_slot_ctx_t const *   slot_ctx,
    1718             :                                     fd_txn_p_t *                 txn,
    1719             :                                     fd_execute_txn_task_info_t * task_info,
    1720             :                                     fd_spad_t *                  exec_spad,
    1721           0 :                                     fd_capture_ctx_t *           capture_ctx ) {
    1722             : 
    1723           0 :   int dump_txn = capture_ctx && slot_ctx->slot_bank.slot >= capture_ctx->dump_proto_start_slot && capture_ctx->dump_txn_to_pb;
    1724           0 :   int res = 0;
    1725             : 
    1726           0 :   fd_exec_txn_ctx_t * txn_ctx     = task_info->txn_ctx;
    1727           0 :   task_info->exec_res             = -1;
    1728           0 :   task_info->txn                  = txn;
    1729           0 :   fd_txn_t const * txn_descriptor = (fd_txn_t const *) txn->_;
    1730           0 :   task_info->txn_ctx->spad        = exec_spad;
    1731             : 
    1732           0 :   fd_rawtxn_b_t raw_txn = { .raw = txn->payload, .txn_sz = (ushort)txn->payload_sz };
    1733             : 
    1734           0 :   res = fd_execute_txn_prepare_start( slot_ctx, txn_ctx, txn_descriptor, &raw_txn, exec_spad );
    1735           0 :   if( FD_UNLIKELY( res ) ) {
    1736           0 :     txn->flags = 0U;
    1737           0 :     return -1;
    1738           0 :   }
    1739             : 
    1740             :   /* Dump txns if necessary */
    1741           0 :   task_info->txn_ctx->capture_ctx = capture_ctx;
    1742           0 :   if( FD_UNLIKELY( dump_txn ) ) {
    1743             :     /* Manual push/pop on the spad within the callee. */
    1744           0 :     fd_dump_txn_to_protobuf( task_info->txn_ctx, exec_spad );
    1745           0 :   }
    1746             : 
    1747           0 :   if( FD_UNLIKELY( fd_executor_txn_verify( txn_ctx )!=0 ) ) {
    1748           0 :     FD_LOG_WARNING(( "sigverify failed: %s", FD_BASE58_ENC_64_ALLOCA( (uchar *)txn_ctx->_txn_raw->raw+txn_ctx->txn_descriptor->signature_off ) ));
    1749           0 :     task_info->txn->flags = 0U;
    1750           0 :     task_info->exec_res   = FD_RUNTIME_TXN_ERR_SIGNATURE_FAILURE;
    1751           0 :   }
    1752             : 
    1753           0 :   fd_runtime_pre_execute_check( task_info );
    1754           0 :   if( FD_UNLIKELY( !( task_info->txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS ) ) ) {
    1755           0 :     res  = task_info->exec_res;
    1756           0 :     return -1;
    1757           0 :   }
    1758             : 
    1759             :   /* Execute */
    1760           0 :   task_info->txn->flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
    1761           0 :   task_info->exec_res    = fd_execute_txn( task_info );
    1762             : 
    1763           0 :   if( task_info->exec_res==0 ) {
    1764           0 :     fd_txn_reclaim_accounts( task_info->txn_ctx );
    1765           0 :   }
    1766             : 
    1767           0 :   return res;
    1768             : 
    1769           0 : }
    1770             : 
    1771             : static void
    1772             : fd_runtime_prepare_execute_finalize_txn_task( void * tpool,
    1773             :                                               ulong  t0,
    1774             :                                               ulong  t1,
    1775             :                                               void * args,
    1776             :                                               void * reduce,
    1777             :                                               ulong  stride FD_PARAM_UNUSED,
    1778             :                                               ulong  l0     FD_PARAM_UNUSED,
    1779             :                                               ulong  l1     FD_PARAM_UNUSED,
    1780             :                                               ulong  m0     FD_PARAM_UNUSED,
    1781             :                                               ulong  m1     FD_PARAM_UNUSED,
    1782             :                                               ulong  n0     FD_PARAM_UNUSED,
    1783           0 :                                               ulong  n1     FD_PARAM_UNUSED ) {
    1784             : 
    1785           0 :   fd_exec_slot_ctx_t *         slot_ctx     = (fd_exec_slot_ctx_t *)tpool;
    1786           0 :   fd_capture_ctx_t *           capture_ctx  = (fd_capture_ctx_t *)t0;
    1787           0 :   fd_txn_p_t *                 txn          = (fd_txn_p_t *)t1;
    1788           0 :   fd_execute_txn_task_info_t * task_info    = (fd_execute_txn_task_info_t *)args;
    1789           0 :   fd_spad_t *                  exec_spad    = (fd_spad_t *)reduce;
    1790             : 
    1791           0 :   fd_runtime_prepare_and_execute_txn( slot_ctx,
    1792           0 :                                       txn,
    1793           0 :                                       task_info,
    1794           0 :                                       exec_spad,
    1795           0 :                                       capture_ctx );
    1796             : 
    1797           0 :   fd_runtime_finalize_txn( slot_ctx, capture_ctx, task_info );
    1798           0 : }
    1799             : 
    1800             : /* fd_executor_txn_verify and fd_runtime_pre_execute_check are responisble
    1801             :    for the bulk of the pre-transaction execution checks in the runtime.
    1802             :    They aim to preserve the ordering present in the Agave client to match
    1803             :    parity in terms of error codes. Sigverify is kept seperate from the rest
    1804             :    of the transaction checks for fuzzing convenience.
    1805             : 
    1806             :    For reference this is the general code path which contains all relevant
    1807             :    pre-transactions checks in the v2.0.x Agave client from upstream
    1808             :    to downstream is as follows:
    1809             : 
    1810             :    confirm_slot_entries() which calls verify_ticks() and
    1811             :    verify_transaction(). verify_transaction() calls verify_and_hash_message()
    1812             :    and verify_precompiles() which parallels fd_executor_txn_verify() and
    1813             :    fd_executor_verify_precompiles().
    1814             : 
    1815             :    process_entries() contains a duplicate account check which is part of
    1816             :    agave account lock acquiring. This is checked inline in
    1817             :    fd_runtime_pre_execute_check().
    1818             : 
    1819             :    load_and_execute_transactions() contains the function check_transactions().
    1820             :    This contains check_age() and check_status_cache() which is paralleled by
    1821             :    fd_check_transaction_age() and fd_executor_check_status_cache()
    1822             :    respectively.
    1823             : 
    1824             :    load_and_execute_sanitized_transactions() contains validate_fees()
    1825             :    which is responsible for executing the compute budget instructions,
    1826             :    validating the fee payer and collecting the fee. This is mirrored in
    1827             :    firedancer with fd_executor_compute_budget_program_execute_instructions()
    1828             :    and fd_executor_collect_fees(). load_and_execute_sanitized_transactions()
    1829             :    also checks the total data size of the accounts in load_accounts() and
    1830             :    validates the program accounts in load_transaction_accounts(). This
    1831             :    is paralled by fd_executor_load_transaction_accounts(). */
    1832             : 
    1833             : int
    1834             : fd_runtime_process_txns_in_microblock_stream( fd_exec_slot_ctx_t * slot_ctx,
    1835             :                                               fd_capture_ctx_t *   capture_ctx,
    1836             :                                               fd_txn_p_t *         txns,
    1837             :                                               ulong                txn_cnt,
    1838             :                                               fd_tpool_t *         tpool,
    1839             :                                               fd_spad_t * *        exec_spads,
    1840             :                                               ulong                exec_spad_cnt,
    1841             :                                               fd_spad_t *          runtime_spad,
    1842           0 :                                               fd_cost_tracker_t *  cost_tracker_opt ) {
    1843             : 
    1844           0 :   int res = 0;
    1845             : 
    1846           0 :   for( ulong i=0UL; i<txn_cnt; i++ ) {
    1847           0 :     txns[i].flags = FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
    1848           0 :   }
    1849             : 
    1850           0 :   fd_execute_txn_task_info_t * task_infos = fd_spad_alloc( runtime_spad,
    1851           0 :                                                            alignof(fd_execute_txn_task_info_t),
    1852           0 :                                                            txn_cnt * sizeof(fd_execute_txn_task_info_t) );
    1853             : 
    1854           0 :   ulong curr_exec_idx = 0UL;
    1855           0 :   while( curr_exec_idx<txn_cnt ) {
    1856           0 :     ulong exec_idx_start = curr_exec_idx;
    1857             : 
    1858             :     // Push a new spad frame for each exec spad
    1859           0 :     for( ulong worker_idx=1UL; worker_idx<exec_spad_cnt; worker_idx++ ) {
    1860           0 :       fd_spad_push( exec_spads[ worker_idx ] );
    1861           0 :     }
    1862             : 
    1863           0 :     for( ulong worker_idx=1UL; worker_idx<exec_spad_cnt; worker_idx++ ) {
    1864           0 :       if( curr_exec_idx>=txn_cnt ) {
    1865           0 :         break;
    1866           0 :       }
    1867           0 :       int state = fd_tpool_worker_state( tpool, worker_idx );
    1868           0 :       if( state!=FD_TPOOL_WORKER_STATE_IDLE ) {
    1869           0 :         continue;
    1870           0 :       }
    1871             : 
    1872           0 :       task_infos[ curr_exec_idx ].spad    = exec_spads[ worker_idx ];
    1873           0 :       task_infos[ curr_exec_idx ].txn     = &txns[ curr_exec_idx ];
    1874           0 :       task_infos[ curr_exec_idx ].txn_ctx = fd_spad_alloc( task_infos[ curr_exec_idx ].spad,
    1875           0 :                                                            FD_EXEC_TXN_CTX_ALIGN,
    1876           0 :                                                            FD_EXEC_TXN_CTX_FOOTPRINT );
    1877           0 :       if( FD_UNLIKELY( !task_infos[ curr_exec_idx ].txn_ctx ) ) {
    1878           0 :         FD_LOG_ERR(( "failed to allocate txn ctx" ));
    1879           0 :       }
    1880             : 
    1881           0 :       fd_tpool_exec( tpool, worker_idx, fd_runtime_prepare_execute_finalize_txn_task,
    1882           0 :                      slot_ctx, (ulong)capture_ctx, (ulong)task_infos[curr_exec_idx].txn,
    1883           0 :                      &task_infos[ curr_exec_idx ], exec_spads[ worker_idx ], 0UL,
    1884           0 :                      0UL, 0UL, 0UL, 0UL, 0UL, 0UL );
    1885             : 
    1886           0 :       curr_exec_idx++;
    1887           0 :     }
    1888             : 
    1889             :     /* Wait for the workers to finish before we try to dispatch them a new task */
    1890           0 :     for( ulong worker_idx=1UL; worker_idx<exec_spad_cnt; worker_idx++ ) {
    1891           0 :       fd_tpool_wait( tpool, worker_idx );
    1892           0 :     }
    1893             : 
    1894             :     /* Verify cost tracker limits (only for offline replay)
    1895             :        https://github.com/anza-xyz/agave/blob/v2.2.0/ledger/src/blockstore_processor.rs#L284-L299 */
    1896           0 :     if( cost_tracker_opt!=NULL && FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, apply_cost_tracker_during_replay ) ) {
    1897           0 :       for( ulong i=exec_idx_start; i<curr_exec_idx; i++ ) {
    1898             : 
    1899             :         /* Skip any transactions that were not processed */
    1900           0 :         fd_execute_txn_task_info_t const * task_info = &task_infos[ i ];
    1901           0 :         if( FD_UNLIKELY( !( task_info->txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS ) ) ) continue;
    1902             : 
    1903           0 :         fd_exec_txn_ctx_t const * txn_ctx          = task_info->txn_ctx;
    1904           0 :         fd_transaction_cost_t     transaction_cost = fd_calculate_cost_for_executed_transaction( task_info->txn_ctx,
    1905           0 :                                                                                                  runtime_spad );
    1906             : 
    1907             :         /* https://github.com/anza-xyz/agave/blob/v2.2.0/ledger/src/blockstore_processor.rs#L302-L307 */
    1908           0 :         res = fd_cost_tracker_try_add( cost_tracker_opt, txn_ctx, &transaction_cost );
    1909           0 :         if( FD_UNLIKELY( res ) ) {
    1910           0 :           FD_LOG_WARNING(( "Block cost limits exceeded for slot %lu", slot_ctx->slot_bank.slot ));
    1911           0 :           break;
    1912           0 :         }
    1913           0 :       }
    1914           0 :     }
    1915             : 
    1916             :     // Pop the spad frame
    1917           0 :     for( ulong worker_idx=1UL; worker_idx<exec_spad_cnt; worker_idx++ ) {
    1918           0 :       fd_spad_pop( exec_spads[ worker_idx ] );
    1919           0 :     }
    1920             : 
    1921             :     /* If there was a error with cost tracker calculations, return the error */
    1922           0 :     if( FD_UNLIKELY( res ) ) return res;
    1923           0 :   }
    1924             : 
    1925           0 :   return 0;
    1926             : 
    1927           0 : }
    1928             : 
    1929             : /******************************************************************************/
    1930             : /* Epoch Boundary                                                             */
    1931             : /******************************************************************************/
    1932             : 
    1933             : /* Update the epoch bank stakes cache with the delegated stake values from the slot bank cache.
    1934             : The slot bank cache will have been accumulating this epoch, and now we are at an epoch boundary
    1935             : we can safely update the epoch stakes cache with the latest values.
    1936             : 
    1937             : In Solana, the stakes cache is updated after every transaction
    1938             :   (https://github.com/solana-labs/solana/blob/c091fd3da8014c0ef83b626318018f238f506435/runtime/src/bank.rs#L7587).
    1939             : As delegations have to warm up, the contents of the cache will not change inter-epoch. We can therefore update
    1940             : the cache only at epoch boundaries.
    1941             : 
    1942             : https://github.com/solana-labs/solana/blob/c091fd3da8014c0ef83b626318018f238f506435/runtime/src/stakes.rs#L65 */
    1943             : static void
    1944             : fd_update_stake_delegations( fd_exec_slot_ctx_t * slot_ctx,
    1945           0 :                              fd_epoch_info_t *    temp_info ) {
    1946           0 :   fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
    1947           0 :   fd_slot_bank_t *  slot_bank  = &slot_ctx->slot_bank;
    1948           0 :   fd_stakes_t *     stakes     = &epoch_bank->stakes;
    1949             : 
    1950             :   /* In one pass, iterate over all the new stake infos and insert the updated values into the epoch stakes cache
    1951             :       This assumes that there is enough memory pre-allocated for the stakes cache. */
    1952           0 :   for( ulong idx=temp_info->stake_infos_new_keys_start_idx; idx<temp_info->stake_infos_len; idx++ ) {
    1953             :     // Fetch and store the delegation associated with this stake account
    1954           0 :     fd_delegation_pair_t_mapnode_t key;
    1955           0 :     fd_memcpy( &key.elem.account, &temp_info->stake_infos[idx].account, sizeof(fd_pubkey_t) );
    1956           0 :     fd_delegation_pair_t_mapnode_t * entry = fd_delegation_pair_t_map_find( stakes->stake_delegations_pool, stakes->stake_delegations_root, &key );
    1957           0 :     if( FD_LIKELY( entry==NULL ) ) {
    1958           0 :       entry = fd_delegation_pair_t_map_acquire( stakes->stake_delegations_pool );
    1959           0 :       fd_memcpy( &entry->elem.account, &temp_info->stake_infos[idx].account, sizeof(fd_pubkey_t) );
    1960           0 :       fd_memcpy( &entry->elem.delegation, &temp_info->stake_infos[idx].stake.delegation, sizeof(fd_delegation_t) );
    1961           0 :       fd_delegation_pair_t_map_insert( stakes->stake_delegations_pool, &stakes->stake_delegations_root, entry );
    1962           0 :     }
    1963           0 :   }
    1964             : 
    1965           0 :   fd_account_keys_pair_t_map_release_tree( slot_bank->stake_account_keys.account_keys_pool, slot_bank->stake_account_keys.account_keys_root );
    1966           0 :   slot_bank->stake_account_keys.account_keys_root = NULL;
    1967           0 : }
    1968             : 
    1969             : /* Replace the stakes in T-2 (slot_ctx->slot_bank.epoch_stakes) by the stakes at T-1 (epoch_bank->next_epoch_stakes) */
    1970             : static void
    1971           0 : fd_update_epoch_stakes( fd_exec_slot_ctx_t * slot_ctx ) {
    1972           0 :   fd_epoch_bank_t * epoch_bank = &slot_ctx->epoch_ctx->epoch_bank;
    1973             : 
    1974             :   /* Copy epoch_bank->next_epoch_stakes into slot_ctx->slot_bank.epoch_stakes */
    1975           0 :   fd_vote_accounts_pair_t_map_release_tree(
    1976           0 :     slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool,
    1977           0 :     slot_ctx->slot_bank.epoch_stakes.vote_accounts_root );
    1978           0 :   slot_ctx->slot_bank.epoch_stakes.vote_accounts_root = NULL;
    1979             : 
    1980           0 :   for( fd_vote_accounts_pair_t_mapnode_t * n = fd_vote_accounts_pair_t_map_minimum(
    1981           0 :         epoch_bank->next_epoch_stakes.vote_accounts_pool,
    1982           0 :         epoch_bank->next_epoch_stakes.vote_accounts_root );
    1983           0 :         n;
    1984           0 :         n = fd_vote_accounts_pair_t_map_successor( epoch_bank->next_epoch_stakes.vote_accounts_pool, n ) ) {
    1985             : 
    1986           0 :     const fd_pubkey_t null_pubkey = {{ 0 }};
    1987           0 :     if( memcmp( &n->elem.key, &null_pubkey, FD_PUBKEY_FOOTPRINT ) == 0 ) {
    1988           0 :       continue;
    1989           0 :     }
    1990             : 
    1991           0 :     fd_vote_accounts_pair_t_mapnode_t * elem = fd_vote_accounts_pair_t_map_acquire(
    1992           0 :       slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool );
    1993           0 :     if( FD_UNLIKELY( fd_vote_accounts_pair_t_map_free( slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool ) == 0 ) ) {
    1994           0 :       FD_LOG_ERR(( "slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool full" ));
    1995           0 :     }
    1996             : 
    1997           0 :     fd_memcpy( &elem->elem, &n->elem, sizeof(fd_vote_accounts_pair_t));
    1998           0 :     fd_vote_accounts_pair_t_map_insert( slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool,
    1999           0 :                                         &slot_ctx->slot_bank.epoch_stakes.vote_accounts_root,
    2000           0 :                                         elem );
    2001           0 :   }
    2002           0 : }
    2003             : 
    2004             : /* Copy epoch_bank->stakes.vote_accounts into epoch_bank->next_epoch_stakes. */
    2005             : static void
    2006           0 : fd_update_next_epoch_stakes( fd_exec_slot_ctx_t * slot_ctx ) {
    2007           0 :   fd_epoch_bank_t * epoch_bank = &slot_ctx->epoch_ctx->epoch_bank;
    2008             : 
    2009             :   /* Copy epoch_ctx->epoch_bank->stakes.vote_accounts into epoch_bank->next_epoch_stakes */
    2010           0 :   fd_vote_accounts_pair_t_map_release_tree(
    2011           0 :     epoch_bank->next_epoch_stakes.vote_accounts_pool,
    2012           0 :     epoch_bank->next_epoch_stakes.vote_accounts_root );
    2013             : 
    2014           0 :   epoch_bank->next_epoch_stakes.vote_accounts_pool = fd_exec_epoch_ctx_next_epoch_stakes_join( slot_ctx->epoch_ctx );
    2015           0 :   epoch_bank->next_epoch_stakes.vote_accounts_root = NULL;
    2016             : 
    2017           0 :   for( fd_vote_accounts_pair_t_mapnode_t * n = fd_vote_accounts_pair_t_map_minimum(
    2018           0 :         epoch_bank->stakes.vote_accounts.vote_accounts_pool,
    2019           0 :         epoch_bank->stakes.vote_accounts.vote_accounts_root );
    2020           0 :         n;
    2021           0 :         n = fd_vote_accounts_pair_t_map_successor( epoch_bank->stakes.vote_accounts.vote_accounts_pool, n ) ) {
    2022           0 :     fd_vote_accounts_pair_t_mapnode_t * elem = fd_vote_accounts_pair_t_map_acquire( epoch_bank->next_epoch_stakes.vote_accounts_pool );
    2023           0 :     fd_memcpy( &elem->elem, &n->elem, sizeof(fd_vote_accounts_pair_t));
    2024           0 :     fd_vote_accounts_pair_t_map_insert( epoch_bank->next_epoch_stakes.vote_accounts_pool, &epoch_bank->next_epoch_stakes.vote_accounts_root, elem );
    2025           0 :   }
    2026           0 : }
    2027             : 
    2028             : /* Mimics `bank.new_target_program_account()`. Assumes `out_rec` is a modifiable record.
    2029             : 
    2030             :    From the calling context, `out_rec` points to a native program record (e.g. Config, ALUT native programs).
    2031             :    There should be enough space in `out_rec->data` to hold at least 36 bytes (the size of a BPF upgradeable
    2032             :    program account) when calling this function. The native program account's owner is set to the BPF loader
    2033             :    upgradeable program ID, and lamports are increased / deducted to contain the rent exempt minimum balance.
    2034             : 
    2035             :    https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L79-L95 */
    2036             : static int
    2037             : fd_new_target_program_account( fd_exec_slot_ctx_t * slot_ctx,
    2038             :                                fd_pubkey_t const *  target_program_data_address,
    2039           0 :                                fd_txn_account_t *   out_rec ) {
    2040             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/sdk/account/src/lib.rs#L471 */
    2041           0 :   out_rec->meta->info.rent_epoch = 0UL;
    2042             : 
    2043             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L86-L88 */
    2044           0 :   fd_bpf_upgradeable_loader_state_t state = {
    2045           0 :     .discriminant = fd_bpf_upgradeable_loader_state_enum_program,
    2046           0 :     .inner = {
    2047           0 :       .program = {
    2048           0 :         .programdata_address = *target_program_data_address,
    2049           0 :       }
    2050           0 :     }
    2051           0 :   };
    2052             : 
    2053             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L89-L90 */
    2054           0 :   fd_rent_t const * rent = (fd_rent_t const *)fd_sysvar_cache_rent( slot_ctx->sysvar_cache );
    2055           0 :   if( FD_UNLIKELY( rent==NULL ) ) {
    2056           0 :     return -1;
    2057           0 :   }
    2058             : 
    2059           0 :   out_rec->meta->info.lamports = fd_rent_exempt_minimum_balance( rent, SIZE_OF_PROGRAM );
    2060           0 :   fd_bincode_encode_ctx_t ctx = {
    2061           0 :     .data = out_rec->data,
    2062           0 :     .dataend = out_rec->data + SIZE_OF_PROGRAM,
    2063           0 :   };
    2064             : 
    2065             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L91-L9 */
    2066           0 :   int err = fd_bpf_upgradeable_loader_state_encode( &state, &ctx );
    2067           0 :   if( FD_UNLIKELY( err ) ) {
    2068           0 :     return err;
    2069           0 :   }
    2070           0 :   fd_memcpy( out_rec->meta->info.owner, fd_solana_bpf_loader_upgradeable_program_id.uc, sizeof(fd_pubkey_t) );
    2071             : 
    2072             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L93-L94 */
    2073           0 :   out_rec->meta->info.executable = 1;
    2074           0 :   return FD_RUNTIME_EXECUTE_SUCCESS;
    2075           0 : }
    2076             : 
    2077             : /* Mimics `bank.new_target_program_data_account()`. Assumes `new_target_program_data_account` is a modifiable record.
    2078             :    `config_upgrade_authority_address` may be NULL.
    2079             : 
    2080             :    This function uses an existing buffer account `buffer_acc_rec` to set the program data account data for a core
    2081             :    program BPF migration. Sets the lamports and data fields of `new_target_program_data_account` based on the
    2082             :    ELF data length, and sets the owner to the BPF loader upgradeable program ID.
    2083             : 
    2084             :    https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L97-L153 */
    2085             : static int
    2086             : fd_new_target_program_data_account( fd_exec_slot_ctx_t * slot_ctx,
    2087             :                                     fd_pubkey_t *        config_upgrade_authority_address,
    2088             :                                     fd_txn_account_t *   buffer_acc_rec,
    2089             :                                     fd_txn_account_t *   new_target_program_data_account,
    2090           0 :                                     fd_spad_t *          runtime_spad ) {
    2091             : 
    2092           0 :   FD_SPAD_FRAME_BEGIN( runtime_spad ) {
    2093             : 
    2094             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L113-L116 */
    2095           0 :   fd_bincode_decode_ctx_t decode_ctx = {
    2096           0 :     .data    = buffer_acc_rec->const_data,
    2097           0 :     .dataend = buffer_acc_rec->const_data + buffer_acc_rec->const_meta->dlen,
    2098           0 :   };
    2099             : 
    2100           0 :   ulong total_sz = 0UL;
    2101           0 :   int   err      = 0;
    2102           0 :   err = fd_bpf_upgradeable_loader_state_decode_footprint( &decode_ctx, &total_sz );
    2103           0 :   if( FD_UNLIKELY( err ) ) {
    2104           0 :     return err;
    2105           0 :   }
    2106           0 :   uchar * mem = fd_spad_alloc( runtime_spad, alignof(fd_bpf_upgradeable_loader_state_t), total_sz );
    2107           0 :   if( FD_UNLIKELY( !mem ) ) {
    2108           0 :     FD_LOG_ERR(( "Unable to allocate memory for bpf loader state" ));
    2109           0 :   }
    2110             : 
    2111           0 :   fd_bpf_upgradeable_loader_state_decode( mem, &decode_ctx );
    2112           0 :   fd_bpf_upgradeable_loader_state_t * state = (fd_bpf_upgradeable_loader_state_t *)mem;
    2113             : 
    2114           0 :   if( FD_UNLIKELY( !fd_bpf_upgradeable_loader_state_is_buffer( state ) ) ) {
    2115           0 :     return -1;
    2116           0 :   }
    2117             : 
    2118             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L118-L125 */
    2119           0 :   if( config_upgrade_authority_address!=NULL ) {
    2120           0 :     if( FD_UNLIKELY( state->inner.buffer.authority_address==NULL ||
    2121           0 :                      memcmp( config_upgrade_authority_address, state->inner.buffer.authority_address, sizeof(fd_pubkey_t) ) ) ) {
    2122           0 :       return -1;
    2123           0 :     }
    2124           0 :   }
    2125             : 
    2126             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L127-L132 */
    2127           0 :   const fd_rent_t * rent = (fd_rent_t const *)fd_sysvar_cache_rent( slot_ctx->sysvar_cache );
    2128           0 :   if( FD_UNLIKELY( rent==NULL ) ) {
    2129           0 :     return -1;
    2130           0 :   }
    2131             : 
    2132           0 :   const uchar * elf = buffer_acc_rec->const_data + BUFFER_METADATA_SIZE;
    2133           0 :   ulong space = PROGRAMDATA_METADATA_SIZE - BUFFER_METADATA_SIZE + buffer_acc_rec->const_meta->dlen;
    2134           0 :   ulong lamports = fd_rent_exempt_minimum_balance( rent, space );
    2135             : 
    2136             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L134-L137 */
    2137           0 :   fd_bpf_upgradeable_loader_state_t programdata_metadata = {
    2138           0 :     .discriminant = fd_bpf_upgradeable_loader_state_enum_program_data,
    2139           0 :     .inner = {
    2140           0 :       .program_data = {
    2141           0 :         .slot = slot_ctx->slot_bank.slot,
    2142           0 :         .upgrade_authority_address = config_upgrade_authority_address
    2143           0 :       }
    2144           0 :     }
    2145           0 :   };
    2146             : 
    2147             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L139-L144 */
    2148           0 :   new_target_program_data_account->meta->info.lamports = lamports;
    2149           0 :   fd_bincode_encode_ctx_t encode_ctx = {
    2150           0 :     .data = new_target_program_data_account->data,
    2151           0 :     .dataend = new_target_program_data_account->data + PROGRAMDATA_METADATA_SIZE,
    2152           0 :   };
    2153           0 :   err = fd_bpf_upgradeable_loader_state_encode( &programdata_metadata, &encode_ctx );
    2154           0 :   if( FD_UNLIKELY( err ) ) {
    2155           0 :     return err;
    2156           0 :   }
    2157           0 :   fd_memcpy( new_target_program_data_account->meta->info.owner, fd_solana_bpf_loader_upgradeable_program_id.uc, sizeof(fd_pubkey_t) );
    2158             : 
    2159             :   /* Copy the ELF data over
    2160             :      https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L145 */
    2161           0 :   fd_memcpy( new_target_program_data_account->data + PROGRAMDATA_METADATA_SIZE, elf, buffer_acc_rec->const_meta->dlen - BUFFER_METADATA_SIZE );
    2162             : 
    2163           0 :   return FD_RUNTIME_EXECUTE_SUCCESS;
    2164             : 
    2165           0 :   } FD_SPAD_FRAME_END;
    2166           0 : }
    2167             : 
    2168             : /* Mimics `migrate_builtin_to_core_bpf()`. The arguments map as follows:
    2169             :     - builtin_program_id: builtin_program_id
    2170             :     - config
    2171             :       - source_buffer_address: source_buffer_address
    2172             :       - migration_target
    2173             :         - Builtin: !stateless
    2174             :         - Stateless: stateless
    2175             :       - upgrade_authority_address: upgrade_authority_address
    2176             :   https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L235-L318 */
    2177             : static void
    2178             : fd_migrate_builtin_to_core_bpf( fd_exec_slot_ctx_t * slot_ctx,
    2179             :                                 fd_pubkey_t *        upgrade_authority_address,
    2180             :                                 fd_pubkey_t const *  builtin_program_id,
    2181             :                                 fd_pubkey_t const *  source_buffer_address,
    2182             :                                 uchar                stateless,
    2183           0 :                                 fd_spad_t *          runtime_spad ) {
    2184           0 :   int err;
    2185             : 
    2186             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L242-L243
    2187             : 
    2188             :      The below logic is used to obtain a `TargetBuiltin` account. There are three fields of `TargetBuiltin` returned:
    2189             :       - target.program_address: builtin_program_id
    2190             :       - target.program_account:
    2191             :           - if stateless: an AccountSharedData::default() (i.e. system program id, 0 lamports, 0 data, non-executable, system program owner)
    2192             :           - if NOT stateless: the existing account (for us its called `target_program_account`)
    2193             :       - target.program_data_address: `target_program_data_address` for us, derived below. */
    2194             : 
    2195             :   /* These checks will fail if the core program has already been migrated to BPF, since the account will exist + the program owner
    2196             :      will no longer be the native loader.
    2197             :      https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L23-L50 */
    2198           0 :   FD_TXN_ACCOUNT_DECL( target_program_account );
    2199           0 :   uchar program_exists = ( fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, builtin_program_id, target_program_account )==FD_ACC_MGR_SUCCESS );
    2200           0 :   if( !stateless ) {
    2201             :     /* The program account should exist.
    2202             :        https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L30-L33 */
    2203           0 :     if( FD_UNLIKELY( !program_exists ) ) {
    2204           0 :       FD_LOG_WARNING(( "Builtin program %s does not exist, skipping migration...", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
    2205           0 :       return;
    2206           0 :     }
    2207             : 
    2208             :     /* The program account should be owned by the native loader.
    2209             :        https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L35-L38 */
    2210           0 :     if( FD_UNLIKELY( memcmp( target_program_account->const_meta->info.owner, fd_solana_native_loader_id.uc, sizeof(fd_pubkey_t) ) ) ) {
    2211           0 :       FD_LOG_WARNING(( "Builtin program %s is not owned by the native loader, skipping migration...", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
    2212           0 :       return;
    2213           0 :     }
    2214           0 :   } else {
    2215             :     /* The program account should _not_ exist.
    2216             :        https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L42-L46 */
    2217           0 :     if( FD_UNLIKELY( program_exists ) ) {
    2218           0 :       FD_LOG_WARNING(( "Stateless program %s already exists, skipping migration...", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
    2219           0 :       return;
    2220           0 :     }
    2221           0 :   }
    2222             : 
    2223             :   /* The program data account should not exist.
    2224             :      https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L52-L62 */
    2225           0 :   uint custom_err = UINT_MAX;
    2226           0 :   fd_pubkey_t target_program_data_address[ 1UL ];
    2227           0 :   uchar * seeds[ 1UL ];
    2228           0 :   seeds[ 0UL ]    = (uchar *)builtin_program_id;
    2229           0 :   ulong seed_sz   = sizeof(fd_pubkey_t);
    2230           0 :   uchar bump_seed = 0;
    2231           0 :   err = fd_pubkey_find_program_address( &fd_solana_bpf_loader_upgradeable_program_id, 1UL, seeds, &seed_sz, target_program_data_address, &bump_seed, &custom_err );
    2232           0 :   if( FD_UNLIKELY( err ) ) {
    2233             :     /* TODO: We should handle these errors more gracefully instead of just killing the client. */
    2234           0 :     FD_LOG_ERR(( "Unable to find a viable program address bump seed" )); // Solana panics, error code is undefined
    2235           0 :     return;
    2236           0 :   }
    2237           0 :   FD_TXN_ACCOUNT_DECL( program_data_account );
    2238           0 :   if( FD_UNLIKELY( fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, target_program_data_address, program_data_account )==FD_ACC_MGR_SUCCESS ) ) {
    2239           0 :     FD_LOG_WARNING(( "Program data account %s already exists, skipping migration...", FD_BASE58_ENC_32_ALLOCA( target_program_data_address ) ));
    2240           0 :     return;
    2241           0 :   }
    2242             : 
    2243             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L244
    2244             : 
    2245             :      Obtains a `SourceBuffer` account. There are two fields returned:
    2246             :       - source.buffer_address: source_buffer_address
    2247             :       - source.buffer_account: the existing buffer account */
    2248             : 
    2249             :   /* The buffer account should exist.
    2250             :      https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/source_buffer.rs#L26-L29 */
    2251           0 :   FD_TXN_ACCOUNT_DECL( source_buffer_account );
    2252           0 :   if( FD_UNLIKELY( fd_acc_mgr_modify( slot_ctx->acc_mgr, slot_ctx->funk_txn, source_buffer_address, 0, 0UL, source_buffer_account )!=FD_ACC_MGR_SUCCESS ) ) {
    2253           0 :     FD_LOG_WARNING(( "Buffer account %s does not exist, skipping migration...", FD_BASE58_ENC_32_ALLOCA( source_buffer_address ) ));
    2254           0 :     return;
    2255           0 :   }
    2256             : 
    2257             :   /* The buffer account should be owned by the upgradeable loader.
    2258             :      https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/source_buffer.rs#L31-L34 */
    2259           0 :   if( FD_UNLIKELY( memcmp( source_buffer_account->const_meta->info.owner, fd_solana_bpf_loader_upgradeable_program_id.uc, sizeof(fd_pubkey_t) ) ) ) {
    2260           0 :     FD_LOG_WARNING(( "Buffer account %s is not owned by the upgradeable loader, skipping migration...", FD_BASE58_ENC_32_ALLOCA( source_buffer_address ) ));
    2261           0 :     return;
    2262           0 :   }
    2263             : 
    2264             :   /* The buffer account should have the correct state. We already check the buffer account state in `fd_new_target_program_data_account`,
    2265             :       so we can skip the checks here.
    2266             :      https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/source_buffer.rs#L37-L47 */
    2267             : 
    2268             :   /* This check is done a bit prematurely because we calculate the previous account state's lamports. We use 0 for starting lamports
    2269             :      for stateless accounts because they don't yet exist.
    2270             : 
    2271             :      https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L277-L280 */
    2272           0 :   ulong lamports_to_burn = ( stateless ? 0UL : target_program_account->const_meta->info.lamports ) + source_buffer_account->const_meta->info.lamports;
    2273             : 
    2274             :   /* Start a funk write txn */
    2275           0 :   fd_funk_txn_t * parent_txn = slot_ctx->funk_txn;
    2276           0 :   fd_funk_txn_xid_t migration_xid = fd_funk_generate_xid();
    2277           0 :   slot_ctx->funk_txn = fd_funk_txn_prepare( slot_ctx->acc_mgr->funk, slot_ctx->funk_txn, &migration_xid, 0UL );
    2278             : 
    2279             :   /* Attempt serialization of program account. If the program is stateless, we want to create the account. Otherwise,
    2280             :      we want a writable handle to modify the existing account.
    2281             :      https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L246-L249 */
    2282           0 :   FD_TXN_ACCOUNT_DECL( new_target_program_account );
    2283           0 :   err = fd_acc_mgr_modify( slot_ctx->acc_mgr, slot_ctx->funk_txn, builtin_program_id, stateless, SIZE_OF_PROGRAM, new_target_program_account );
    2284           0 :   if( FD_UNLIKELY( err ) ) {
    2285           0 :     FD_LOG_WARNING(( "Builtin program ID %s does not exist", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
    2286           0 :     goto fail;
    2287           0 :   }
    2288           0 :   new_target_program_account->meta->dlen = SIZE_OF_PROGRAM;
    2289           0 :   new_target_program_account->meta->slot = slot_ctx->slot_bank.slot;
    2290             : 
    2291             :   /* Create a new target program account. This modifies the existing record. */
    2292           0 :   err = fd_new_target_program_account( slot_ctx, target_program_data_address, new_target_program_account );
    2293           0 :   if( FD_UNLIKELY( err ) ) {
    2294           0 :     FD_LOG_WARNING(( "Failed to write new program state to %s", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
    2295           0 :     goto fail;
    2296           0 :   }
    2297             : 
    2298             :   /* Create a new target program data account. */
    2299           0 :   ulong new_target_program_data_account_sz = PROGRAMDATA_METADATA_SIZE - BUFFER_METADATA_SIZE + source_buffer_account->const_meta->dlen;
    2300           0 :   FD_TXN_ACCOUNT_DECL( new_target_program_data_account );
    2301           0 :   err = fd_acc_mgr_modify( slot_ctx->acc_mgr,
    2302           0 :                            slot_ctx->funk_txn,
    2303           0 :                            target_program_data_address,
    2304           0 :                            1,
    2305           0 :                            new_target_program_data_account_sz,
    2306           0 :                            new_target_program_data_account );
    2307           0 :   if( FD_UNLIKELY( err ) ) {
    2308           0 :     FD_LOG_WARNING(( "Failed to create new program data account to %s", FD_BASE58_ENC_32_ALLOCA( target_program_data_address ) ));
    2309           0 :     goto fail;
    2310           0 :   }
    2311           0 :   new_target_program_data_account->meta->dlen = new_target_program_data_account_sz;
    2312           0 :   new_target_program_data_account->meta->slot = slot_ctx->slot_bank.slot;
    2313             : 
    2314           0 :   err = fd_new_target_program_data_account( slot_ctx,
    2315           0 :                                             upgrade_authority_address,
    2316           0 :                                             source_buffer_account,
    2317           0 :                                             new_target_program_data_account,
    2318           0 :                                             runtime_spad );
    2319           0 :   if( FD_UNLIKELY( err ) ) {
    2320           0 :     FD_LOG_WARNING(( "Failed to write new program data state to %s", FD_BASE58_ENC_32_ALLOCA( target_program_data_address ) ));
    2321           0 :     goto fail;
    2322           0 :   }
    2323             : 
    2324             :   /* Deploy the new target Core BPF program.
    2325             :      https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L268-L271 */
    2326           0 :   err = fd_directly_invoke_loader_v3_deploy( slot_ctx,
    2327           0 :                                              new_target_program_data_account->const_data + PROGRAMDATA_METADATA_SIZE,
    2328           0 :                                              new_target_program_data_account->const_meta->dlen - PROGRAMDATA_METADATA_SIZE,
    2329           0 :                                              runtime_spad );
    2330           0 :   if( FD_UNLIKELY( err ) ) {
    2331           0 :     FD_LOG_WARNING(( "Failed to deploy program %s", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
    2332           0 :     goto fail;
    2333           0 :   }
    2334             : 
    2335             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L281-L284 */
    2336           0 :   ulong lamports_to_fund = new_target_program_account->const_meta->info.lamports + new_target_program_data_account->const_meta->info.lamports;
    2337             : 
    2338             :   /* Update capitalization.
    2339             :      https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L286-L297 */
    2340           0 :   if( lamports_to_burn>lamports_to_fund ) {
    2341           0 :     slot_ctx->slot_bank.capitalization -= lamports_to_burn - lamports_to_fund;
    2342           0 :   } else {
    2343           0 :     slot_ctx->slot_bank.capitalization += lamports_to_fund - lamports_to_burn;
    2344           0 :   }
    2345             : 
    2346             :   /* Reclaim the source buffer account
    2347             :      https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L305 */
    2348           0 :   source_buffer_account->meta->info.lamports = 0;
    2349           0 :   source_buffer_account->meta->dlen = 0;
    2350           0 :   fd_memset( source_buffer_account->meta->info.owner, 0, sizeof(fd_pubkey_t) );
    2351             : 
    2352             :   /* Publish the in-preparation transaction into the parent. We should not have to create
    2353             :      a BPF cache entry here because the program is technically "delayed visibility", so the program
    2354             :      should not be invokable until the next slot. The cache entry will be created at the end of the
    2355             :      block as a part of the finalize routine. */
    2356           0 :   fd_funk_txn_publish_into_parent( slot_ctx->acc_mgr->funk, slot_ctx->funk_txn, 1 );
    2357           0 :   slot_ctx->funk_txn = parent_txn;
    2358           0 :   return;
    2359             : 
    2360           0 : fail:
    2361             :   /* Cancel the in-preparation transaction and discard any in-progress changes. */
    2362           0 :   fd_funk_txn_cancel( slot_ctx->acc_mgr->funk, slot_ctx->funk_txn, 0UL );
    2363           0 :   slot_ctx->funk_txn = parent_txn;
    2364           0 : }
    2365             : 
    2366             : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6704 */
    2367             : static void
    2368             : fd_apply_builtin_program_feature_transitions( fd_exec_slot_ctx_t * slot_ctx,
    2369           0 :                                               fd_spad_t *          runtime_spad ) {
    2370             :   /* TODO: Set the upgrade authority properly from the core bpf migration config. Right now it's set to None.
    2371             : 
    2372             :      Migrate any necessary stateless builtins to core BPF. So far, the only "stateless" builtin
    2373             :      is the Feature program. Beginning checks in the `migrate_builtin_to_core_bpf` function will
    2374             :      fail if the program has already been migrated to BPF. */
    2375             : 
    2376           0 :   FD_SPAD_FRAME_BEGIN( runtime_spad ) {
    2377             : 
    2378           0 :   fd_builtin_program_t const * builtins = fd_builtins();
    2379           0 :   for( ulong i=0UL; i<fd_num_builtins(); i++ ) {
    2380             :     /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6732-L6751 */
    2381           0 :     if( builtins[i].core_bpf_migration_config && FD_FEATURE_ACTIVE_OFFSET( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, builtins[i].core_bpf_migration_config->enable_feature_offset ) ) {
    2382           0 :       FD_LOG_NOTICE(( "Migrating builtin program %s to core BPF", FD_BASE58_ENC_32_ALLOCA( builtins[i].pubkey->key ) ));
    2383           0 :       fd_migrate_builtin_to_core_bpf( slot_ctx,
    2384           0 :                                       builtins[i].core_bpf_migration_config->upgrade_authority_address,
    2385           0 :                                       builtins[i].core_bpf_migration_config->builtin_program_id,
    2386           0 :                                       builtins[i].core_bpf_migration_config->source_buffer_address,
    2387           0 :                                       0,
    2388           0 :                                       runtime_spad );
    2389           0 :     }
    2390             :     /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6753-L6774 */
    2391           0 :     if( builtins[i].enable_feature_offset!=NO_ENABLE_FEATURE_ID && FD_FEATURE_JUST_ACTIVATED_OFFSET( slot_ctx, builtins[i].enable_feature_offset ) ) {
    2392           0 :       FD_LOG_NOTICE(( "Enabling builtin program %s", FD_BASE58_ENC_32_ALLOCA( builtins[i].pubkey->key ) ));
    2393           0 :       fd_write_builtin_account( slot_ctx, *builtins[i].pubkey, builtins[i].data,strlen(builtins[i].data) );
    2394           0 :     }
    2395           0 :   }
    2396             : 
    2397             :   /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6776-L6793 */
    2398           0 :   fd_stateless_builtin_program_t const * stateless_builtins = fd_stateless_builtins();
    2399           0 :   for( ulong i=0UL; i<fd_num_stateless_builtins(); i++ ) {
    2400           0 :     if( stateless_builtins[i].core_bpf_migration_config && FD_FEATURE_ACTIVE_OFFSET( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, stateless_builtins[i].core_bpf_migration_config->enable_feature_offset ) ) {
    2401           0 :       FD_LOG_NOTICE(( "Migrating stateless builtin program %s to core BPF", FD_BASE58_ENC_32_ALLOCA( stateless_builtins[i].pubkey->key ) ));
    2402           0 :       fd_migrate_builtin_to_core_bpf( slot_ctx,
    2403           0 :                                       stateless_builtins[i].core_bpf_migration_config->upgrade_authority_address,
    2404           0 :                                       stateless_builtins[i].core_bpf_migration_config->builtin_program_id,
    2405           0 :                                       stateless_builtins[i].core_bpf_migration_config->source_buffer_address,
    2406           0 :                                       1,
    2407           0 :                                       runtime_spad );
    2408           0 :     }
    2409           0 :   }
    2410             : 
    2411           0 :   } FD_SPAD_FRAME_END;
    2412           0 : }
    2413             : 
    2414             : static void
    2415             : fd_feature_activate( fd_exec_slot_ctx_t *   slot_ctx,
    2416             :                      fd_feature_id_t const * id,
    2417             :                      uchar const             acct[ static 32 ],
    2418           0 :                      fd_spad_t *             runtime_spad ) {
    2419             : 
    2420             :   // Skip reverted features from being activated
    2421           0 :   if( id->reverted==1 ) {
    2422           0 :     return;
    2423           0 :   }
    2424             : 
    2425           0 :   FD_TXN_ACCOUNT_DECL( acct_rec );
    2426           0 :   int err = fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, (fd_pubkey_t*)acct, acct_rec );
    2427           0 :   if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) {
    2428           0 :     return;
    2429           0 :   }
    2430             : 
    2431           0 :   FD_SPAD_FRAME_BEGIN( runtime_spad ) {
    2432             : 
    2433           0 :   fd_bincode_decode_ctx_t ctx = {
    2434           0 :       .data    = acct_rec->const_data,
    2435           0 :       .dataend = acct_rec->const_data + acct_rec->const_meta->dlen,
    2436           0 :   };
    2437             : 
    2438           0 :   ulong total_sz   = 0UL;
    2439           0 :   int   decode_err = 0;
    2440           0 :   decode_err = fd_feature_decode_footprint( &ctx, &total_sz );
    2441           0 :   if( FD_UNLIKELY( decode_err ) ) {
    2442           0 :     FD_LOG_WARNING(( "Failed to decode feature account %s (%d)", FD_BASE58_ENC_32_ALLOCA( acct ), decode_err ));
    2443           0 :     return;
    2444           0 :   }
    2445             : 
    2446           0 :   uchar * mem = fd_spad_alloc( runtime_spad, alignof(fd_feature_t), total_sz );
    2447           0 :   if( FD_UNLIKELY( !mem ) ) {
    2448           0 :     FD_LOG_ERR(( "Unable to allocate memory for feature" ));
    2449           0 :   }
    2450             : 
    2451           0 :   fd_feature_t * feature = fd_feature_decode( mem, &ctx );
    2452             : 
    2453           0 :   if( feature->has_activated_at ) {
    2454           0 :     FD_LOG_INFO(( "feature already activated - acc: %s, slot: %lu", FD_BASE58_ENC_32_ALLOCA( acct ), feature->activated_at ));
    2455           0 :     fd_features_set(&slot_ctx->epoch_ctx->features, id, feature->activated_at);
    2456           0 :   } else {
    2457           0 :     FD_LOG_INFO(( "Feature %s not activated at %lu, activating", FD_BASE58_ENC_32_ALLOCA( acct ), feature->activated_at ));
    2458             : 
    2459           0 :     FD_TXN_ACCOUNT_DECL( modify_acct_rec );
    2460           0 :     err = fd_acc_mgr_modify( slot_ctx->acc_mgr, slot_ctx->funk_txn, (fd_pubkey_t *)acct, 0, 0UL, modify_acct_rec );
    2461           0 :     if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) {
    2462           0 :       return;
    2463           0 :     }
    2464             : 
    2465           0 :     feature->has_activated_at = 1;
    2466           0 :     feature->activated_at     = slot_ctx->slot_bank.slot;
    2467           0 :     fd_bincode_encode_ctx_t encode_ctx = {
    2468           0 :       .data    = modify_acct_rec->data,
    2469           0 :       .dataend = modify_acct_rec->data + modify_acct_rec->meta->dlen,
    2470           0 :     };
    2471           0 :     int encode_err = fd_feature_encode( feature, &encode_ctx );
    2472           0 :     if( FD_UNLIKELY( encode_err != FD_BINCODE_SUCCESS ) ) {
    2473           0 :       FD_LOG_ERR(( "Failed to encode feature account %s (%d)", FD_BASE58_ENC_32_ALLOCA( acct ), decode_err ));
    2474           0 :     }
    2475           0 :   }
    2476             : 
    2477           0 :   } FD_SPAD_FRAME_END;
    2478           0 : }
    2479             : 
    2480             : static void
    2481           0 : fd_features_activate( fd_exec_slot_ctx_t * slot_ctx, fd_spad_t * runtime_spad ) {
    2482           0 :   for( fd_feature_id_t const * id = fd_feature_iter_init();
    2483           0 :                                    !fd_feature_iter_done( id );
    2484           0 :                                id = fd_feature_iter_next( id ) ) {
    2485           0 :     fd_feature_activate( slot_ctx, id, id->id.key, runtime_spad );
    2486           0 :   }
    2487           0 : }
    2488             : 
    2489             : uint
    2490           0 : fd_runtime_is_epoch_boundary( fd_epoch_bank_t * epoch_bank, ulong curr_slot, ulong prev_slot ) {
    2491           0 :   ulong slot_idx;
    2492           0 :   ulong prev_epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, prev_slot, &slot_idx );
    2493           0 :   ulong new_epoch  = fd_slot_to_epoch( &epoch_bank->epoch_schedule, curr_slot, &slot_idx );
    2494             : 
    2495           0 :   return ( prev_epoch < new_epoch || slot_idx == 0 );
    2496           0 : }
    2497             : 
    2498             : /* Starting a new epoch.
    2499             :   New epoch:        T
    2500             :   Just ended epoch: T-1
    2501             :   Epoch before:     T-2
    2502             : 
    2503             :   In this function:
    2504             :   - stakes in T-2 (slot_ctx->slot_bank.epoch_stakes) should be replaced by T-1 (epoch_bank->next_epoch_stakes)
    2505             :   - stakes at T-1 (epoch_bank->next_epoch_stakes) should be replaced by updated stakes at T (stakes->vote_accounts)
    2506             :   - leader schedule should be calculated using new T-2 stakes (slot_ctx->slot_bank.epoch_stakes)
    2507             : 
    2508             :   Invariant during an epoch T:
    2509             :   epoch_bank->next_epoch_stakes    holds the stakes at T-1
    2510             :   slot_ctx->slot_bank.epoch_stakes holds the stakes at T-2
    2511             :  */
    2512             : /* process for the start of a new epoch */
    2513             : static void
    2514             : fd_runtime_process_new_epoch( fd_exec_slot_ctx_t * slot_ctx,
    2515             :                               ulong                parent_epoch,
    2516             :                               fd_tpool_t *         tpool,
    2517             :                               fd_spad_t * *        exec_spads,
    2518             :                               ulong                exec_spad_cnt,
    2519           0 :                               fd_spad_t *          runtime_spad ) {
    2520           0 :   FD_LOG_NOTICE(( "fd_process_new_epoch start" ));
    2521             : 
    2522           0 :   long start = fd_log_wallclock();
    2523             : 
    2524           0 :   ulong             slot;
    2525           0 :   fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
    2526           0 :   ulong             epoch      = fd_slot_to_epoch( &epoch_bank->epoch_schedule, slot_ctx->slot_bank.slot, &slot );
    2527             : 
    2528             :   /* Activate new features
    2529             :      https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6587-L6598 */
    2530           0 :   fd_features_activate( slot_ctx, runtime_spad );
    2531           0 :   fd_features_restore( slot_ctx, runtime_spad );
    2532             : 
    2533             :   /* Apply builtin program feature transitions
    2534             :      https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6621-L6624 */
    2535           0 :   fd_apply_builtin_program_feature_transitions( slot_ctx, runtime_spad );
    2536             : 
    2537             :   /* Change the speed of the poh clock
    2538             :      https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6627-L6649 */
    2539           0 :   if( FD_FEATURE_JUST_ACTIVATED( slot_ctx, update_hashes_per_tick6 ) ) {
    2540           0 :     epoch_bank->hashes_per_tick = UPDATED_HASHES_PER_TICK6;
    2541           0 :   } else if( FD_FEATURE_JUST_ACTIVATED( slot_ctx, update_hashes_per_tick5 ) ) {
    2542           0 :     epoch_bank->hashes_per_tick = UPDATED_HASHES_PER_TICK5;
    2543           0 :   } else if( FD_FEATURE_JUST_ACTIVATED( slot_ctx, update_hashes_per_tick4 ) ) {
    2544           0 :     epoch_bank->hashes_per_tick = UPDATED_HASHES_PER_TICK4;
    2545           0 :   } else if( FD_FEATURE_JUST_ACTIVATED( slot_ctx, update_hashes_per_tick3 ) ) {
    2546           0 :     epoch_bank->hashes_per_tick = UPDATED_HASHES_PER_TICK3;
    2547           0 :   } else if( FD_FEATURE_JUST_ACTIVATED( slot_ctx, update_hashes_per_tick2 ) ) {
    2548           0 :     epoch_bank->hashes_per_tick = UPDATED_HASHES_PER_TICK2;
    2549           0 :   }
    2550             : 
    2551             :   /* Get the new rate activation epoch */
    2552           0 :   int _err[1];
    2553           0 :   ulong   new_rate_activation_epoch_val = 0UL;
    2554           0 :   ulong * new_rate_activation_epoch     = &new_rate_activation_epoch_val;
    2555           0 :   int     is_some                       = fd_new_warmup_cooldown_rate_epoch( slot_ctx->slot_bank.slot,
    2556           0 :                                                                              slot_ctx->sysvar_cache,
    2557           0 :                                                                              &slot_ctx->epoch_ctx->features,
    2558           0 :                                                                              new_rate_activation_epoch,
    2559           0 :                                                                              _err );
    2560           0 :   if( FD_UNLIKELY( !is_some ) ) {
    2561           0 :     new_rate_activation_epoch = NULL;
    2562           0 :   }
    2563             : 
    2564           0 :   fd_epoch_info_t temp_info = {0};
    2565           0 :   fd_epoch_info_new( &temp_info );
    2566             : 
    2567             :   /* If appropiate, use the stakes at T-1 to generate the leader schedule instead of T-2.
    2568             :       This is due to a subtlety in how Agave's stake caches interact when loading from snapshots.
    2569             :       See the comment in fd_exec_slot_ctx_recover_. */
    2570           0 :   if( slot_ctx->slot_bank.has_use_preceeding_epoch_stakes && slot_ctx->slot_bank.use_preceeding_epoch_stakes == epoch ) {
    2571           0 :     fd_update_epoch_stakes( slot_ctx );
    2572           0 :   }
    2573             : 
    2574             :   /* Updates stake history sysvar accumulated values. */
    2575           0 :   fd_stakes_activate_epoch( slot_ctx,
    2576           0 :                             new_rate_activation_epoch,
    2577           0 :                             &temp_info,
    2578           0 :                             tpool,
    2579           0 :                             exec_spads,
    2580           0 :                             exec_spad_cnt,
    2581           0 :                             runtime_spad );
    2582             : 
    2583             :   /* Update the stakes epoch value to the new epoch */
    2584           0 :   epoch_bank->stakes.epoch = epoch;
    2585             : 
    2586           0 :   fd_update_stake_delegations( slot_ctx, &temp_info );
    2587             : 
    2588             :   /* Refresh vote accounts in stakes cache using updated stake weights, and merges slot bank vote accounts with the epoch bank vote accounts.
    2589             :     https://github.com/anza-xyz/agave/blob/v2.1.6/runtime/src/stakes.rs#L363-L370 */
    2590           0 :   fd_stake_history_t const * history = (fd_stake_history_t const *)fd_sysvar_cache_stake_history( slot_ctx->sysvar_cache );
    2591           0 :   if( FD_UNLIKELY( !history ) ) {
    2592           0 :     FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
    2593           0 :   }
    2594             : 
    2595             :   /* In order to correctly handle the lifetimes of allocations for partitioned
    2596             :      epoch rewards, we will push a spad frame when rewards partitioning starts.
    2597             :      We will only pop this frame when all of the rewards for the epoch have
    2598             :      been distributed. As a note, this is technically not the most optimal use
    2599             :      of memory as some data structures used can be freed when this function
    2600             :      exits, but this is okay since the additional allocations are on the order
    2601             :      of a few megabytes and are freed after a few thousand slots. */
    2602             : 
    2603           0 :   fd_spad_push( runtime_spad );
    2604             : 
    2605           0 :   fd_refresh_vote_accounts( slot_ctx,
    2606           0 :                             history,
    2607           0 :                             new_rate_activation_epoch,
    2608           0 :                             &temp_info,
    2609           0 :                             tpool,
    2610           0 :                             exec_spads,
    2611           0 :                             exec_spad_cnt,
    2612           0 :                             runtime_spad );
    2613             : 
    2614             :   /* Distribute rewards */
    2615           0 :   fd_hash_t const * parent_blockhash = slot_ctx->slot_bank.block_hash_queue.last_hash;
    2616           0 :   if( FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, enable_partitioned_epoch_reward ) ||
    2617           0 :       FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, partitioned_epoch_rewards_superfeature ) ) {
    2618           0 :     FD_LOG_NOTICE(( "fd_begin_partitioned_rewards" ));
    2619           0 :     fd_begin_partitioned_rewards( slot_ctx,
    2620           0 :                                   parent_blockhash,
    2621           0 :                                   parent_epoch,
    2622           0 :                                   &temp_info,
    2623           0 :                                   tpool,
    2624           0 :                                   exec_spads,
    2625           0 :                                   exec_spad_cnt,
    2626           0 :                                   runtime_spad );
    2627           0 :   } else {
    2628           0 :     fd_update_rewards( slot_ctx,
    2629           0 :                        parent_blockhash,
    2630           0 :                        parent_epoch,
    2631           0 :                        &temp_info,
    2632           0 :                        tpool,
    2633           0 :                        exec_spads,
    2634           0 :                        exec_spad_cnt,
    2635           0 :                        runtime_spad );
    2636           0 :   }
    2637             : 
    2638             :   /* Replace stakes at T-2 (slot_ctx->slot_bank.epoch_stakes) by stakes at T-1 (epoch_bank->next_epoch_stakes) */
    2639           0 :   fd_update_epoch_stakes( slot_ctx );
    2640             : 
    2641             :   /* Replace stakes at T-1 (epoch_bank->next_epoch_stakes) by updated stakes at T (stakes->vote_accounts) */
    2642           0 :   fd_update_next_epoch_stakes( slot_ctx );
    2643             : 
    2644             :   /* Update current leaders using slot_ctx->slot_bank.epoch_stakes (new T-2 stakes) */
    2645           0 :   fd_runtime_update_leaders( slot_ctx, slot_ctx->slot_bank.slot, runtime_spad );
    2646             : 
    2647           0 :   fd_calculate_epoch_accounts_hash_values( slot_ctx );
    2648             : 
    2649           0 :   FD_LOG_NOTICE(( "fd_process_new_epoch end" ));
    2650             : 
    2651           0 :   long end = fd_log_wallclock();
    2652           0 :   FD_LOG_NOTICE(("fd_process_new_epoch took %ld ns", end - start));
    2653           0 : }
    2654             : 
    2655             : /******************************************************************************/
    2656             : /* Block Parsing                                                              */
    2657             : /******************************************************************************/
    2658             : 
    2659             : /* Block iteration and parsing */
    2660             : 
    2661             : /* As a note, all of the logic in this section is used by the full firedancer
    2662             :    client. The store tile uses these APIs to help parse raw (micro)blocks
    2663             :    received from the network. */
    2664             : 
    2665             : /* Helpers */
    2666             : 
    2667             : static int
    2668             : fd_runtime_parse_microblock_hdr( void const *          buf FD_PARAM_UNUSED,
    2669           0 :                                  ulong                 buf_sz ) {
    2670             : 
    2671           0 :   if( FD_UNLIKELY( buf_sz<sizeof(fd_microblock_hdr_t) ) ) {
    2672           0 :     return -1;
    2673           0 :   }
    2674           0 :   return 0;
    2675           0 : }
    2676             : 
    2677             : /* if we are currently in the middle of a batch, batch_cnt will include the current batch.
    2678             :    if we are at the start of a batch, batch_cnt will include the current batch. */
    2679             : static fd_raw_block_txn_iter_t
    2680             : find_next_txn_in_raw_block( uchar const *                  orig_data,
    2681             :                             fd_block_entry_batch_t const * batches, /* The batch we are currently consuming. */
    2682             :                             ulong                          batch_cnt, /* Includes batch we are currently consuming. */
    2683             :                             ulong                          curr_offset,
    2684           0 :                             ulong                          num_microblocks ) {
    2685             : 
    2686             :   /* At this point, all the transactions in the current microblock have been consumed
    2687             :      by fd_raw_block_txn_iter_next */
    2688             : 
    2689             :   /* Case 1: there are microblocks remaining in the current batch */
    2690           0 :   for( ulong i=0UL; i<num_microblocks; i++ ) {
    2691           0 :     ulong microblock_hdr_size            = sizeof(fd_microblock_hdr_t);
    2692           0 :     fd_microblock_info_t microblock_info = {0};
    2693           0 :     if( FD_UNLIKELY( fd_runtime_parse_microblock_hdr( orig_data + curr_offset,
    2694           0 :                                                       batches->end_off - curr_offset ) ) ) {
    2695             :       /* TODO: improve error handling */
    2696           0 :       FD_LOG_ERR(( "premature end of batch" ));
    2697           0 :     }
    2698           0 :     microblock_info.microblock.hdr = (fd_microblock_hdr_t const * )(orig_data + curr_offset);
    2699           0 :     curr_offset += microblock_hdr_size;
    2700             : 
    2701             :     /* If we have found a microblock with transactions in the current batch, return that */
    2702           0 :     if( FD_LIKELY( microblock_info.microblock.hdr->txn_cnt ) ) {
    2703           0 :       return (fd_raw_block_txn_iter_t){
    2704           0 :         .curr_batch            = batches,
    2705           0 :         .orig_data             = orig_data,
    2706           0 :         .remaining_batches     = batch_cnt,
    2707           0 :         .remaining_microblocks = fd_ulong_sat_sub( fd_ulong_sat_sub(num_microblocks, i), 1UL),
    2708           0 :         .remaining_txns        = microblock_info.microblock.hdr->txn_cnt,
    2709           0 :         .curr_offset           = curr_offset,
    2710           0 :         .curr_txn_sz           = ULONG_MAX
    2711           0 :       };
    2712           0 :     }
    2713           0 :   }
    2714             : 
    2715             :   /* If we have consumed the current batch, but did not find any txns, we need to move on to the next one */
    2716           0 :   curr_offset = batches->end_off;
    2717           0 :   batch_cnt   = fd_ulong_sat_sub( batch_cnt, 1UL );
    2718           0 :   batches++;
    2719             : 
    2720             :   /* Case 2: need to find the next batch with a microblock in that has a non-zero number of txns */
    2721           0 :   for( ulong i=0UL; i<batch_cnt; i++ ) {
    2722             :     /* Sanity-check that we have not over-shot the end of the batch */
    2723           0 :     ulong const batch_end_off = batches[i].end_off;
    2724           0 :     if( FD_UNLIKELY( curr_offset+sizeof(ulong)>batch_end_off ) ) {
    2725           0 :       FD_LOG_ERR(( "premature end of batch" ));
    2726           0 :     }
    2727             : 
    2728             :     /* Consume the ulong describing how many microblocks there are */
    2729           0 :     num_microblocks = FD_LOAD( ulong, orig_data + curr_offset );
    2730           0 :     curr_offset    += sizeof(ulong);
    2731             : 
    2732             :     /* Iterate over each microblock until we find one with a non-zero txn cnt */
    2733           0 :     for( ulong j=0UL; j<num_microblocks; j++ ) {
    2734           0 :       ulong microblock_hdr_size            = sizeof(fd_microblock_hdr_t);
    2735           0 :       fd_microblock_info_t microblock_info = {0};
    2736           0 :       if( FD_UNLIKELY( fd_runtime_parse_microblock_hdr( orig_data + curr_offset,
    2737           0 :                                                         batch_end_off - curr_offset ) ) ) {
    2738             :         /* TODO: improve error handling */
    2739           0 :         FD_LOG_ERR(( "premature end of batch" ));
    2740           0 :       }
    2741           0 :       microblock_info.microblock.hdr = (fd_microblock_hdr_t const * )(orig_data + curr_offset);
    2742           0 :       curr_offset += microblock_hdr_size;
    2743             : 
    2744             :       /* If we have found a microblock with a non-zero number of transactions in, return that */
    2745           0 :       if( FD_LIKELY( microblock_info.microblock.hdr->txn_cnt ) ) {
    2746           0 :         return (fd_raw_block_txn_iter_t){
    2747           0 :           .curr_batch            = &batches[i],
    2748           0 :           .orig_data             = orig_data,
    2749           0 :           .remaining_batches     = fd_ulong_sat_sub( batch_cnt, i ),
    2750           0 :           .remaining_microblocks = fd_ulong_sat_sub( fd_ulong_sat_sub( num_microblocks, j ), 1UL ),
    2751           0 :           .remaining_txns        = microblock_info.microblock.hdr->txn_cnt,
    2752           0 :           .curr_offset           = curr_offset,
    2753           0 :           .curr_txn_sz           = ULONG_MAX
    2754           0 :         };
    2755           0 :       }
    2756           0 :     }
    2757             : 
    2758             :     /* Skip to the start of the next batch */
    2759           0 :     curr_offset = batch_end_off;
    2760           0 :   }
    2761             : 
    2762             :   /* Case 3: we didn't manage to find any microblocks with non-zero transaction counts in */
    2763           0 :   return (fd_raw_block_txn_iter_t) {
    2764           0 :     .curr_batch            = batches,
    2765           0 :     .orig_data             = orig_data,
    2766           0 :     .remaining_batches     = 0UL,
    2767           0 :     .remaining_microblocks = 0UL,
    2768           0 :     .remaining_txns        = 0UL,
    2769           0 :     .curr_offset           = curr_offset,
    2770           0 :     .curr_txn_sz           = ULONG_MAX
    2771           0 :   };
    2772           0 : }
    2773             : 
    2774             : /* Public API */
    2775             : 
    2776             : fd_raw_block_txn_iter_t
    2777             : fd_raw_block_txn_iter_init( uchar const *                  orig_data,
    2778             :                             fd_block_entry_batch_t const * batches,
    2779           0 :                             ulong                          batch_cnt ) {
    2780             :   /* In general, every read of a lower level count should lead to a
    2781             :      decrement of a higher level count.  For example, reading a count
    2782             :      of microblocks should lead to a decrement of the number of
    2783             :      remaining batches.  In some sense, the batch count is drained into
    2784             :      the microblock count. */
    2785             : 
    2786           0 :   ulong num_microblocks = FD_LOAD( ulong, orig_data );
    2787           0 :   return find_next_txn_in_raw_block( orig_data, batches, batch_cnt, sizeof(ulong), num_microblocks );
    2788           0 : }
    2789             : 
    2790             : ulong
    2791           0 : fd_raw_block_txn_iter_done( fd_raw_block_txn_iter_t iter ) {
    2792           0 :   return iter.remaining_batches==0UL && iter.remaining_microblocks==0UL && iter.remaining_txns==0UL;
    2793           0 : }
    2794             : 
    2795             : fd_raw_block_txn_iter_t
    2796           0 : fd_raw_block_txn_iter_next( fd_raw_block_txn_iter_t iter ) {
    2797           0 :   ulong const batch_end_off = iter.curr_batch->end_off;
    2798           0 :   fd_txn_p_t out_txn;
    2799           0 :   if( iter.curr_txn_sz == ULONG_MAX ) {
    2800           0 :     ulong payload_sz = 0;
    2801           0 :     ulong txn_sz = fd_txn_parse_core( iter.orig_data + iter.curr_offset, fd_ulong_min( batch_end_off - iter.curr_offset, FD_TXN_MTU), TXN(&out_txn), NULL, &payload_sz );
    2802           0 :     if( FD_UNLIKELY( !txn_sz || txn_sz>FD_TXN_MTU ) ) {
    2803           0 :       FD_LOG_ERR(("Invalid txn parse"));
    2804           0 :     }
    2805           0 :     iter.curr_offset += payload_sz;
    2806           0 :   } else {
    2807           0 :     iter.curr_offset += iter.curr_txn_sz;
    2808           0 :     iter.curr_txn_sz  = ULONG_MAX;
    2809           0 :   }
    2810             : 
    2811           0 :   if( --iter.remaining_txns ) {
    2812           0 :     return iter;
    2813           0 :   }
    2814             : 
    2815           0 :   return find_next_txn_in_raw_block( iter.orig_data,
    2816           0 :                                      iter.curr_batch,
    2817           0 :                                      iter.remaining_batches,
    2818           0 :                                      iter.curr_offset,
    2819           0 :                                      iter.remaining_microblocks );
    2820           0 : }
    2821             : 
    2822             : void
    2823           0 : fd_raw_block_txn_iter_ele( fd_raw_block_txn_iter_t iter, fd_txn_p_t * out_txn ) {
    2824           0 :   ulong const batch_end_off = iter.curr_batch->end_off;
    2825           0 :   ulong       payload_sz    = 0UL;
    2826           0 :   ulong       txn_sz        = fd_txn_parse_core( iter.orig_data + iter.curr_offset,
    2827           0 :                                                  fd_ulong_min( batch_end_off - iter.curr_offset, FD_TXN_MTU ),
    2828           0 :                                                  TXN( out_txn ), NULL, &payload_sz );
    2829             : 
    2830           0 :   if( FD_UNLIKELY( !txn_sz || txn_sz>FD_TXN_MTU ) ) {
    2831           0 :     FD_LOG_ERR(( "Invalid txn parse %lu", txn_sz ));
    2832           0 :   }
    2833           0 :   fd_memcpy( out_txn->payload, iter.orig_data + iter.curr_offset, payload_sz );
    2834           0 :   out_txn->payload_sz = (ushort)payload_sz;
    2835           0 :   iter.curr_txn_sz    = payload_sz;
    2836           0 : }
    2837             : 
    2838             : /******************************************************************************/
    2839             : /* Block Parsing logic (Only for offline replay)                              */
    2840             : /******************************************************************************/
    2841             : 
    2842             : /* The below runtime block parsing and block destroying logic is ONLY used in
    2843             :    offline replay to simulate the block parsing/freeing that would occur in
    2844             :    the full, live firedancer client. This is done via two APIs:
    2845             :    fd_runtime_block_prepare and fd_runtime_block_destroy. */
    2846             : 
    2847             : /* Helpers for fd_runtime_block_prepare */
    2848             : 
    2849             : static int
    2850             : fd_runtime_parse_microblock_txns( void const *                buf,
    2851             :                                   ulong                       buf_sz,
    2852             :                                   fd_microblock_hdr_t const * microblock_hdr,
    2853             :                                   fd_txn_p_t *                out_txns,
    2854             :                                   ulong *                     out_signature_cnt,
    2855             :                                   ulong *                     out_account_cnt,
    2856           0 :                                   ulong *                     out_microblock_txns_sz ) {
    2857             : 
    2858           0 :   ulong buf_off       = 0UL;
    2859           0 :   ulong signature_cnt = 0UL;
    2860           0 :   ulong account_cnt   = 0UL;
    2861             : 
    2862           0 :   for( ulong i=0UL; i<microblock_hdr->txn_cnt; i++ ) {
    2863           0 :     ulong payload_sz = 0UL;
    2864           0 :     ulong txn_sz     = fd_txn_parse_core( (uchar const *)buf + buf_off,
    2865           0 :                                           fd_ulong_min( buf_sz-buf_off, FD_TXN_MTU ),
    2866           0 :                                           TXN( &out_txns[i] ),
    2867           0 :                                           NULL,
    2868           0 :                                           &payload_sz );
    2869           0 :     if( FD_UNLIKELY( !txn_sz || txn_sz>FD_TXN_MTU || !payload_sz  ) ) {
    2870           0 :       return -1;
    2871           0 :     }
    2872             : 
    2873           0 :     fd_memcpy( out_txns[i].payload, (uchar *)buf + buf_off, payload_sz );
    2874           0 :     out_txns[i].payload_sz = (ushort)payload_sz;
    2875             : 
    2876           0 :     signature_cnt += TXN( &out_txns[i] )->signature_cnt;
    2877           0 :     account_cnt   += fd_txn_account_cnt( TXN(&out_txns[i]), FD_TXN_ACCT_CAT_ALL );
    2878           0 :     buf_off       += payload_sz;
    2879           0 :   }
    2880             : 
    2881           0 :   *out_signature_cnt      = signature_cnt;
    2882           0 :   *out_account_cnt        = account_cnt;
    2883           0 :   *out_microblock_txns_sz = buf_off;
    2884             : 
    2885           0 :   return 0;
    2886           0 : }
    2887             : 
    2888             : static int
    2889             : fd_runtime_microblock_prepare( void const *           buf,
    2890             :                                ulong                  buf_sz,
    2891             :                                fd_spad_t *            runtime_spad,
    2892           0 :                                fd_microblock_info_t * out_microblock_info ) {
    2893             : 
    2894           0 :   fd_microblock_info_t microblock_info = {
    2895           0 :     .signature_cnt  = 0UL,
    2896           0 :   };
    2897           0 :   ulong buf_off = 0UL;
    2898           0 :   ulong hdr_sz  = sizeof(fd_microblock_hdr_t);
    2899           0 :   if( FD_UNLIKELY( fd_runtime_parse_microblock_hdr( buf, buf_sz ) ) ) {
    2900           0 :     return -1;
    2901           0 :   }
    2902           0 :   microblock_info.microblock.hdr = (fd_microblock_hdr_t const *)buf;
    2903           0 :   buf_off += hdr_sz;
    2904             : 
    2905           0 :   ulong txn_cnt        = microblock_info.microblock.hdr->txn_cnt;
    2906           0 :   microblock_info.txns = fd_spad_alloc( runtime_spad, alignof(fd_txn_p_t), txn_cnt * sizeof(fd_txn_p_t) );
    2907           0 :   ulong txns_sz        = 0UL;
    2908           0 :   if( FD_UNLIKELY( fd_runtime_parse_microblock_txns( (uchar *)buf + buf_off,
    2909           0 :                                                      buf_sz - buf_off,
    2910           0 :                                                      microblock_info.microblock.hdr,
    2911           0 :                                                      microblock_info.txns,
    2912           0 :                                                      &microblock_info.signature_cnt,
    2913           0 :                                                      &microblock_info.account_cnt,
    2914           0 :                                                      &txns_sz ) ) ) {
    2915           0 :     return -1;
    2916           0 :   }
    2917             : 
    2918           0 :   buf_off                          += txns_sz;
    2919           0 :   microblock_info.raw_microblock_sz = buf_off;
    2920           0 :   *out_microblock_info              = microblock_info;
    2921             : 
    2922           0 :   return 0;
    2923           0 : }
    2924             : 
    2925             : static int
    2926             : fd_runtime_microblock_batch_prepare( void const *                 buf,
    2927             :                                      ulong                        buf_sz,
    2928             :                                      fd_spad_t *                  runtime_spad,
    2929           0 :                                      fd_microblock_batch_info_t * out_microblock_batch_info ) {
    2930             : 
    2931           0 :   fd_microblock_batch_info_t microblock_batch_info = {
    2932           0 :     .raw_microblock_batch = buf,
    2933           0 :     .signature_cnt        = 0UL,
    2934           0 :     .txn_cnt              = 0UL,
    2935           0 :     .account_cnt          = 0UL,
    2936           0 :   };
    2937           0 :   ulong buf_off = 0UL;
    2938             : 
    2939           0 :   if( FD_UNLIKELY( buf_sz<sizeof(ulong) ) ) {
    2940           0 :     FD_LOG_WARNING(( "microblock batch buffer too small" ));
    2941           0 :     return -1;
    2942           0 :   }
    2943           0 :   ulong microblock_cnt = FD_LOAD( ulong, buf );
    2944           0 :   buf_off             += sizeof(ulong);
    2945             : 
    2946           0 :   microblock_batch_info.microblock_cnt   = microblock_cnt;
    2947           0 :   microblock_batch_info.microblock_infos = fd_spad_alloc( runtime_spad, alignof(fd_microblock_info_t), microblock_cnt * sizeof(fd_microblock_info_t) );
    2948             : 
    2949           0 :   ulong signature_cnt = 0UL;
    2950           0 :   ulong txn_cnt       = 0UL;
    2951           0 :   ulong account_cnt   = 0UL;
    2952           0 :   for( ulong i=0UL; i<microblock_cnt; i++ ) {
    2953           0 :     fd_microblock_info_t * microblock_info = &microblock_batch_info.microblock_infos[i];
    2954           0 :     if( FD_UNLIKELY( fd_runtime_microblock_prepare( (uchar const *)buf + buf_off, buf_sz - buf_off, runtime_spad, microblock_info ) ) ) {
    2955           0 :       return -1;
    2956           0 :     }
    2957             : 
    2958           0 :     signature_cnt += microblock_info->signature_cnt;
    2959           0 :     txn_cnt       += microblock_info->microblock.hdr->txn_cnt;
    2960           0 :     account_cnt   += microblock_info->account_cnt;
    2961           0 :     buf_off       += microblock_info->raw_microblock_sz;
    2962           0 :   }
    2963             : 
    2964           0 :   microblock_batch_info.signature_cnt           = signature_cnt;
    2965           0 :   microblock_batch_info.txn_cnt                 = txn_cnt;
    2966           0 :   microblock_batch_info.account_cnt             = account_cnt;
    2967           0 :   microblock_batch_info.raw_microblock_batch_sz = buf_off;
    2968             : 
    2969           0 :   *out_microblock_batch_info                    = microblock_batch_info;
    2970             : 
    2971           0 :   return 0;
    2972           0 : }
    2973             : 
    2974             : /* This function is used for parsing/preparing blocks during offline runtime replay. */
    2975             : static int
    2976             : fd_runtime_block_prepare( fd_blockstore_t         * blockstore,
    2977             :                           fd_block_t              * block,
    2978             :                           ulong                     slot,
    2979             :                           fd_spad_t               * runtime_spad,
    2980           0 :                           fd_runtime_block_info_t * out_block_info ) {
    2981           0 :   uchar const *                  buf         = fd_blockstore_block_data_laddr( blockstore, block );
    2982           0 :   ulong const                    buf_sz      = block->data_sz;
    2983           0 :   fd_block_entry_batch_t const * batch_laddr = fd_blockstore_block_batch_laddr( blockstore, block );
    2984           0 :   ulong const                    batch_cnt   = block->batch_cnt;
    2985             : 
    2986           0 :   fd_runtime_block_info_t block_info = {
    2987           0 :       .raw_block    = buf,
    2988           0 :       .raw_block_sz = buf_sz,
    2989           0 :   };
    2990             : 
    2991           0 :   ulong microblock_batch_cnt        = 0UL;
    2992           0 :   ulong microblock_cnt              = 0UL;
    2993           0 :   ulong signature_cnt               = 0UL;
    2994           0 :   ulong txn_cnt                     = 0UL;
    2995           0 :   ulong account_cnt                 = 0UL;
    2996           0 :   block_info.microblock_batch_infos = fd_spad_alloc( runtime_spad, alignof(fd_microblock_batch_info_t), block->batch_cnt * sizeof(fd_microblock_batch_info_t) );
    2997             : 
    2998           0 :   ulong buf_off = 0UL;
    2999           0 :   for( microblock_batch_cnt=0UL; microblock_batch_cnt < batch_cnt; microblock_batch_cnt++ ) {
    3000           0 :     ulong const batch_end_off = batch_laddr[ microblock_batch_cnt ].end_off;
    3001           0 :     fd_microblock_batch_info_t * microblock_batch_info = block_info.microblock_batch_infos + microblock_batch_cnt;
    3002           0 :     if( FD_UNLIKELY( fd_runtime_microblock_batch_prepare( buf + buf_off, batch_end_off - buf_off, runtime_spad, microblock_batch_info ) ) ) {
    3003           0 :       return -1;
    3004           0 :     }
    3005             : 
    3006           0 :     signature_cnt  += microblock_batch_info->signature_cnt;
    3007           0 :     txn_cnt        += microblock_batch_info->txn_cnt;
    3008           0 :     account_cnt    += microblock_batch_info->account_cnt;
    3009           0 :     microblock_cnt += microblock_batch_info->microblock_cnt;
    3010             : 
    3011           0 :     uchar allow_trailing = 1UL;
    3012           0 :     buf_off += microblock_batch_info->raw_microblock_batch_sz;
    3013           0 :     if( FD_UNLIKELY( buf_off > batch_end_off ) ) {
    3014           0 :       FD_LOG_ERR(( "parser error: shouldn't have been allowed to read past batch boundary" ));
    3015           0 :     }
    3016           0 :     if( FD_UNLIKELY( buf_off < batch_end_off ) ) {
    3017           0 :       if( FD_LIKELY( allow_trailing ) ) {
    3018           0 :         FD_LOG_NOTICE(( "ignoring %lu trailing bytes in slot %lu batch %lu", batch_end_off-buf_off, slot, microblock_batch_cnt ));
    3019           0 :       }
    3020           0 :       if( FD_UNLIKELY( !allow_trailing ) ) {
    3021           0 :         FD_LOG_WARNING(( "%lu trailing bytes in slot %lu batch %lu", batch_end_off-buf_off, slot, microblock_batch_cnt ));
    3022           0 :         return -1;
    3023           0 :       }
    3024           0 :     }
    3025           0 :     buf_off = batch_end_off;
    3026           0 :   }
    3027             : 
    3028           0 :   block_info.microblock_batch_cnt = microblock_batch_cnt;
    3029           0 :   block_info.microblock_cnt       = microblock_cnt;
    3030           0 :   block_info.signature_cnt        = signature_cnt;
    3031           0 :   block_info.txn_cnt              = txn_cnt;
    3032           0 :   block_info.account_cnt          = account_cnt;
    3033             : 
    3034           0 :   *out_block_info = block_info;
    3035             : 
    3036           0 :   return 0;
    3037           0 : }
    3038             : 
    3039             : /* Block collecting (Only for offline replay) */
    3040             : 
    3041             : static ulong
    3042             : fd_runtime_microblock_collect_txns( fd_microblock_info_t const * microblock_info,
    3043           0 :                                     fd_txn_p_t *                 out_txns ) {
    3044           0 :   ulong txn_cnt = microblock_info->microblock.hdr->txn_cnt;
    3045           0 :   fd_memcpy( out_txns, microblock_info->txns, txn_cnt * sizeof(fd_txn_p_t) );
    3046           0 :   return txn_cnt;
    3047           0 : }
    3048             : 
    3049             : static ulong
    3050             : fd_runtime_microblock_batch_collect_txns( fd_microblock_batch_info_t const * microblock_batch_info,
    3051           0 :                                           fd_txn_p_t *                       out_txns ) {
    3052           0 :   for( ulong i=0UL; i<microblock_batch_info->microblock_cnt; i++ ) {
    3053           0 :     ulong txns_collected = fd_runtime_microblock_collect_txns( &microblock_batch_info->microblock_infos[i], out_txns );
    3054           0 :     out_txns            += txns_collected;
    3055           0 :   }
    3056             : 
    3057           0 :   return microblock_batch_info->txn_cnt;
    3058           0 : }
    3059             : 
    3060             : static ulong
    3061             : fd_runtime_block_collect_txns( fd_runtime_block_info_t const * block_info,
    3062           0 :                                fd_txn_p_t *            out_txns ) {
    3063           0 :   for( ulong i=0UL; i<block_info->microblock_batch_cnt; i++ ) {
    3064           0 :     ulong txns_collected = fd_runtime_microblock_batch_collect_txns( &block_info->microblock_batch_infos[i], out_txns );
    3065           0 :     out_txns            += txns_collected;
    3066           0 :   }
    3067             : 
    3068           0 :   return block_info->txn_cnt;
    3069           0 : }
    3070             : 
    3071             : /******************************************************************************/
    3072             : /* Genesis                                                                    */
    3073             : /*******************************************************************************/
    3074             : 
    3075             : static void
    3076             : fd_runtime_init_program( fd_exec_slot_ctx_t * slot_ctx,
    3077           0 :                          fd_spad_t *          runtime_spad ) {
    3078           0 :   fd_sysvar_recent_hashes_init( slot_ctx, runtime_spad );
    3079           0 :   fd_sysvar_clock_init( slot_ctx );
    3080           0 :   fd_sysvar_slot_history_init( slot_ctx, runtime_spad );
    3081           0 :   fd_sysvar_epoch_schedule_init( slot_ctx );
    3082           0 :   if( !FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, disable_fees_sysvar ) ) {
    3083           0 :     fd_sysvar_fees_init( slot_ctx );
    3084           0 :   }
    3085           0 :   fd_sysvar_rent_init( slot_ctx );
    3086           0 :   fd_sysvar_stake_history_init( slot_ctx );
    3087           0 :   fd_sysvar_last_restart_slot_init( slot_ctx );
    3088             : 
    3089           0 :   fd_builtin_programs_init( slot_ctx );
    3090           0 :   fd_stake_program_config_init( slot_ctx );
    3091           0 : }
    3092             : 
    3093             : static void
    3094             : fd_runtime_init_bank_from_genesis( fd_exec_slot_ctx_t *  slot_ctx,
    3095             :                                    fd_genesis_solana_t * genesis_block,
    3096             :                                    fd_hash_t const *     genesis_hash,
    3097           0 :                                    fd_spad_t *           runtime_spad ) {
    3098           0 :   slot_ctx->slot_bank.slot = 0UL;
    3099             : 
    3100           0 :   memcpy( &slot_ctx->slot_bank.poh, genesis_hash->hash, FD_SHA256_HASH_SZ );
    3101           0 :   memset( slot_ctx->slot_bank.banks_hash.hash, 0, FD_SHA256_HASH_SZ );
    3102             : 
    3103           0 :   slot_ctx->slot_bank.fee_rate_governor      = genesis_block->fee_rate_governor;
    3104           0 :   slot_ctx->slot_bank.lamports_per_signature = 0UL;
    3105           0 :   slot_ctx->prev_lamports_per_signature      = 0UL;
    3106             : 
    3107           0 :   fd_poh_config_t *     poh        = &genesis_block->poh_config;
    3108           0 :   fd_exec_epoch_ctx_t * epoch_ctx  = slot_ctx->epoch_ctx;
    3109           0 :   fd_epoch_bank_t *     epoch_bank = fd_exec_epoch_ctx_epoch_bank( epoch_ctx );
    3110           0 :   if( poh->has_hashes_per_tick ) {
    3111           0 :     epoch_bank->hashes_per_tick = poh->hashes_per_tick;
    3112           0 :   } else {
    3113           0 :     epoch_bank->hashes_per_tick = 0UL;
    3114           0 :   }
    3115           0 :   epoch_bank->ticks_per_slot        = genesis_block->ticks_per_slot;
    3116           0 :   epoch_bank->genesis_creation_time = genesis_block->creation_time;
    3117           0 :   uint128 target_tick_duration      = ((uint128)poh->target_tick_duration.seconds * 1000000000UL + (uint128)poh->target_tick_duration.nanoseconds);
    3118           0 :   epoch_bank->ns_per_slot           = target_tick_duration * epoch_bank->ticks_per_slot;
    3119             : 
    3120           0 :   epoch_bank->slots_per_year          = SECONDS_PER_YEAR * (1000000000.0 / (double)target_tick_duration) / (double)epoch_bank->ticks_per_slot;
    3121           0 :   epoch_bank->genesis_creation_time   = genesis_block->creation_time;
    3122           0 :   slot_ctx->slot_bank.max_tick_height = epoch_bank->ticks_per_slot * (slot_ctx->slot_bank.slot + 1);
    3123           0 :   epoch_bank->epoch_schedule          = genesis_block->epoch_schedule;
    3124           0 :   epoch_bank->inflation               = genesis_block->inflation;
    3125           0 :   epoch_bank->rent                    = genesis_block->rent;
    3126           0 :   slot_ctx->slot_bank.block_height    = 0UL;
    3127             : 
    3128           0 :   slot_ctx->slot_bank.block_hash_queue.ages_root = NULL;
    3129           0 :   uchar * pool_mem = fd_spad_alloc( runtime_spad, fd_hash_hash_age_pair_t_map_align(), fd_hash_hash_age_pair_t_map_footprint( FD_HASH_FOOTPRINT * 400 ) );
    3130           0 :   slot_ctx->slot_bank.block_hash_queue.ages_pool = fd_hash_hash_age_pair_t_map_join( fd_hash_hash_age_pair_t_map_new( pool_mem, FD_HASH_FOOTPRINT * 400 ) );
    3131           0 :   fd_hash_hash_age_pair_t_mapnode_t * node       = fd_hash_hash_age_pair_t_map_acquire( slot_ctx->slot_bank.block_hash_queue.ages_pool );
    3132           0 :   node->elem = (fd_hash_hash_age_pair_t){
    3133           0 :     .key = *genesis_hash,
    3134           0 :     .val = (fd_hash_age_t){ .hash_index = 0UL, .fee_calculator = (fd_fee_calculator_t){.lamports_per_signature = 0UL}, .timestamp = (ulong)fd_log_wallclock() }
    3135           0 :   };
    3136           0 :   fd_hash_hash_age_pair_t_map_insert( slot_ctx->slot_bank.block_hash_queue.ages_pool, &slot_ctx->slot_bank.block_hash_queue.ages_root, node );
    3137           0 :   slot_ctx->slot_bank.block_hash_queue.last_hash_index = 0UL;
    3138           0 :   slot_ctx->slot_bank.block_hash_queue.last_hash       = fd_spad_alloc( runtime_spad, FD_HASH_ALIGN, FD_HASH_FOOTPRINT );
    3139           0 :   fd_memcpy( slot_ctx->slot_bank.block_hash_queue.last_hash, genesis_hash, FD_HASH_FOOTPRINT );
    3140           0 :   slot_ctx->slot_bank.block_hash_queue.max_age         = FD_BLOCKHASH_QUEUE_MAX_ENTRIES;
    3141             : 
    3142           0 :   slot_ctx->signature_cnt = 0UL;
    3143             : 
    3144             :   /* Derive epoch stakes */
    3145             : 
    3146           0 :   fd_vote_accounts_pair_t_mapnode_t * vacc_pool = fd_exec_epoch_ctx_stake_votes_join( epoch_ctx );
    3147           0 :   fd_vote_accounts_pair_t_mapnode_t * vacc_root = NULL;
    3148           0 :   FD_TEST( vacc_pool );
    3149             : 
    3150           0 :   fd_delegation_pair_t_mapnode_t * sacc_pool = fd_exec_epoch_ctx_stake_delegations_join( epoch_ctx );
    3151           0 :   fd_delegation_pair_t_mapnode_t * sacc_root = NULL;
    3152             : 
    3153           0 :   fd_acc_lamports_t capitalization = 0UL;
    3154             : 
    3155           0 :   for( ulong i=0UL; i<genesis_block->accounts_len; i++ ) {
    3156           0 :     fd_pubkey_account_pair_t const * acc = &genesis_block->accounts[i];
    3157           0 :     capitalization = fd_ulong_sat_add( capitalization, acc->account.lamports );
    3158             : 
    3159           0 :     if( !memcmp(acc->account.owner.key, fd_solana_vote_program_id.key, sizeof(fd_pubkey_t)) ) {
    3160             :       /* Vote Program Account */
    3161           0 :       fd_vote_accounts_pair_t_mapnode_t *node = fd_vote_accounts_pair_t_map_acquire(vacc_pool);
    3162           0 :       FD_TEST( node );
    3163             : 
    3164             :       /* FIXME: Reimplement when we try to fix genesis. */
    3165             :       // fd_vote_block_timestamp_t last_timestamp = {0};
    3166             :       // fd_pubkey_t               node_pubkey    = {0};
    3167             :       // FD_SPAD_FRAME_BEGIN( runtime_spad ) {
    3168             :       //   /* Deserialize content */
    3169             :       //   fd_vote_state_versioned_t vs[1];
    3170             :       //   fd_bincode_decode_ctx_t decode = {
    3171             :       //     .data    = acc->account.data,
    3172             :       //     .dataend = acc->account.data + acc->account.data_len,
    3173             :       //     .valloc  = fd_spad_virtual( runtime_spad )
    3174             :       //   };
    3175             :       //   int decode_err = fd_vote_state_versioned_decode( vs, &decode );
    3176             :       //   if( FD_UNLIKELY( decode_err!=FD_BINCODE_SUCCESS ) ) {
    3177             :       //     FD_LOG_WARNING(( "fd_vote_state_versioned_decode failed (%d)", decode_err ));
    3178             :       //     return;
    3179             :       //   }
    3180             : 
    3181             :       //   switch( vs->discriminant )
    3182             :       //   {
    3183             :       //   case fd_vote_state_versioned_enum_current:
    3184             :       //     last_timestamp = vs->inner.current.last_timestamp;
    3185             :       //     node_pubkey    = vs->inner.current.node_pubkey;
    3186             :       //     break;
    3187             :       //   case fd_vote_state_versioned_enum_v0_23_5:
    3188             :       //     last_timestamp = vs->inner.v0_23_5.last_timestamp;
    3189             :       //     node_pubkey    = vs->inner.v0_23_5.node_pubkey;
    3190             :       //     break;
    3191             :       //   case fd_vote_state_versioned_enum_v1_14_11:
    3192             :       //     last_timestamp = vs->inner.v1_14_11.last_timestamp;
    3193             :       //     node_pubkey    = vs->inner.v1_14_11.node_pubkey;
    3194             :       //     break;
    3195             :       //   default:
    3196             :       //     __builtin_unreachable();
    3197             :       //   }
    3198             : 
    3199             :       // } FD_SPAD_FRAME_END;
    3200             : 
    3201             :       // fd_memcpy(node->elem.key.key, acc->key.key, sizeof(fd_pubkey_t));
    3202             :       // node->elem.stake = acc->account.lamports;
    3203             :       // node->elem.value = (fd_solana_vote_account_t){
    3204             :       //   .lamports = acc->account.lamports,
    3205             :       //   .node_pubkey = node_pubkey,
    3206             :       //   .last_timestamp_ts = last_timestamp.timestamp,
    3207             :       //   .last_timestamp_slot = last_timestamp.slot,
    3208             :       //   .owner = acc->account.owner,
    3209             :       //   .executable = acc->account.executable,
    3210             :       //   .rent_epoch = acc->account.rent_epoch
    3211             :       // };
    3212             : 
    3213           0 :       fd_vote_accounts_pair_t_map_insert( vacc_pool, &vacc_root, node );
    3214             : 
    3215           0 :       FD_LOG_INFO(( "Adding genesis vote account: key=%s stake=%lu",
    3216           0 :                     FD_BASE58_ENC_32_ALLOCA( node->elem.key.key ),
    3217           0 :                     node->elem.stake ));
    3218           0 :     } else if( !memcmp( acc->account.owner.key, fd_solana_stake_program_id.key, sizeof(fd_pubkey_t) ) ) {
    3219             :       /* stake program account */
    3220           0 :       fd_stake_state_v2_t   stake_state   = {0};
    3221           0 :       fd_account_meta_t     meta          = { .dlen = acc->account.data_len };
    3222           0 :       fd_txn_account_t stake_account = {
    3223           0 :         .const_data = acc->account.data,
    3224           0 :         .const_meta = &meta,
    3225           0 :         .data = acc->account.data,
    3226           0 :         .meta = &meta
    3227           0 :       };
    3228           0 :       FD_TEST( fd_stake_get_state( &stake_account, &stake_state ) == 0 );
    3229           0 :       if( !stake_state.inner.stake.stake.delegation.stake ) {
    3230           0 :         continue;
    3231           0 :       }
    3232           0 :       fd_delegation_pair_t_mapnode_t   query_node = {0};
    3233           0 :       fd_memcpy(&query_node.elem.account, acc->key.key, sizeof(fd_pubkey_t));
    3234           0 :       fd_delegation_pair_t_mapnode_t * node = fd_delegation_pair_t_map_find( sacc_pool, sacc_root, &query_node );
    3235             : 
    3236           0 :       if( !node ) {
    3237           0 :         node = fd_delegation_pair_t_map_acquire( sacc_pool );
    3238           0 :         fd_memcpy( &node->elem.account, acc->key.key, sizeof(fd_pubkey_t) );
    3239           0 :         fd_memcpy( &node->elem.delegation, &stake_state.inner.stake.stake.delegation, sizeof(fd_delegation_t) );
    3240           0 :         fd_delegation_pair_t_map_insert( sacc_pool, &sacc_root, node );
    3241           0 :       } else {
    3242           0 :         fd_memcpy( &node->elem.account, acc->key.key, sizeof(fd_pubkey_t) );
    3243           0 :         fd_memcpy( &node->elem.delegation, &stake_state.inner.stake.stake.delegation, sizeof(fd_delegation_t) );
    3244           0 :       }
    3245           0 :     } else if( !memcmp(acc->account.owner.key, fd_solana_feature_program_id.key, sizeof(fd_pubkey_t)) ) {
    3246             :       /* Feature Account */
    3247             : 
    3248             :       /* Scan list of feature IDs to resolve address => feature offset */
    3249           0 :       fd_feature_id_t const *found = NULL;
    3250           0 :       for( fd_feature_id_t const * id = fd_feature_iter_init();
    3251           0 :            !fd_feature_iter_done( id );
    3252           0 :            id = fd_feature_iter_next( id ) ) {
    3253           0 :         if( !memcmp( acc->key.key, id->id.key, sizeof(fd_pubkey_t) ) ) {
    3254           0 :           found = id;
    3255           0 :           break;
    3256           0 :         }
    3257           0 :       }
    3258             : 
    3259           0 :       if( found ) {
    3260             :         /* Load feature activation */
    3261           0 :         FD_SPAD_FRAME_BEGIN( runtime_spad ) {
    3262           0 :           fd_bincode_decode_ctx_t decode = {
    3263           0 :             .data    = acc->account.data,
    3264           0 :             .dataend = acc->account.data + acc->account.data_len,
    3265           0 :           };
    3266             : 
    3267           0 :           ulong total_sz = 0UL;
    3268           0 :           int   err      = fd_feature_decode_footprint( &decode, &total_sz );
    3269           0 :           FD_TEST( err==FD_BINCODE_SUCCESS );
    3270             : 
    3271           0 :           uchar * mem = fd_spad_alloc( runtime_spad, FD_FEATURE_ALIGN, total_sz );
    3272           0 :           if( FD_UNLIKELY ( !mem ) ) {
    3273           0 :             FD_LOG_ERR(( "fd_spad_alloc failed" ));
    3274           0 :             return;
    3275           0 :           }
    3276             : 
    3277           0 :           fd_feature_t * feature = fd_feature_decode( mem, &decode );
    3278             : 
    3279           0 :           if( feature->has_activated_at ) {
    3280           0 :             FD_LOG_DEBUG(( "Feature %s activated at %lu (genesis)", FD_BASE58_ENC_32_ALLOCA( acc->key.key ), feature->activated_at ));
    3281           0 :             fd_features_set( &slot_ctx->epoch_ctx->features, found, feature->activated_at );
    3282           0 :           } else {
    3283           0 :             FD_LOG_DEBUG(( "Feature %s not activated (genesis)", FD_BASE58_ENC_32_ALLOCA( acc->key.key ) ));
    3284           0 :             fd_features_set( &slot_ctx->epoch_ctx->features, found, ULONG_MAX );
    3285           0 :           }
    3286           0 :         } FD_SPAD_FRAME_END;
    3287           0 :       }
    3288           0 :     }
    3289           0 :   }
    3290             : 
    3291           0 :   pool_mem = fd_spad_alloc( runtime_spad, fd_vote_accounts_pair_t_map_align(), fd_vote_accounts_pair_t_map_footprint( FD_HASH_FOOTPRINT * 400 ) );
    3292             : 
    3293           0 :   slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool = fd_vote_accounts_pair_t_map_join( fd_vote_accounts_pair_t_map_new( pool_mem, FD_HASH_FOOTPRINT * 400 ) );
    3294           0 :   slot_ctx->slot_bank.epoch_stakes.vote_accounts_root = NULL;
    3295             : 
    3296           0 :   fd_vote_accounts_pair_t_mapnode_t * next_pool = fd_exec_epoch_ctx_next_epoch_stakes_join( slot_ctx->epoch_ctx );
    3297           0 :   fd_vote_accounts_pair_t_mapnode_t * next_root = NULL;
    3298             : 
    3299           0 :   for( fd_vote_accounts_pair_t_mapnode_t *n = fd_vote_accounts_pair_t_map_minimum( vacc_pool, vacc_root );
    3300           0 :        n;
    3301           0 :        n = fd_vote_accounts_pair_t_map_successor( vacc_pool, n )) {
    3302           0 :     fd_vote_accounts_pair_t_mapnode_t * e = fd_vote_accounts_pair_t_map_acquire( slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool );
    3303           0 :     fd_memcpy( &e->elem, &n->elem, sizeof(fd_vote_accounts_pair_t) );
    3304           0 :     fd_vote_accounts_pair_t_map_insert( slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool, &slot_ctx->slot_bank.epoch_stakes.vote_accounts_root, e );
    3305             : 
    3306           0 :     fd_vote_accounts_pair_t_mapnode_t * next_e = fd_vote_accounts_pair_t_map_acquire( next_pool );
    3307           0 :     fd_memcpy( &next_e->elem, &n->elem, sizeof(fd_vote_accounts_pair_t) );
    3308           0 :     fd_vote_accounts_pair_t_map_insert( next_pool, &next_root, next_e );
    3309           0 :   }
    3310             : 
    3311           0 :   for( fd_delegation_pair_t_mapnode_t *n = fd_delegation_pair_t_map_minimum( sacc_pool, sacc_root );
    3312           0 :        n;
    3313           0 :        n = fd_delegation_pair_t_map_successor( sacc_pool, n )) {
    3314           0 :     fd_vote_accounts_pair_t_mapnode_t query_voter  = {0};
    3315           0 :     fd_pubkey_t *                     voter_pubkey = &n->elem.delegation.voter_pubkey;
    3316           0 :     fd_memcpy( &query_voter.elem.key, voter_pubkey, sizeof(fd_pubkey_t) );
    3317             : 
    3318           0 :     fd_vote_accounts_pair_t_mapnode_t * voter = fd_vote_accounts_pair_t_map_find( vacc_pool, vacc_root, &query_voter );
    3319             : 
    3320           0 :     if( !!voter ) {
    3321           0 :       voter->elem.stake = fd_ulong_sat_add( voter->elem.stake, n->elem.delegation.stake );
    3322           0 :     }
    3323           0 :   }
    3324             : 
    3325           0 :   epoch_bank->next_epoch_stakes = (fd_vote_accounts_t){
    3326           0 :     .vote_accounts_pool = next_pool,
    3327           0 :     .vote_accounts_root = next_root,
    3328           0 :   };
    3329             : 
    3330             :   /* Initializes the stakes cache in the Bank structure. */
    3331           0 :   epoch_bank->stakes = (fd_stakes_t){
    3332           0 :       .stake_delegations_pool = sacc_pool,
    3333           0 :       .stake_delegations_root = sacc_root,
    3334           0 :       .epoch                  = 0UL,
    3335           0 :       .unused                 = 0UL,
    3336           0 :       .vote_accounts = (fd_vote_accounts_t){
    3337           0 :         .vote_accounts_pool = vacc_pool,
    3338           0 :         .vote_accounts_root = vacc_root
    3339           0 :       },
    3340           0 :       .stake_history = {0}
    3341           0 :   };
    3342             : 
    3343           0 :   slot_ctx->slot_bank.capitalization             = capitalization;
    3344           0 :   pool_mem                                       = fd_spad_alloc( runtime_spad,
    3345           0 :                                                                   fd_clock_timestamp_vote_t_map_align(),
    3346           0 :                                                                   fd_clock_timestamp_vote_t_map_footprint( FD_HASH_FOOTPRINT * 400 ) );
    3347           0 :   slot_ctx->slot_bank.timestamp_votes.votes_pool = fd_clock_timestamp_vote_t_map_join( fd_clock_timestamp_vote_t_map_new( pool_mem, 10000 ) ); /* FIXME: remove magic constant */
    3348           0 :   slot_ctx->slot_bank.timestamp_votes.votes_root = NULL;
    3349             : 
    3350           0 : }
    3351             : 
    3352             : static int
    3353             : fd_runtime_process_genesis_block( fd_exec_slot_ctx_t * slot_ctx,
    3354             :                                   fd_capture_ctx_t *   capture_ctx,
    3355             :                                   fd_tpool_t *         tpool,
    3356           0 :                                   fd_spad_t *          runtime_spad ) {
    3357           0 :   ulong hashcnt_per_slot = slot_ctx->epoch_ctx->epoch_bank.hashes_per_tick * slot_ctx->epoch_ctx->epoch_bank.ticks_per_slot;
    3358           0 :   while( hashcnt_per_slot-- ) {
    3359           0 :     fd_sha256_hash( slot_ctx->slot_bank.poh.uc, sizeof(fd_hash_t), slot_ctx->slot_bank.poh.uc );
    3360           0 :   }
    3361             : 
    3362           0 :   slot_ctx->slot_bank.collected_execution_fees = 0UL;
    3363           0 :   slot_ctx->slot_bank.collected_priority_fees  = 0UL;
    3364           0 :   slot_ctx->slot_bank.collected_rent           = 0UL;
    3365           0 :   slot_ctx->signature_cnt                      = 0UL;
    3366           0 :   slot_ctx->txn_count                          = 0UL;
    3367           0 :   slot_ctx->nonvote_txn_count                  = 0UL;
    3368           0 :   slot_ctx->failed_txn_count                   = 0UL;
    3369           0 :   slot_ctx->nonvote_failed_txn_count           = 0UL;
    3370           0 :   slot_ctx->total_compute_units_used           = 0UL;
    3371             : 
    3372           0 :   fd_sysvar_slot_history_update( slot_ctx, runtime_spad );
    3373             : 
    3374           0 :   fd_runtime_freeze( slot_ctx, runtime_spad );
    3375             : 
    3376             :   /* sort and update bank hash */
    3377           0 :   int result = fd_update_hash_bank_tpool( slot_ctx, capture_ctx, &slot_ctx->slot_bank.banks_hash, slot_ctx->signature_cnt, tpool,runtime_spad );
    3378           0 :   if( FD_UNLIKELY( result != FD_EXECUTOR_INSTR_SUCCESS ) ) {
    3379           0 :     FD_LOG_ERR(( "Failed to update bank hash with error=%d", result ));
    3380           0 :   }
    3381             : 
    3382           0 :   FD_TEST( FD_RUNTIME_EXECUTE_SUCCESS==fd_runtime_save_epoch_bank( slot_ctx ) );
    3383             : 
    3384           0 :   FD_TEST( FD_RUNTIME_EXECUTE_SUCCESS==fd_runtime_save_slot_bank( slot_ctx ) );
    3385             : 
    3386           0 :   return FD_RUNTIME_EXECUTE_SUCCESS;
    3387           0 : }
    3388             : 
    3389             : void
    3390             : fd_runtime_read_genesis( fd_exec_slot_ctx_t * slot_ctx,
    3391             :                          char const *         genesis_filepath,
    3392             :                          uchar                is_snapshot,
    3393             :                          fd_capture_ctx_t *   capture_ctx,
    3394             :                          fd_tpool_t *         tpool,
    3395           0 :                          fd_spad_t *          runtime_spad ) {
    3396             : 
    3397           0 :   if( strlen( genesis_filepath ) == 0 ) {
    3398           0 :     return;
    3399           0 :   }
    3400             : 
    3401           0 :   struct stat sbuf;
    3402           0 :   if( FD_UNLIKELY( stat( genesis_filepath, &sbuf) < 0 ) ) {
    3403           0 :     FD_LOG_ERR(( "cannot open %s : %s", genesis_filepath, strerror(errno) ));
    3404           0 :   }
    3405           0 :   int fd = open( genesis_filepath, O_RDONLY );
    3406           0 :   if( FD_UNLIKELY( fd < 0 ) ) {
    3407           0 :     FD_LOG_ERR(("cannot open %s : %s", genesis_filepath, strerror(errno)));
    3408           0 :   }
    3409             : 
    3410           0 :   fd_genesis_solana_t genesis_block = {0};
    3411           0 :   fd_hash_t           genesis_hash;
    3412             : 
    3413           0 :   fd_epoch_bank_t *   epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
    3414             : 
    3415           0 :   FD_SPAD_FRAME_BEGIN( runtime_spad ) {
    3416           0 :     uchar * buf = fd_spad_alloc( runtime_spad, alignof(ulong), (ulong)sbuf.st_size );
    3417           0 :     ssize_t n   = read( fd, buf, (ulong)sbuf.st_size );
    3418           0 :     close( fd );
    3419             : 
    3420             :     /* FIXME: This needs to be patched to support new decoder properly */
    3421           0 :     fd_bincode_decode_ctx_t decode_ctx = {
    3422           0 :       .data    = buf,
    3423           0 :       .dataend = buf + n,
    3424           0 :     };
    3425             : 
    3426           0 :     fd_genesis_solana_decode( &genesis_block, &decode_ctx );
    3427             : 
    3428             :     // The hash is generated from the raw data... don't mess with this..
    3429           0 :     fd_sha256_hash( buf, (ulong)n, genesis_hash.uc );
    3430             : 
    3431           0 :   } FD_SPAD_FRAME_END;
    3432             : 
    3433           0 :   fd_memcpy( epoch_bank->genesis_hash.uc, genesis_hash.uc, sizeof(fd_hash_t) );
    3434           0 :   epoch_bank->cluster_type = genesis_block.cluster_type;
    3435             : 
    3436           0 :   fd_funk_start_write( slot_ctx->acc_mgr->funk );
    3437             : 
    3438           0 :   if( !is_snapshot ) {
    3439           0 :     fd_runtime_init_bank_from_genesis( slot_ctx,
    3440           0 :                                        &genesis_block,
    3441           0 :                                        &genesis_hash,
    3442           0 :                                        runtime_spad );
    3443             : 
    3444           0 :     fd_runtime_init_program( slot_ctx, runtime_spad );
    3445             : 
    3446           0 :     FD_LOG_DEBUG(( "start genesis accounts - count: %lu", genesis_block.accounts_len ));
    3447             : 
    3448           0 :     for( ulong i=0; i<genesis_block.accounts_len; i++ ) {
    3449           0 :       fd_pubkey_account_pair_t * a = &genesis_block.accounts[i];
    3450             : 
    3451           0 :       FD_TXN_ACCOUNT_DECL( rec );
    3452             : 
    3453           0 :       int err = fd_acc_mgr_modify( slot_ctx->acc_mgr,
    3454           0 :                                    slot_ctx->funk_txn,
    3455           0 :                                    &a->key,
    3456           0 :                                    /* do_create */ 1,
    3457           0 :                                    a->account.data_len,
    3458           0 :                                    rec );
    3459             : 
    3460           0 :       if( FD_UNLIKELY( err ) ) {
    3461           0 :         FD_LOG_ERR(( "fd_acc_mgr_modify failed (%d)", err ));
    3462           0 :       }
    3463             : 
    3464           0 :       rec->meta->dlen            = a->account.data_len;
    3465           0 :       rec->meta->info.lamports   = a->account.lamports;
    3466           0 :       rec->meta->info.rent_epoch = a->account.rent_epoch;
    3467           0 :       rec->meta->info.executable = a->account.executable;
    3468           0 :       memcpy( rec->meta->info.owner, a->account.owner.key, sizeof(fd_hash_t));
    3469           0 :       if( a->account.data_len ) {
    3470           0 :         memcpy( rec->data, a->account.data, a->account.data_len );
    3471           0 :       }
    3472           0 :     }
    3473             : 
    3474           0 :     FD_LOG_DEBUG(( "end genesis accounts" ));
    3475             : 
    3476           0 :     FD_LOG_DEBUG(( "native instruction processors - count: %lu", genesis_block.native_instruction_processors_len ));
    3477             : 
    3478           0 :     for( ulong i=0UL; i < genesis_block.native_instruction_processors_len; i++ ) {
    3479           0 :       fd_string_pubkey_pair_t * a = &genesis_block.native_instruction_processors[i];
    3480           0 :       fd_write_builtin_account( slot_ctx, a->pubkey, (const char *) a->string, a->string_len );
    3481           0 :     }
    3482             : 
    3483           0 :     fd_features_restore( slot_ctx, runtime_spad );
    3484             : 
    3485           0 :     slot_ctx->slot_bank.slot = 0UL;
    3486             : 
    3487           0 :     int err = fd_runtime_process_genesis_block( slot_ctx, capture_ctx, tpool, runtime_spad );
    3488           0 :     if( FD_UNLIKELY( err ) ) {
    3489           0 :       FD_LOG_ERR(( "Genesis slot 0 execute failed with error %d", err ));
    3490           0 :     }
    3491           0 :   }
    3492             : 
    3493           0 :   slot_ctx->slot_bank.stake_account_keys.account_keys_root = NULL;
    3494           0 :   uchar * pool_mem = fd_spad_alloc( runtime_spad, fd_account_keys_pair_t_map_align(), fd_account_keys_pair_t_map_footprint( 100000UL ) );
    3495           0 :   slot_ctx->slot_bank.stake_account_keys.account_keys_pool = fd_account_keys_pair_t_map_join( fd_account_keys_pair_t_map_new( pool_mem, 100000UL ) );
    3496             : 
    3497           0 :   slot_ctx->slot_bank.vote_account_keys.account_keys_root   = NULL;
    3498           0 :   pool_mem = fd_spad_alloc( runtime_spad, fd_account_keys_pair_t_map_align(), fd_account_keys_pair_t_map_footprint( 100000UL ) );
    3499           0 :   slot_ctx->slot_bank.vote_account_keys.account_keys_pool   = fd_account_keys_pair_t_map_join( fd_account_keys_pair_t_map_new( pool_mem, 100000UL ) );
    3500             : 
    3501           0 :   fd_funk_end_write( slot_ctx->acc_mgr->funk );
    3502             : 
    3503           0 :   fd_genesis_solana_destroy( &genesis_block );
    3504           0 : }
    3505             : 
    3506             : /******************************************************************************/
    3507             : /* Offline Replay                                                             */
    3508             : /******************************************************************************/
    3509             : 
    3510             : /* As a note, currently offline and live replay of transactions has differences
    3511             :    with regards to how the execution environment is setup. These are helpers
    3512             :    used to emulate this behavior */
    3513             : 
    3514             : struct fd_poh_verification_info {
    3515             :   fd_microblock_info_t const * microblock_info;
    3516             :   fd_hash_t const            * in_poh_hash;
    3517             :   int success;
    3518             : };
    3519             : typedef struct fd_poh_verification_info fd_poh_verification_info_t;
    3520             : 
    3521             : static void
    3522             : fd_runtime_microblock_verify_info_collect( fd_microblock_info_t const * microblock_info,
    3523             :                                            fd_hash_t            const * in_poh_hash,
    3524           0 :                                            fd_poh_verification_info_t * poh_verification_info ) {
    3525           0 :   poh_verification_info->microblock_info = microblock_info;
    3526           0 :   poh_verification_info->in_poh_hash     = in_poh_hash;
    3527           0 :   poh_verification_info->success         = 0;
    3528           0 : }
    3529             : 
    3530             : static void
    3531             : fd_runtime_microblock_batch_verify_info_collect( fd_microblock_batch_info_t const * microblock_batch_info,
    3532             :                                                  fd_hash_t                  const * in_poh_hash,
    3533           0 :                                                  fd_poh_verification_info_t *       poh_verification_info ) {
    3534           0 :   for( ulong i=0UL; i<microblock_batch_info->microblock_cnt; i++ ) {
    3535           0 :     fd_microblock_info_t const * microblock_info = &microblock_batch_info->microblock_infos[i];
    3536           0 :     fd_runtime_microblock_verify_info_collect( microblock_info, in_poh_hash, &poh_verification_info[i] );
    3537           0 :     in_poh_hash = (fd_hash_t const *)&microblock_info->microblock.hdr->hash;
    3538           0 :   }
    3539           0 : }
    3540             : 
    3541             : static void
    3542             : fd_runtime_block_verify_info_collect( fd_runtime_block_info_t const *      block_info,
    3543             :                                       fd_hash_t       const *      in_poh_hash,
    3544             :                                       fd_poh_verification_info_t * poh_verification_info)
    3545           0 : {
    3546           0 :   for( ulong i=0UL; i<block_info->microblock_batch_cnt; i++ ) {
    3547           0 :     fd_microblock_batch_info_t const * microblock_batch_info = &block_info->microblock_batch_infos[i];
    3548             : 
    3549           0 :     fd_runtime_microblock_batch_verify_info_collect( microblock_batch_info, in_poh_hash, poh_verification_info );
    3550           0 :     in_poh_hash            = (fd_hash_t const *)poh_verification_info[microblock_batch_info->microblock_cnt - 1].microblock_info->microblock.hdr->hash;
    3551           0 :     poh_verification_info += microblock_batch_info->microblock_cnt;
    3552           0 :   }
    3553           0 : }
    3554             : 
    3555             : static void
    3556             : fd_runtime_poh_verify_wide_task( void * tpool,
    3557             :                                  ulong  t0 FD_PARAM_UNUSED,
    3558             :                                  ulong  t1 FD_PARAM_UNUSED,
    3559             :                                  void * args FD_PARAM_UNUSED,
    3560             :                                  void * reduce FD_PARAM_UNUSED,
    3561             :                                  ulong  stride FD_PARAM_UNUSED,
    3562             :                                  ulong  l0 FD_PARAM_UNUSED,
    3563             :                                  ulong  l1 FD_PARAM_UNUSED,
    3564             :                                  ulong  m0,
    3565             :                                  ulong  m1 FD_PARAM_UNUSED,
    3566             :                                  ulong  n0 FD_PARAM_UNUSED,
    3567           0 :                                  ulong  n1 FD_PARAM_UNUSED ) {
    3568           0 :   fd_poh_verification_info_t * poh_info = (fd_poh_verification_info_t *)tpool + m0;
    3569             : 
    3570           0 :   fd_hash_t out_poh_hash = *poh_info->in_poh_hash;
    3571           0 :   fd_hash_t init_poh_hash_cpy = *poh_info->in_poh_hash;
    3572             : 
    3573           0 :   fd_microblock_info_t const *microblock_info = poh_info->microblock_info;
    3574           0 :   ulong hash_cnt = microblock_info->microblock.hdr->hash_cnt;
    3575           0 :   ulong txn_cnt = microblock_info->microblock.hdr->txn_cnt;
    3576             : 
    3577           0 :   if( !txn_cnt ) { /* microblock is a tick */
    3578           0 :     fd_poh_append( &out_poh_hash, hash_cnt );
    3579           0 :   } else {
    3580           0 :     if( hash_cnt ) {
    3581           0 :       fd_poh_append(&out_poh_hash, hash_cnt - 1);
    3582           0 :     }
    3583             : 
    3584           0 :     ulong                 leaf_cnt = microblock_info->signature_cnt;
    3585           0 :     uchar *               commit   = fd_alloca_check( FD_WBMTREE32_ALIGN, fd_wbmtree32_footprint(leaf_cnt));
    3586           0 :     fd_wbmtree32_leaf_t * leafs    = fd_alloca_check(alignof(fd_wbmtree32_leaf_t), sizeof(fd_wbmtree32_leaf_t) * leaf_cnt);
    3587           0 :     uchar *               mbuf     = fd_alloca_check( 1UL, leaf_cnt * (sizeof(fd_ed25519_sig_t) + 1) );
    3588             : 
    3589           0 :     fd_wbmtree32_t *      tree = fd_wbmtree32_init(commit, leaf_cnt);
    3590           0 :     fd_wbmtree32_leaf_t * l    = &leafs[0];
    3591             : 
    3592             :     /* Loop across transactions */
    3593           0 :     for( ulong txn_idx=0UL; txn_idx<txn_cnt; txn_idx++ ) {
    3594           0 :       fd_txn_p_t          * txn_p      = &microblock_info->txns[txn_idx];
    3595           0 :       fd_txn_t      const * txn        = (fd_txn_t const *) txn_p->_;
    3596           0 :       fd_rawtxn_b_t const   raw_txn[1] = {{ .raw = txn_p->payload, .txn_sz = (ushort)txn_p->payload_sz } };
    3597             : 
    3598             :       /* Loop across signatures */
    3599           0 :       fd_ed25519_sig_t const * sigs = (fd_ed25519_sig_t const *)((ulong)raw_txn->raw + (ulong)txn->signature_off);
    3600           0 :       for( ulong j=0UL; j<txn->signature_cnt; j++ ) {
    3601           0 :         l->data     = (uchar *)&sigs[j];
    3602           0 :         l->data_len = sizeof(fd_ed25519_sig_t);
    3603           0 :         l++;
    3604           0 :       }
    3605           0 :     }
    3606             : 
    3607           0 :     fd_wbmtree32_append( tree, leafs, leaf_cnt, mbuf );
    3608           0 :     uchar * root = fd_wbmtree32_fini( tree );
    3609           0 :     fd_poh_mixin( &out_poh_hash, root );
    3610           0 :   }
    3611             : 
    3612           0 :   if( FD_UNLIKELY( memcmp(microblock_info->microblock.hdr->hash, out_poh_hash.hash, sizeof(fd_hash_t)) ) ) {
    3613           0 :     FD_LOG_WARNING(( "poh mismatch (bank: %s, entry: %s. INIT: %s)",
    3614           0 :         FD_BASE58_ENC_32_ALLOCA( out_poh_hash.hash ),
    3615           0 :         FD_BASE58_ENC_32_ALLOCA( microblock_info->microblock.hdr->hash ),
    3616           0 :         FD_BASE58_ENC_32_ALLOCA(&init_poh_hash_cpy) ));
    3617           0 :     poh_info->success = -1;
    3618           0 :   }
    3619           0 : }
    3620             : 
    3621             : static int
    3622             : fd_runtime_poh_verify_tpool( fd_poh_verification_info_t * poh_verification_info,
    3623             :                              ulong                        poh_verification_info_cnt,
    3624           0 :                              fd_tpool_t *                 tpool ) {
    3625           0 :   fd_tpool_exec_all_rrobin( tpool,
    3626           0 :                             0,
    3627           0 :                             fd_tpool_worker_cnt( tpool ),
    3628           0 :                             fd_runtime_poh_verify_wide_task,
    3629           0 :                             poh_verification_info,
    3630           0 :                             NULL,
    3631           0 :                             NULL,
    3632           0 :                             1,
    3633           0 :                             0,
    3634           0 :                             poh_verification_info_cnt );
    3635             : 
    3636           0 :   for( ulong i=0UL; i<poh_verification_info_cnt; i++ ) {
    3637           0 :     if( poh_verification_info[i].success ) {
    3638           0 :       return -1;
    3639           0 :     }
    3640           0 :   }
    3641             : 
    3642           0 :   return 0;
    3643           0 : }
    3644             : 
    3645             : static int
    3646             : fd_runtime_block_verify_tpool( fd_exec_slot_ctx_t *    slot_ctx,
    3647             :                                fd_runtime_block_info_t const * block_info,
    3648             :                                fd_hash_t       const * in_poh_hash,
    3649             :                                fd_hash_t *             out_poh_hash,
    3650             :                                fd_tpool_t *            tpool,
    3651           0 :                                fd_spad_t *             runtime_spad ) {
    3652             : 
    3653           0 :   FD_SPAD_FRAME_BEGIN( runtime_spad ) {
    3654             : 
    3655           0 :   long block_verify_time = -fd_log_wallclock();
    3656             : 
    3657           0 :   fd_hash_t                    tmp_in_poh_hash           = *in_poh_hash;
    3658           0 :   ulong                        poh_verification_info_cnt = block_info->microblock_cnt;
    3659           0 :   fd_poh_verification_info_t * poh_verification_info     = fd_spad_alloc( runtime_spad,
    3660           0 :                                                                           alignof(fd_poh_verification_info_t),
    3661           0 :                                                                           poh_verification_info_cnt * sizeof(fd_poh_verification_info_t) );
    3662           0 :   fd_runtime_block_verify_info_collect( block_info, &tmp_in_poh_hash, poh_verification_info );
    3663             : 
    3664           0 :   uchar * block_data = fd_spad_alloc( runtime_spad, 128UL, FD_SHRED_DATA_PAYLOAD_MAX_PER_SLOT );
    3665           0 :   ulong   tick_res   = fd_runtime_block_verify_ticks( slot_ctx->blockstore,
    3666           0 :                                                       slot_ctx->slot_bank.slot,
    3667           0 :                                                       block_data,
    3668           0 :                                                       FD_SHRED_DATA_PAYLOAD_MAX_PER_SLOT,
    3669           0 :                                                       slot_ctx->slot_bank.tick_height,
    3670           0 :                                                       slot_ctx->slot_bank.max_tick_height,
    3671           0 :                                                       slot_ctx->epoch_ctx->epoch_bank.hashes_per_tick
    3672           0 :   );
    3673           0 :   if( FD_UNLIKELY( tick_res != FD_BLOCK_OK ) ) {
    3674           0 :     FD_LOG_WARNING(( "failed to verify ticks res %lu slot %lu", tick_res, slot_ctx->slot_bank.slot ));
    3675           0 :     return FD_RUNTIME_EXECUTE_GENERIC_ERR;
    3676           0 :   }
    3677             : 
    3678             :   /* poh_verification_info is now in order information of all the microblocks */
    3679             : 
    3680           0 :   int result = fd_runtime_poh_verify_tpool( poh_verification_info, poh_verification_info_cnt, tpool );
    3681           0 :   fd_memcpy( out_poh_hash->hash, poh_verification_info[poh_verification_info_cnt - 1].microblock_info->microblock.hdr->hash, sizeof(fd_hash_t) );
    3682             : 
    3683           0 :   block_verify_time          += fd_log_wallclock();
    3684           0 :   double block_verify_time_ms = (double)block_verify_time * 1e-6;
    3685             : 
    3686           0 :   FD_LOG_INFO(( "verified block successfully - elapsed: %6.6f ms", block_verify_time_ms ));
    3687             : 
    3688           0 :   return result;
    3689             : 
    3690           0 :   } FD_SPAD_FRAME_END;
    3691           0 : }
    3692             : 
    3693             : static int
    3694             : fd_runtime_publish_old_txns( fd_exec_slot_ctx_t * slot_ctx,
    3695             :                              fd_capture_ctx_t *   capture_ctx,
    3696             :                              fd_tpool_t *         tpool,
    3697           0 :                              fd_spad_t *          runtime_spad ) {
    3698             :   /* Publish any transaction older than 31 slots */
    3699           0 :   fd_funk_t *       funk       = slot_ctx->acc_mgr->funk;
    3700           0 :   fd_funk_txn_t *   txnmap     = fd_funk_txn_map( funk, fd_funk_wksp( funk ) );
    3701           0 :   fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
    3702             : 
    3703           0 :   if( capture_ctx != NULL ) {
    3704           0 :     fd_funk_start_write( funk );
    3705           0 :     fd_runtime_checkpt( capture_ctx, slot_ctx, slot_ctx->slot_bank.slot );
    3706           0 :     fd_funk_end_write( funk );
    3707           0 :   }
    3708             : 
    3709           0 :   uint depth = 0;
    3710           0 :   for( fd_funk_txn_t * txn = slot_ctx->funk_txn; txn; txn = fd_funk_txn_parent(txn, txnmap) ) {
    3711           0 :     if( ++depth == (FD_RUNTIME_NUM_ROOT_BLOCKS - 1 ) ) {
    3712           0 :       FD_LOG_DEBUG(("publishing %s (slot %lu)", FD_BASE58_ENC_32_ALLOCA( &txn->xid ), txn->xid.ul[0]));
    3713             : 
    3714           0 :       if( slot_ctx->status_cache && !fd_txncache_get_is_constipated( slot_ctx->status_cache ) ) {
    3715           0 :         fd_txncache_register_root_slot( slot_ctx->status_cache, txn->xid.ul[0] );
    3716           0 :       } else if( slot_ctx->status_cache ) {
    3717           0 :         fd_txncache_register_constipated_slot( slot_ctx->status_cache, txn->xid.ul[0] );
    3718           0 :       }
    3719             : 
    3720           0 :       fd_funk_start_write( funk );
    3721           0 :       if( slot_ctx->epoch_ctx->constipate_root ) {
    3722           0 :         fd_funk_txn_t * parent = fd_funk_txn_parent( txn, txnmap );
    3723           0 :         if( parent != NULL ) {
    3724           0 :           slot_ctx->root_slot = txn->xid.ul[0];
    3725             : 
    3726           0 :           if( FD_UNLIKELY( fd_funk_txn_publish_into_parent( funk, txn, 1) != FD_FUNK_SUCCESS ) ) {
    3727           0 :             FD_LOG_ERR(( "Unable to publish into the parent transaction" ));
    3728           0 :           }
    3729           0 :         }
    3730           0 :       } else {
    3731           0 :         slot_ctx->root_slot = txn->xid.ul[0];
    3732             :         /* TODO: The epoch boundary check is not correct due to skipped slots. */
    3733           0 :         if( (!(slot_ctx->root_slot % slot_ctx->snapshot_freq) || (
    3734           0 :              !(slot_ctx->root_slot % slot_ctx->incremental_freq) && slot_ctx->last_snapshot_slot)) &&
    3735           0 :              !fd_runtime_is_epoch_boundary( epoch_bank, slot_ctx->root_slot, slot_ctx->root_slot - 1UL )) {
    3736             : 
    3737           0 :           slot_ctx->last_snapshot_slot         = slot_ctx->root_slot;
    3738           0 :           slot_ctx->epoch_ctx->constipate_root = 1;
    3739           0 :           fd_txncache_set_is_constipated( slot_ctx->status_cache, 1 );
    3740           0 :         }
    3741             : 
    3742           0 :         if( FD_UNLIKELY( !fd_funk_txn_publish( funk, txn, 1 ) ) ) {
    3743           0 :           FD_LOG_ERR(( "No transactions were published" ));
    3744           0 :         }
    3745           0 :       }
    3746             : 
    3747           0 :       if( txn->xid.ul[0] >= epoch_bank->eah_start_slot ) {
    3748           0 :         if( !FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, accounts_lt_hash ) ) {
    3749           0 :           fd_accounts_hash( slot_ctx->acc_mgr->funk, &slot_ctx->slot_bank, tpool, &slot_ctx->slot_bank.epoch_account_hash, runtime_spad, 0, &slot_ctx->epoch_ctx->features );
    3750           0 :         }
    3751           0 :         epoch_bank->eah_start_slot = ULONG_MAX;
    3752           0 :       }
    3753             : 
    3754           0 :       fd_funk_end_write( funk );
    3755             : 
    3756           0 :       break;
    3757           0 :     }
    3758           0 :   }
    3759             : 
    3760           0 :   return 0;
    3761           0 : }
    3762             : 
    3763             : int
    3764             : fd_runtime_block_execute_tpool( fd_exec_slot_ctx_t *    slot_ctx,
    3765             :                                 fd_capture_ctx_t *      capture_ctx,
    3766             :                                 fd_runtime_block_info_t const * block_info,
    3767             :                                 fd_tpool_t *            tpool,
    3768             :                                 fd_spad_t * *           exec_spads,
    3769             :                                 ulong                   exec_spad_cnt,
    3770           0 :                                 fd_spad_t *             runtime_spad ) {
    3771             : 
    3772           0 :   if ( capture_ctx != NULL && capture_ctx->capture ) {
    3773           0 :     fd_solcap_writer_set_slot( capture_ctx->capture, slot_ctx->slot_bank.slot );
    3774           0 :   }
    3775             : 
    3776           0 :   long block_execute_time = -fd_log_wallclock();
    3777             : 
    3778           0 :   int res = fd_runtime_block_execute_prepare( slot_ctx, runtime_spad );
    3779           0 :   if( res != FD_RUNTIME_EXECUTE_SUCCESS ) {
    3780           0 :     return res;
    3781           0 :   }
    3782             : 
    3783           0 :   ulong        txn_cnt  = block_info->txn_cnt;
    3784           0 :   fd_txn_p_t * txn_ptrs = fd_spad_alloc( runtime_spad, alignof(fd_txn_p_t), txn_cnt * sizeof(fd_txn_p_t) );
    3785             : 
    3786           0 :   fd_runtime_block_collect_txns( block_info, txn_ptrs );
    3787             : 
    3788             :   /* Initialize the cost tracker when the feature is active */
    3789           0 :   fd_cost_tracker_t * cost_tracker = fd_spad_alloc( runtime_spad, FD_COST_TRACKER_ALIGN, FD_COST_TRACKER_FOOTPRINT );
    3790           0 :   if( FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, apply_cost_tracker_during_replay ) ) {
    3791           0 :     fd_cost_tracker_init( cost_tracker, slot_ctx, runtime_spad );
    3792           0 :   }
    3793             : 
    3794             :   /* We want to emulate microblock-by-microblock execution */
    3795           0 :   ulong to_exec_idx = 0UL;
    3796           0 :   for( ulong i=0UL; i<block_info->microblock_batch_cnt; i++ ) {
    3797           0 :     for( ulong j=0UL; j<block_info->microblock_batch_infos[i].microblock_cnt; j++ ) {
    3798           0 :       ulong txn_cnt = block_info->microblock_batch_infos[i].microblock_infos[j].microblock.hdr->txn_cnt;
    3799           0 :       fd_txn_p_t * mblock_txn_ptrs = &txn_ptrs[ to_exec_idx ];
    3800           0 :       ulong        mblock_txn_cnt  = txn_cnt;
    3801           0 :       to_exec_idx += txn_cnt;
    3802             : 
    3803           0 :       if( !mblock_txn_cnt ) continue;
    3804             : 
    3805           0 :       res = fd_runtime_process_txns_in_microblock_stream( slot_ctx,
    3806           0 :                                                           capture_ctx,
    3807           0 :                                                           mblock_txn_ptrs,
    3808           0 :                                                           mblock_txn_cnt,
    3809           0 :                                                           tpool,
    3810           0 :                                                           exec_spads,
    3811           0 :                                                           exec_spad_cnt,
    3812           0 :                                                           runtime_spad,
    3813           0 :                                                           cost_tracker );
    3814           0 :       if( FD_UNLIKELY( res!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
    3815           0 :         return res;
    3816           0 :       }
    3817           0 :     }
    3818           0 :   }
    3819             : 
    3820           0 :   long block_finalize_time = -fd_log_wallclock();
    3821           0 :   res = fd_runtime_block_execute_finalize_tpool( slot_ctx, capture_ctx, block_info, tpool, runtime_spad );
    3822           0 :   if( FD_UNLIKELY( res!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
    3823           0 :     return res;
    3824           0 :   }
    3825             : 
    3826           0 :   slot_ctx->slot_bank.transaction_count += txn_cnt;
    3827             : 
    3828           0 :   block_finalize_time += fd_log_wallclock();
    3829           0 :   double block_finalize_time_ms = (double)block_finalize_time * 1e-6;
    3830           0 :   FD_LOG_INFO(( "finalized block successfully - slot: %lu, elapsed: %6.6f ms", slot_ctx->slot_bank.slot, block_finalize_time_ms ));
    3831             : 
    3832           0 :   block_execute_time += fd_log_wallclock();
    3833           0 :   double block_execute_time_ms = (double)block_execute_time * 1e-6;
    3834             : 
    3835           0 :   FD_LOG_INFO(( "executed block successfully - slot: %lu, elapsed: %6.6f ms", slot_ctx->slot_bank.slot, block_execute_time_ms ));
    3836             : 
    3837           0 :   return FD_RUNTIME_EXECUTE_SUCCESS;
    3838           0 : }
    3839             : 
    3840             : void
    3841             : fd_runtime_block_pre_execute_process_new_epoch( fd_exec_slot_ctx_t * slot_ctx,
    3842             :                                                 fd_tpool_t *         tpool,
    3843             :                                                 fd_spad_t * *        exec_spads,
    3844             :                                                 ulong                exec_spad_cnt,
    3845           0 :                                                 fd_spad_t *          runtime_spad ) {
    3846             : 
    3847             :   /* Update block height. */
    3848           0 :   slot_ctx->slot_bank.block_height += 1UL;
    3849             : 
    3850           0 :   if( slot_ctx->slot_bank.slot != 0UL ) {
    3851           0 :     ulong             slot_idx;
    3852           0 :     fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
    3853           0 :     ulong             prev_epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, slot_ctx->slot_bank.prev_slot, &slot_idx );
    3854           0 :     ulong             new_epoch  = fd_slot_to_epoch( &epoch_bank->epoch_schedule, slot_ctx->slot_bank.slot, &slot_idx );
    3855           0 :     if( FD_UNLIKELY( slot_idx==1UL && new_epoch==0UL ) ) {
    3856             :       /* The block after genesis has a height of 1. */
    3857           0 :       slot_ctx->slot_bank.block_height = 1UL;
    3858           0 :     }
    3859             : 
    3860           0 :     if( FD_UNLIKELY( prev_epoch<new_epoch || !slot_idx ) ) {
    3861           0 :       FD_LOG_DEBUG(( "Epoch boundary" ));
    3862             :       /* Epoch boundary! */
    3863           0 :       fd_funk_start_write( slot_ctx->acc_mgr->funk );
    3864           0 :       fd_runtime_process_new_epoch( slot_ctx,
    3865           0 :                                     new_epoch - 1UL,
    3866           0 :                                     tpool,
    3867           0 :                                     exec_spads,
    3868           0 :                                     exec_spad_cnt,
    3869           0 :                                     runtime_spad );
    3870           0 :       fd_funk_end_write( slot_ctx->acc_mgr->funk );
    3871           0 :     }
    3872           0 :   }
    3873             : 
    3874           0 :   if( slot_ctx->slot_bank.slot != 0UL && (
    3875           0 :       FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, enable_partitioned_epoch_reward ) ||
    3876           0 :       FD_FEATURE_ACTIVE( slot_ctx->slot_bank.slot, slot_ctx->epoch_ctx->features, partitioned_epoch_rewards_superfeature ) ) ) {
    3877           0 :     fd_funk_start_write( slot_ctx->acc_mgr->funk );
    3878           0 :     fd_distribute_partitioned_epoch_rewards( slot_ctx,
    3879           0 :                                              tpool,
    3880           0 :                                              exec_spads,
    3881           0 :                                              exec_spad_cnt,
    3882           0 :                                              runtime_spad );
    3883           0 :     fd_funk_end_write( slot_ctx->acc_mgr->funk );
    3884           0 :   }
    3885           0 : }
    3886             : 
    3887             : int
    3888             : fd_runtime_block_eval_tpool( fd_exec_slot_ctx_t * slot_ctx,
    3889             :                              fd_block_t *         block,
    3890             :                              fd_capture_ctx_t *   capture_ctx,
    3891             :                              fd_tpool_t *         tpool,
    3892             :                              ulong                scheduler,
    3893             :                              ulong *              txn_cnt,
    3894             :                              fd_spad_t * *        exec_spads,
    3895             :                              ulong                exec_spad_cnt,
    3896           0 :                              fd_spad_t *          runtime_spad ) {
    3897             : 
    3898             :   /* offline replay */
    3899           0 :   (void)scheduler;
    3900             : 
    3901           0 :   int err = fd_runtime_publish_old_txns( slot_ctx, capture_ctx, tpool, runtime_spad );
    3902           0 :   if( err != 0 ) {
    3903           0 :     return err;
    3904           0 :   }
    3905             : 
    3906           0 :   fd_funk_t * funk = slot_ctx->acc_mgr->funk;
    3907             : 
    3908           0 :   ulong slot = slot_ctx->slot_bank.slot;
    3909             : 
    3910           0 :   long block_eval_time = -fd_log_wallclock();
    3911           0 :   fd_runtime_block_info_t block_info;
    3912           0 :   int ret = FD_RUNTIME_EXECUTE_SUCCESS;
    3913           0 :   do {
    3914             : 
    3915             :     /* Start a new funk txn. */
    3916             : 
    3917           0 :     fd_funk_txn_xid_t xid = { .ul = { slot_ctx->slot_bank.slot, slot_ctx->slot_bank.slot } };
    3918           0 :     fd_funk_start_write( funk );
    3919           0 :     slot_ctx->funk_txn = fd_funk_txn_prepare( funk, slot_ctx->funk_txn, &xid, 1 );
    3920           0 :     fd_funk_end_write( funk );
    3921             : 
    3922             :     /* Capturing block-agnostic state in preparation for the epoch boundary */
    3923           0 :     uchar dump_block = capture_ctx && slot_ctx->slot_bank.slot >= capture_ctx->dump_proto_start_slot && capture_ctx->dump_block_to_pb;
    3924           0 :     fd_exec_test_block_context_t * block_ctx = NULL;
    3925           0 :     if( FD_UNLIKELY( dump_block ) ) {
    3926             :       /* TODO: This probably should get allocated from a separate spad for the capture ctx */
    3927           0 :       block_ctx = fd_spad_alloc( runtime_spad, alignof(fd_exec_test_block_context_t), sizeof(fd_exec_test_block_context_t) );
    3928           0 :       fd_memset( block_ctx, 0, sizeof(fd_exec_test_block_context_t) );
    3929           0 :       fd_dump_block_to_protobuf( slot_ctx, capture_ctx, runtime_spad, block_ctx );
    3930           0 :     }
    3931             : 
    3932           0 :     fd_runtime_block_pre_execute_process_new_epoch( slot_ctx,
    3933           0 :                                                     tpool,
    3934           0 :                                                     exec_spads,
    3935           0 :                                                     exec_spad_cnt,
    3936           0 :                                                     runtime_spad );
    3937             : 
    3938             :     /* All runtime allocations here are scoped to the end of a block. */
    3939           0 :     FD_SPAD_FRAME_BEGIN( runtime_spad ) {
    3940             : 
    3941           0 :     if( FD_UNLIKELY( (ret = fd_runtime_block_prepare( slot_ctx->blockstore,
    3942           0 :                                                       block,
    3943           0 :                                                       slot,
    3944           0 :                                                       runtime_spad,
    3945           0 :                                                       &block_info )) != FD_RUNTIME_EXECUTE_SUCCESS ) ) {
    3946           0 :       break;
    3947           0 :     }
    3948           0 :     *txn_cnt = block_info.txn_cnt;
    3949             : 
    3950           0 :     if( FD_UNLIKELY( (ret = fd_runtime_block_verify_tpool( slot_ctx, &block_info, &slot_ctx->slot_bank.poh, &slot_ctx->slot_bank.poh, tpool, runtime_spad )) != FD_RUNTIME_EXECUTE_SUCCESS ) ) {
    3951           0 :       break;
    3952           0 :     }
    3953             : 
    3954             :     /* Dump the remainder of the block after preparation, POH verification, etc */
    3955           0 :     if( dump_block ) {
    3956           0 :       fd_dump_block_to_protobuf_tx_only( &block_info, slot_ctx, capture_ctx, runtime_spad, block_ctx );
    3957           0 :     }
    3958             : 
    3959           0 :     if( FD_UNLIKELY( (ret = fd_runtime_block_execute_tpool( slot_ctx, capture_ctx, &block_info, tpool, exec_spads, exec_spad_cnt, runtime_spad )) != FD_RUNTIME_EXECUTE_SUCCESS ) ) {
    3960           0 :       break;
    3961           0 :     }
    3962             : 
    3963           0 :     } FD_SPAD_FRAME_END;
    3964             : 
    3965           0 :   } while( 0 );
    3966             : 
    3967             :   // FIXME: better way of using starting slot
    3968           0 :   if( FD_UNLIKELY( FD_RUNTIME_EXECUTE_SUCCESS != ret ) ) {
    3969           0 :     FD_LOG_WARNING(( "execution failure, code %d", ret ));
    3970             :     /* Skip over slot next time */
    3971           0 :     slot_ctx->slot_bank.slot = slot+1UL;
    3972           0 :     return ret;
    3973           0 :   }
    3974             : 
    3975           0 :   block_eval_time          += fd_log_wallclock();
    3976           0 :   double block_eval_time_ms = (double)block_eval_time * 1e-6;
    3977           0 :   double tps                = (double) block_info.txn_cnt / ((double)block_eval_time * 1e-9);
    3978           0 :   FD_LOG_INFO(( "evaluated block successfully - slot: %lu, elapsed: %6.6f ms, signatures: %lu, txns: %lu, tps: %6.6f, bank_hash: %s, leader: %s",
    3979           0 :                 slot_ctx->slot_bank.slot,
    3980           0 :                 block_eval_time_ms,
    3981           0 :                 block_info.signature_cnt,
    3982           0 :                 block_info.txn_cnt,
    3983           0 :                 tps,
    3984           0 :                 FD_BASE58_ENC_32_ALLOCA( slot_ctx->slot_bank.banks_hash.hash ),
    3985           0 :                 FD_BASE58_ENC_32_ALLOCA( fd_epoch_leaders_get( fd_exec_epoch_ctx_leaders( slot_ctx->epoch_ctx ), slot_ctx->slot_bank.slot ) ) ));
    3986             : 
    3987           0 :   slot_ctx->slot_bank.transaction_count += block_info.txn_cnt;
    3988             : 
    3989           0 :   fd_funk_start_write( slot_ctx->acc_mgr->funk );
    3990           0 :   fd_runtime_save_slot_bank( slot_ctx );
    3991           0 :   fd_funk_end_write( slot_ctx->acc_mgr->funk );
    3992             : 
    3993           0 :   slot_ctx->slot_bank.prev_slot = slot;
    3994             :   // FIXME: this shouldn't be doing this, it doesn't work with forking. punting changing it though
    3995           0 :   slot_ctx->slot_bank.slot = slot+1UL;
    3996             : 
    3997           0 :   return 0;
    3998           0 : }
    3999             : 
    4000             : /******************************************************************************/
    4001             : /* Debugging Tools                                                            */
    4002             : /******************************************************************************/
    4003             : 
    4004             : void
    4005             : fd_runtime_checkpt( fd_capture_ctx_t *   capture_ctx,
    4006             :                     fd_exec_slot_ctx_t * slot_ctx,
    4007           0 :                     ulong                slot ) {
    4008           0 :   int is_checkpt_freq = capture_ctx != NULL && slot % capture_ctx->checkpt_freq == 0;
    4009           0 :   int is_abort_slot   = slot == ULONG_MAX;
    4010           0 :   if( !is_checkpt_freq && !is_abort_slot ) {
    4011           0 :     return;
    4012           0 :   }
    4013             : 
    4014           0 :   if( capture_ctx->checkpt_path != NULL ) {
    4015           0 :     if( !is_abort_slot ) {
    4016           0 :       FD_LOG_NOTICE(( "checkpointing at slot=%lu to file=%s", slot, capture_ctx->checkpt_path ));
    4017           0 :       fd_funk_end_write( slot_ctx->acc_mgr->funk );
    4018           0 :     } else {
    4019           0 :       FD_LOG_NOTICE(( "checkpointing after mismatch to file=%s", capture_ctx->checkpt_path ));
    4020           0 :     }
    4021             : 
    4022           0 :     unlink( capture_ctx->checkpt_path );
    4023           0 :     int err = fd_wksp_checkpt( fd_funk_wksp( slot_ctx->acc_mgr->funk ), capture_ctx->checkpt_path, 0666, 0, NULL );
    4024           0 :     if ( err ) {
    4025           0 :       FD_LOG_ERR(( "backup failed: error %d", err ));
    4026           0 :     }
    4027             : 
    4028           0 :     if( !is_abort_slot ) {
    4029           0 :       fd_funk_start_write( slot_ctx->acc_mgr->funk );
    4030           0 :     }
    4031           0 :   }
    4032             : 
    4033           0 : }
    4034             : 
    4035             : // TODO: add tracking account_state hashes so that we can verify our
    4036             : // banks hash... this has interesting threading implications since we
    4037             : // could execute the cryptography in another thread for tracking this
    4038             : // but we don't actually have anything to compare it to until we hit
    4039             : // another snapshot...  Probably we should just store the results into
    4040             : // the slot_ctx state (a slot/hash map)?
    4041             : //
    4042             : // What slots exactly do cache'd account_updates go into?  how are
    4043             : // they hashed (which slot?)?
    4044             : 
    4045             : ulong
    4046           0 : fd_runtime_public_footprint ( void ) {
    4047           0 :   return sizeof(fd_runtime_public_t);
    4048           0 : }
    4049             : 
    4050             : fd_runtime_public_t *
    4051             : fd_runtime_public_join ( void * ptr )
    4052           0 : {
    4053           0 :   return (fd_runtime_public_t *) ptr;
    4054           0 : }
    4055             : 
    4056             : void *
    4057           0 : fd_runtime_public_new ( void * ptr )  {
    4058           0 :   fd_memset(ptr, 0, sizeof(fd_runtime_public_t));
    4059           0 :   return ptr;
    4060           0 : }

Generated by: LCOV version 1.14