LCOV - code coverage report
Current view: top level - discof/replay - fd_replay_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 3369 0.0 %
Date: 2026-11-11 12:40:43 Functions: 0 180 0.0 %

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : #include <linux/futex.h> /* FUTEX_WAKE */
       3             : #include <sys/syscall.h> /* SYS_futex */
       4             : #include <unistd.h> /* syscall(2) */
       5             : 
       6             : #include "fd_replay_tile.h"
       7             : #include "fd_replay_tile_private.h"
       8             : #include "../../ballet/bls/fd_bls12_381.h"
       9             : #include "fd_sched.h"
      10             : #include "fd_execrp.h"
      11             : #include "generated/fd_replay_tile_seccomp.h"
      12             : 
      13             : #include "../admin/fd_adminctl.h"
      14             : #include <errno.h>
      15             : #include <fcntl.h>
      16             : #include "../genesis/fd_genesi_tile.h"
      17             : #include "../poh/fd_poh.h"
      18             : #include "../votor/fd_votor_tile.h"
      19             : #include "../resolv/fd_resolv_tile.h"
      20             : #include "../restore/utils/fd_ssload.h"
      21             : #include "../restore/utils/fd_wfs.h"
      22             : #include "../../flamenco/gossip/fd_gossip_message.h"
      23             : 
      24             : #include "../../disco/tiles.h"
      25             : #include "../../disco/fd_txn_m.h"
      26             : #include "../../disco/shred/fd_fec_set.h"
      27             : #include "../../disco/shred/fd_shred_tile.h"
      28             : #include "../../disco/pack/fd_pack.h"
      29             : #include "../../disco/pack/fd_pack_cost.h"
      30             : #include "../backup/fd_snapmk_tile.h"
      31             : #include "../../disco/keyguard/fd_keyload.h"
      32             : #include "../../disco/genesis/fd_genesis_cluster.h"
      33             : #include "../../discof/genesis/genesis_hash.h"
      34             : #include "../../util/pod/fd_pod.h"
      35             : #include "../../util/fd_boolau.h"
      36             : #include "../../flamenco/rewards/fd_rewards.h"
      37             : #include "../../flamenco/leaders/fd_multi_epoch_leaders.h"
      38             : #include "../../flamenco/stakes/fd_stakes.h"
      39             : #include "../../flamenco/progcache/fd_progcache_admin.h"
      40             : #include "../../flamenco/rewards/fd_rewards.h"
      41             : #include "../../disco/metrics/fd_metrics.h"
      42             : #include "../../flamenco/events/fd_event_runtime.h"
      43             : #include "../../disco/events/generated/fd_event_gen.h"
      44             : #include "../../disco/repair/fd_repair_tile.h"
      45             : #include "../../flamenco/runtime/fd_runtime.h"
      46             : #include "../../flamenco/runtime/fd_runtime_stack_tmpl.h"
      47             : 
      48             : #include "../../flamenco/runtime/sysvar/fd_sysvar_cache.h"
      49             : #include "../../flamenco/runtime/sysvar/fd_sysvar_stake_history.h"
      50             : #include "../../flamenco/runtime/sysvar/fd_sysvar_clock.h"
      51             : #include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h"
      52             : #include "../../flamenco/runtime/sysvar/fd_sysvar_rent.h"
      53             : #include "../../flamenco/runtime/program/fd_precompiles.h"
      54             : #include "../../flamenco/runtime/program/vote/fd_vote_state_versioned.h"
      55             : #include "../../flamenco/runtime/program/vote/fd_vote_codec_tmpl.h"
      56             : #include "../../flamenco/runtime/tests/fd_dump_pb.h"
      57             : #include "../../disco/events/fd_event_report.h"
      58             : #include "../../disco/metrics/fd_metrics_sample.h"
      59             : 
      60             : /* Replay concepts:
      61             : 
      62             :    - Blocks are aggregations of entries aka. microblocks which are
      63             :      groupings of txns and are constructed by the block producer (see
      64             :      fd_pack).
      65             : 
      66             :    - Entries are grouped into entry batches by the block producer (see
      67             :      fd_pack / fd_shredder).
      68             : 
      69             :    - Entry batches are divided into chunks known as shreds by the block
      70             :      producer (see fd_shredder).
      71             : 
      72             :    - Shreds are grouped into forward-error-correction sets (FEC sets) by
      73             :      the block producer (see fd_shredder).
      74             : 
      75             :    - Shreds are transmitted to the rest of the cluster via the Turbine
      76             :      protocol (see fd_shredder / fd_shred).
      77             : 
      78             :    - Once enough shreds within a FEC set are received to recover the
      79             :      entirety of the shred data encoded by that FEC set, the receiver
      80             :      can "complete" the FEC set (see fd_fec_resolver).
      81             : 
      82             :    - If shreds in the FEC set are missing such that it can't complete,
      83             :      the receiver can use the Repair protocol to request missing shreds
      84             :      in FEC set (see fd_repair).
      85             : 
      86             :   -  The current Repair protocol does not support requesting coding
      87             :      shreds.  As a result, some FEC sets might be actually complete
      88             :      (contain all data shreds).  Repair currently hacks around this by
      89             :      forcing completion but the long-term solution is to add support for
      90             :      fec_repairing coding shreds via Repair.
      91             : 
      92             :   - FEC sets are delivered in partial-order to the Replay tile by the
      93             :     Repair tile.  Currently Replay only supports replaying entry batches
      94             :     so FEC sets need to reassembled into an entry batch before they can
      95             :     be replayed.  The new Dispatcher will change this by taking a FEC
      96             :     set as input instead. */
      97             : 
      98             : /* Agave defines TIME_TO_COMPLETE_BLOCK_BROADCAST to adjust their slot
      99             :    end.  Our own telemetry data of finishing pack => shredding last
     100             :    batch suggests roughly the same delay, so we mirror the constant. */
     101             : 
     102           0 : #define FD_TIME_TO_COMPLETE_BROADCAST_NS (6000000L)
     103             : 
     104           0 : #define IN_KIND_SNAP       ( 0)
     105           0 : #define IN_KIND_GENESIS    ( 1)
     106           0 : #define IN_KIND_IPECHO     ( 2)
     107           0 : #define IN_KIND_RESOLV     ( 4)
     108           0 : #define IN_KIND_MOTOR      ( 5)
     109           0 : #define IN_KIND_EXECRP     ( 6)
     110           0 : #define IN_KIND_REPAIR     ( 7)
     111           0 : #define IN_KIND_TXSEND     ( 8)
     112           0 : #define IN_KIND_RPC        ( 9)
     113           0 : #define IN_KIND_GOSSIP_OUT (10)
     114           0 : #define IN_KIND_SNAPMK     (11)
     115           0 : #define IN_KIND_ADMIN      (12)
     116           0 : #define IN_KIND_VOTOR      (13)
     117             : 
     118             : #define DEBUG_LOGGING 0
     119             : 
     120             : /* The first bank that the replay tile produces either for genesis
     121             :    or the snapshot boot will always be at bank index 0. */
     122           0 : #define FD_REPLAY_BOOT_BANK_IDX (0UL)
     123             : 
     124             : /* wfs_resolve_leader_gate_auto resolves to true when
     125             :    wait_for_vote_to_start_leader is configured as auto, otherwise it
     126             :    passes the config value through.  True keeps the leader gate armed,
     127             :    which is correct for a non-WFS boot and for NOOP. */
     128             : 
     129             : static inline int
     130           0 : wfs_resolve_leader_gate_auto( int wait_for_vote_to_start_leader ) {
     131           0 :   return fd_int_if( wait_for_vote_to_start_leader==FD_BOOLAU_AUTO,
     132           0 :                     FD_BOOLAU_TRUE,
     133           0 :                     wait_for_vote_to_start_leader );
     134           0 : }
     135             : 
     136             : static inline ulong
     137           0 : fd_block_id_ele_get_idx( fd_block_id_ele_t * ele_arr, fd_block_id_ele_t * ele ) {
     138           0 :   return (ulong)(ele - ele_arr);
     139           0 : }
     140             : 
     141             : FD_FN_CONST static inline ulong
     142           0 : scratch_align( void ) {
     143           0 :   return 128UL;
     144           0 : }
     145             : FD_FN_PURE static inline ulong
     146           0 : scratch_footprint( fd_topo_tile_t const * tile ) {
     147           0 :   ulong chain_cnt = fd_block_id_map_chain_cnt_est( tile->replay.max_live_slots );
     148             : 
     149           0 :   ulong l = FD_LAYOUT_INIT;
     150           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_replay_tile_t),           sizeof(fd_replay_tile_t) );
     151           0 :   l = FD_LAYOUT_APPEND( l, fd_genesis_align(),                  fd_genesis_footprint( fd_genesis_account_max( tile->replay.genesis_max_message_size ) ) );
     152           0 :   l = FD_LAYOUT_APPEND( l, fd_runtime_stack_align(),            fd_runtime_stack_footprint( FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS, FD_RUNTIME_MAX_STAKED_VOTE_ACCOUNTS, FD_RUNTIME_MAX_STAKE_ACCOUNTS ) );
     153           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_block_id_ele_t),          sizeof(fd_block_id_ele_t) * tile->replay.max_live_slots );
     154           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),                      sizeof(ulong) * tile->replay.max_live_slots );
     155           0 :   if( FD_UNLIKELY( tile->replay.report_runtime_diffs ) ) {
     156           0 :     l = FD_LAYOUT_APPEND( l, alignof(fd_hash_t),                  sizeof(fd_hash_t) * FD_FEC_BLK_MAX * tile->replay.max_live_slots );
     157           0 :     l = FD_LAYOUT_APPEND( l, 8UL,                                 FD_EVENT_RUNTIME_SLOT_DIFFS_FOOTPRINT * tile->replay.max_live_slots );
     158           0 :   }
     159           0 :   l = FD_LAYOUT_APPEND( l, fd_block_id_map_align(),             fd_block_id_map_footprint( chain_cnt ) );
     160           0 :   l = FD_LAYOUT_APPEND( l, fd_bhm_map_align(),                  fd_bhm_map_footprint( chain_cnt ) );
     161           0 :   l = FD_LAYOUT_APPEND( l, fd_bhm_pool_align(),                 fd_bhm_pool_footprint( tile->replay.max_live_slots ) );
     162           0 :   l = FD_LAYOUT_APPEND( l, fd_bhm_dlist_align(),                fd_bhm_dlist_footprint() );
     163           0 :   l = FD_LAYOUT_APPEND( l, fd_txncache_align(),                 fd_txncache_footprint( tile->replay.max_live_slots ) );
     164           0 :   l = FD_LAYOUT_APPEND( l, fd_accdb_align(),                    fd_accdb_footprint( tile->replay.max_live_slots, 0 ) );
     165           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_reception_stats_t),       sizeof(fd_reception_stats_t)*tile->replay.max_live_slots );
     166           0 :   l = FD_LAYOUT_APPEND( l, fd_sched_align(),                    fd_sched_footprint( tile->replay.sched_depth, tile->replay.max_live_slots, tile->replay.max_shreds_per_block, tile->replay.max_txn_per_slot ) );
     167           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_pubkey_t),                FD_SCHED_LTHASH_START_WRITES_MAX*sizeof(fd_pubkey_t) );
     168           0 :   l = FD_LAYOUT_APPEND( l, fd_vote_tracker_align(),             fd_vote_tracker_footprint() );
     169           0 :   l = FD_LAYOUT_APPEND( l, fd_capture_ctx_align(),              fd_capture_ctx_footprint() );
     170           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_dump_proto_ctx_t),        sizeof(fd_dump_proto_ctx_t) );
     171           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_event_block_completed_t), sizeof(fd_event_block_completed_t) );
     172           0 :   l = FD_LAYOUT_APPEND( l, fd_timing_slot_pool_align(),         fd_timing_slot_pool_footprint( FD_REPLAY_TXN_TIMING_SLOTS ) );
     173           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_replay_txn_timing_t),     FD_REPLAY_TXN_TIMING_SLOTS*tile->replay.max_txn_per_slot*sizeof(fd_replay_txn_timing_t) );
     174           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),                      tile->replay.max_live_slots*sizeof(ulong) );
     175           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_replay_footer_end_t),     tile->replay.max_live_slots*sizeof(fd_replay_footer_end_t) );
     176             : 
     177           0 :   if( FD_UNLIKELY( tile->replay.dump_block_to_pb ) ) {
     178           0 :     l = FD_LAYOUT_APPEND( l, fd_block_dump_context_align(), fd_block_dump_context_footprint() );
     179           0 :   }
     180             : 
     181           0 :   l = FD_LAYOUT_FINI( l, scratch_align() );
     182             : 
     183           0 :   return l;
     184           0 : }
     185             : 
     186             : /* wait_info_healthy returns 1 if the replay tile is healthy: caught up
     187             :    and not falling behind turbine by more than 12 slots. */
     188             : 
     189             : static inline int
     190             : wait_info_healthy( int   caught_up,
     191             :                    ulong catch_up_max_fec_slot,
     192           0 :                    ulong reset_slot ) {
     193           0 :   ulong turbine_slot = catch_up_max_fec_slot==ULONG_MAX ? 0UL : catch_up_max_fec_slot;
     194           0 :   ulong health_reset = reset_slot==ULONG_MAX            ? 0UL : reset_slot;
     195           0 :   return caught_up && !( turbine_slot>health_reset && turbine_slot-health_reset>12UL );
     196           0 : }
     197             : 
     198             : /* query_next_leader_slot returns the next slot at or after from the
     199             :    one we lead, ULONG_MAX if the schedules hold none. */
     200             : 
     201             : static ulong
     202             : query_next_leader_slot( fd_replay_tile_t * ctx,
     203           0 :                         ulong              from ) {
     204           0 :   if( FD_UNLIKELY( ctx->next_leader_query_start==ULONG_MAX ||
     205           0 :                    from<ctx->next_leader_query_start       ||
     206           0 :                    from>ctx->next_leader_query_slot        ) ) {
     207           0 :     ctx->next_leader_query_start = from;
     208           0 :     ctx->next_leader_query_slot  = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, from, ctx->identity_pubkey );
     209           0 :   }
     210           0 :   return ctx->next_leader_query_slot;
     211           0 : }
     212             : 
     213             : /* refresh_epoch_info republishes the epoch fields wait_info exposes.
     214             :    Call wherever reset_slot moves. */
     215             : 
     216             : static void
     217             : refresh_epoch_info( fd_replay_tile_t * ctx,
     218             :                     fd_bank_t const *  bank,
     219           0 :                     ulong              slot ) {
     220           0 :   fd_epoch_schedule_t const * sched = &bank->f.epoch_schedule;
     221           0 :   ulong ep = fd_slot_to_epoch( sched, slot, NULL );
     222           0 :   ctx->epoch_end_slot  = fd_epoch_slot0( sched, ep ) + fd_epoch_slot_cnt( sched, ep ) - 1UL;
     223           0 :   ctx->slots_per_epoch = sched->slots_per_epoch;
     224           0 :   ctx->ns_per_slot     = bank->f.slot_params.ns_per_slot;
     225           0 : }
     226             : 
     227             : /* wait_info_publish writes the wait_info topology object so the wait
     228             :    command can read a consistent snapshot without touching metrics. */
     229             : 
     230             : static void
     231           0 : wait_info_publish( fd_replay_tile_t * ctx ) {
     232           0 :   if( FD_UNLIKELY( !ctx->wait_info ) ) return; /* not in this topology */
     233             : 
     234             :   /* Query the schedule, not ctx->next_leader_slot: there ULONG_MAX
     235             :      means already consumed, but the wire maps it to 0, which the gap
     236             :      check reads as no upcoming leader slot and passes. */
     237             : 
     238           0 :   ulong highwater = fd_ulong_if( ctx->highwater_leader_slot==ULONG_MAX, 0UL, ctx->highwater_leader_slot );
     239           0 :   ulong from = fd_ulong_max( ctx->reset_slot==ULONG_MAX ? 0UL : ctx->reset_slot+1UL,
     240           0 :                              fd_ulong_if( !highwater, 0UL, highwater+1UL ) );
     241           0 :   int   next_leader_known = !!fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, from );
     242           0 :   ulong nl                = query_next_leader_slot( ctx, from );
     243           0 :   ulong next_leader       = nl==ULONG_MAX ? 0UL : nl;
     244           0 :   int   healthy           = wait_info_healthy( ctx->caught_up, ctx->catch_up_max_fec_slot, ctx->reset_slot );
     245             : 
     246           0 :   fd_wait_info_write_begin( ctx->wait_info );
     247           0 :   ctx->wait_info->info.caught_up                  = healthy;
     248           0 :   ctx->wait_info->info.reset_slot                 = ctx->reset_slot==ULONG_MAX ? 0UL : ctx->reset_slot;
     249           0 :   ctx->wait_info->info.tip_slot                   = ctx->catch_up_max_fec_slot==ULONG_MAX ? 0UL : ctx->catch_up_max_fec_slot;
     250           0 :   ctx->wait_info->info.leader_highwater_slot      = highwater;
     251           0 :   ctx->wait_info->info.next_leader_known          = next_leader_known;
     252           0 :   ctx->wait_info->info.next_leader_slot           = ctx->leader_bank ? ctx->leader_bank->f.slot : next_leader;
     253           0 :   ctx->wait_info->info.leader_slot                = ctx->leader_bank ? ctx->leader_bank->f.slot : 0UL;
     254           0 :   ctx->wait_info->info.epoch_end_slot             = ctx->epoch_end_slot;
     255           0 :   ctx->wait_info->info.slots_per_epoch            = ctx->slots_per_epoch;
     256           0 :   ctx->wait_info->info.ns_per_slot                = ctx->ns_per_slot;
     257           0 :   ctx->wait_info->info.delinquent_known           = ctx->delinquent_known;
     258           0 :   ctx->wait_info->info.delinquent_stake_lamports  = ctx->delinquent_stake_lamports;
     259           0 :   ctx->wait_info->info.cluster_active_stake_lamports = ctx->cluster_active_stake_lamports;
     260           0 :   ctx->wait_info->info.snap_active                = ctx->snapmk.active;
     261           0 :   ctx->wait_info->info.snap_finished_full         = ctx->snapmk.snap_finished_full;
     262           0 :   ctx->wait_info->info.snap_finished_incr         = ctx->snapmk.snap_finished_incr;
     263           0 :   ctx->wait_info->info.snap_full_interval_blocks  = ctx->snapmk.full_interval_blocks;
     264           0 :   ctx->wait_info->info.snap_incr_interval_blocks  = ctx->snapmk.incremental_interval_blocks;
     265           0 :   ctx->wait_info->info.snap_produced_incr_cnt     = ctx->snapmk.snap_produced_incr_cnt;
     266           0 :   fd_wait_info_write_end( ctx->wait_info );
     267           0 : }
     268             : 
     269             : static inline void
     270           0 : metrics_write( fd_replay_tile_t * ctx ) {
     271           0 :   fd_accdb_flush_metrics( ctx->accdb );
     272             : 
     273           0 :   ulong store_fec_spilled    = 0UL;
     274           0 :   ulong store_fec_spill_bytes= 0UL;
     275           0 :   ulong store_fec_spill_reads= 0UL;
     276           0 :   ulong store_fec_spill_read_bytes = 0UL;
     277           0 :   ulong store_fec_cache_used = 0UL;
     278           0 :   ulong store_fec_cache_max  = 0UL;
     279           0 :   ulong store_fec_cache_pinned = 0UL;
     280           0 :   if( FD_LIKELY( ctx->store ) ) {
     281           0 :     store_fec_spilled          = FD_VOLATILE_CONST( ctx->store->fec_spill_cnt );
     282           0 :     store_fec_spill_bytes      = FD_VOLATILE_CONST( ctx->store->fec_spill_bytes );
     283           0 :     store_fec_spill_reads      = FD_VOLATILE_CONST( ctx->store->fec_spill_read_cnt );
     284           0 :     store_fec_spill_read_bytes = FD_VOLATILE_CONST( ctx->store->fec_spill_read_bytes );
     285           0 :     store_fec_cache_pinned     = FD_VOLATILE_CONST( ctx->store->cache_pinned_cnt );
     286             : 
     287           0 :     store_fec_cache_max = FD_VOLATILE_CONST( ctx->store->cache_slot_cnt );
     288           0 :     ulong store_fec_cache_free = fd_ulong_min( FD_VOLATILE_CONST( ctx->store->cache_free_cnt ), store_fec_cache_max );
     289           0 :     store_fec_cache_used = store_fec_cache_max - store_fec_cache_free;
     290           0 :   }
     291             : 
     292           0 :   FD_MHIST_COPY( REPLAY, STORE_QUERY_DURATION_NANOS,     ctx->metrics.store_query_work        );
     293           0 :   FD_MCNT_SET  ( REPLAY, STORE_FEC_QUERIED,              ctx->metrics.store_query_cnt         );
     294           0 :   FD_MCNT_SET  ( REPLAY, STORE_FEC_QUERY_MISSED,        ctx->metrics.store_query_missing_cnt );
     295           0 :   FD_MCNT_SET  ( REPLAY, STORE_FEC_SPILLED,          store_fec_spilled                    );
     296           0 :   FD_MCNT_SET  ( REPLAY, STORE_FEC_SPILLED_BYTES,      store_fec_spill_bytes                );
     297           0 :   FD_MCNT_SET  ( REPLAY, STORE_FEC_SPILL_READ,       store_fec_spill_reads                );
     298           0 :   FD_MCNT_SET  ( REPLAY, STORE_FEC_SPILL_READ_BYTES, store_fec_spill_read_bytes           );
     299           0 :   FD_MGAUGE_SET( REPLAY, STORE_FEC_CACHE_USED,       store_fec_cache_used                 );
     300           0 :   FD_MGAUGE_SET( REPLAY, STORE_FEC_CACHE_CAPACITY,   store_fec_cache_max                  );
     301           0 :   FD_MGAUGE_SET( REPLAY, STORE_FEC_CACHE_PINNED,     store_fec_cache_pinned               );
     302             : 
     303           0 :   fd_txncache_metrics_t txncache_metrics[1];
     304           0 :   fd_txncache_metrics_snap( ctx->txncache, txncache_metrics );
     305           0 :   FD_MCNT_SET  ( REPLAY, TXNCACHE_PAGE_MISS,                       txncache_metrics->page_miss_cnt         );
     306           0 :   FD_MCNT_SET  ( REPLAY, TXNCACHE_PAGE_SPILLED,                    txncache_metrics->page_spilled_cnt      );
     307           0 :   FD_MCNT_SET  ( REPLAY, TXNCACHE_PAGE_SPILL_READ,                 txncache_metrics->page_spill_read_cnt   );
     308           0 :   FD_MCNT_SET  ( REPLAY, TXNCACHE_FRAME_EVICTED,                   txncache_metrics->frame_evicted_cnt     );
     309           0 :   FD_MCNT_SET  ( REPLAY, TXNCACHE_SPILL_DURATION_NANOS,            txncache_metrics->spill_ticks           );
     310           0 :   FD_MCNT_SET  ( REPLAY, TXNCACHE_LOCK_READ_WAIT_DURATION_NANOS,   txncache_metrics->lock_read_wait_ticks  );
     311           0 :   FD_MCNT_SET  ( REPLAY, TXNCACHE_LOCK_WRITE_WAIT_DURATION_NANOS,  txncache_metrics->lock_write_wait_ticks );
     312           0 :   FD_MGAUGE_SET( REPLAY, TXNCACHE_PAGE_USED,                       txncache_metrics->page_used             );
     313           0 :   FD_MGAUGE_SET( REPLAY, TXNCACHE_PAGE_CAPACITY,                   txncache_metrics->page_capacity         );
     314           0 :   FD_MGAUGE_SET( REPLAY, TXNCACHE_FRAME_USED,                      txncache_metrics->frame_used            );
     315           0 :   FD_MGAUGE_SET( REPLAY, TXNCACHE_FRAME_CAPACITY,                  txncache_metrics->frame_capacity        );
     316             : 
     317           0 :   fd_stake_delegations_metrics_t stake_delegations_metrics[1];
     318           0 :   fd_stake_delegations_metrics_snap( fd_banks_stake_delegations_root_query( ctx->banks ), stake_delegations_metrics );
     319           0 :   FD_MGAUGE_SET( REPLAY, STAKE_DELEGATION_ROOT_USED,           stake_delegations_metrics->root_used         );
     320           0 :   FD_MGAUGE_SET( REPLAY, STAKE_DELEGATION_ROOT_CAPACITY,       stake_delegations_metrics->root_capacity     );
     321           0 :   FD_MGAUGE_SET( REPLAY, STAKE_DELEGATION_ROOT_DISK_COUNT,     stake_delegations_metrics->root_disk_cnt     );
     322           0 :   FD_MGAUGE_SET( REPLAY, STAKE_DELEGATION_DELTA_USED,          stake_delegations_metrics->delta_used        );
     323           0 :   FD_MGAUGE_SET( REPLAY, STAKE_DELEGATION_DELTA_CAPACITY,      stake_delegations_metrics->delta_capacity    );
     324           0 :   FD_MGAUGE_SET( REPLAY, STAKE_DELEGATION_DELTA_DISK_COUNT,    stake_delegations_metrics->delta_disk_cnt    );
     325           0 :   FD_MCNT_SET  ( REPLAY, STAKE_DELEGATION_ROOT_SPILLED,        stake_delegations_metrics->root_spilled_cnt  );
     326           0 :   FD_MCNT_SET  ( REPLAY, STAKE_DELEGATION_DELTA_SPILLED,       stake_delegations_metrics->delta_spilled_cnt );
     327           0 :   FD_MCNT_SET  ( REPLAY, STAKE_DELEGATION_DISK_READ,           stake_delegations_metrics->disk_read_cnt     );
     328           0 :   FD_MCNT_SET  ( REPLAY, STAKE_DELEGATION_DISK_WRITTEN,        stake_delegations_metrics->disk_written_cnt  );
     329           0 :   FD_MCNT_SET  ( REPLAY, STAKE_DELEGATION_DISK_DURATION_NANOS, stake_delegations_metrics->disk_ticks        );
     330             : 
     331           0 :   FD_MGAUGE_SET( REPLAY, ROOT_SLOT, ctx->consensus_root_slot==ULONG_MAX ? 0UL : ctx->consensus_root_slot );
     332           0 :   ulong leader_slot = ctx->leader_bank ? ctx->leader_bank->f.slot : 0UL;
     333             : 
     334           0 :   if( FD_LIKELY( ctx->leader_bank ) ) {
     335           0 :     FD_MGAUGE_SET( REPLAY, NEXT_LEADER_SLOT, leader_slot );
     336           0 :     FD_MGAUGE_SET( REPLAY, LEADER_SLOT, leader_slot );
     337           0 :   } else {
     338           0 :     FD_MGAUGE_SET( REPLAY, NEXT_LEADER_SLOT, ctx->next_leader_slot==ULONG_MAX ? 0UL : ctx->next_leader_slot );
     339           0 :     FD_MGAUGE_SET( REPLAY, LEADER_SLOT, 0UL );
     340           0 :   }
     341           0 :   FD_MGAUGE_SET( REPLAY, RESET_SLOT, ctx->reset_slot==ULONG_MAX ? 0UL : ctx->reset_slot );
     342           0 :   if( FD_UNLIKELY( ctx->reset_slot!=ULONG_MAX && ctx->consensus_root_slot!=ULONG_MAX ) ) {
     343           0 :     FD_MGAUGE_SET( REPLAY, ROOT_SLOT_DISTANCE, fd_ulong_sat_sub( ctx->reset_slot, ctx->consensus_root_slot ) );
     344           0 :   }
     345           0 :   FD_MGAUGE_SET( REPLAY, LAST_REWARDED_VOTE_SLOT, fd_ulong_if( ctx->metrics.voted_slot==ULONG_MAX, 0UL, ctx->metrics.voted_slot ) );
     346             : 
     347           0 :   FD_MGAUGE_SET( REPLAY, BANK_USED, fd_banks_pool_used_cnt( ctx->banks ) );
     348             : 
     349           0 :   FD_MCNT_SET( REPLAY, BANK_FROZEN, ctx->metrics.slots_total );
     350           0 :   FD_MGAUGE_SET( REPLAY, CHAIN_TXN_HEIGHT, ctx->metrics.transactions_total );
     351             : 
     352           0 :   FD_MGAUGE_SET( REPLAY, LAST_REASSEMBLY_DEQUEUED_SLOT,      ctx->metrics.reasm_latest_slot );
     353           0 :   FD_MGAUGE_SET( REPLAY, LAST_REASSEMBLY_DEQUEUED_FEC_INDEX, ctx->metrics.reasm_latest_fec_idx );
     354             : 
     355           0 :   fd_sched_metrics_write( ctx->sched, ctx->clock );
     356             : 
     357           0 :   FD_MCNT_SET( REPLAY, FEC_POLL_SKIPPED_SCHED_FULL,          ctx->metrics.sched_full );
     358           0 :   FD_MCNT_SET( REPLAY, FEC_POLL_SKIPPED_LEADER_FREEZE_WAIT,  ctx->metrics.leader_bid_wait );
     359           0 :   FD_MCNT_SET( REPLAY, FEC_POLL_SKIPPED_BANK_FULL,           ctx->metrics.banks_full );
     360           0 :   FD_MCNT_SET( REPLAY, FEC_POLL_SKIPPED_LANE_WAIT,           ctx->metrics.lane_wait );
     361           0 :   FD_MCNT_SET( REPLAY, FEC_PARENT_MISSING_DROPPED,           ctx->metrics.parent_unavailable );
     362           0 :   FD_MCNT_SET( REPLAY, LEADER_WINDOW_ALREADY_PRODUCED,       ctx->metrics.leader_slot_already_produced );
     363           0 :   FD_MCNT_SET( REPLAY, LEADER_WINDOW_SUPERSEDED,             ctx->metrics.leader_window_superseded     );
     364           0 :   FD_MCNT_SET( REPLAY, FEC_ROOTED_PAST_SKIPPED,              ctx->metrics.fec_rooted_past    );
     365           0 :   FD_MCNT_SET( REPLAY, ROOT_ADVANCE_DEFERRED,               ctx->metrics.storage_root_behind );
     366             : 
     367           0 :   fd_progcache_admin_metrics_t const * pcm = &fd_progcache_admin_metrics_g;
     368           0 :   FD_MCNT_SET( REPLAY, PROGCACHE_ROOTED, pcm->root_cnt );
     369             : 
     370             :   /* Shared metrics for the program cache */
     371           0 :   ulong pc_class_used[ FD_PROGCACHE_CACHE_CLASS_CNT ];
     372           0 :   ulong pc_class_max [ FD_PROGCACHE_CACHE_CLASS_CNT ];
     373           0 :   fd_progcache_cache_class_occupancy( ctx->progcache, pc_class_used, pc_class_max );
     374           0 :   FD_MGAUGE_ENUM_COPY( REPLAY, PROGCACHE_CLASS_USED,     pc_class_used );
     375           0 :   FD_MGAUGE_ENUM_COPY( REPLAY, PROGCACHE_CLASS_CAPACITY, pc_class_max  );
     376             : 
     377           0 :   FD_ACCDB_METRICS_WRITE( REPLAY, fd_accdb_metrics( ctx->accdb ) );
     378           0 : }
     379             : 
     380             : static void
     381             : metrics_trigger( fd_replay_tile_t * ctx,
     382             :                  uint               reason,
     383           0 :                  ulong              slot ) {
     384           0 :   FD_COMPILER_MFENCE();
     385           0 :   metrics_write( ctx );
     386           0 :   FD_COMPILER_MFENCE();
     387           0 :   fd_event_metrics_trigger( reason, slot, ULONG_MAX );
     388           0 : }
     389             : 
     390             : /* Agave: delinquent once the last vote is this far behind.
     391             :    https://github.com/anza-xyz/agave/blob/v4.2.1/rpc-client-types/src/request.rs#L166 */
     392             : 
     393           0 : #define DELINQUENT_VALIDATOR_SLOT_DISTANCE 128UL
     394             : 
     395             : /* Sampling interval for the O(vote accounts) stake walk, a quarter of
     396             :    DELINQUENT_VALIDATOR_SLOT_DISTANCE so the figure cannot lag far
     397             :    behind the threshold it feeds.  Stamping is not gated by it: a cert
     398             :    not stamped when it arrives is lost. */
     399             : 
     400           0 : #define DELINQUENT_SAMPLE_SLOTS 32UL
     401             : 
     402             : static ushort
     403             : replay_voter_rank( fd_replay_tile_t * ctx,
     404             :                    fd_bank_t *        bank,
     405           0 :                    ulong              epoch ) {
     406           0 :   if( FD_LIKELY( ctx->voter_rank_cached && ctx->voter_rank_epoch==epoch ) ) return ctx->voter_rank;
     407             : 
     408           0 :   ushort fork_id    = bank->vote_stakes_fork_id;
     409           0 :   ulong  fork_epoch = fd_vote_stakes_fork_epoch( fd_bank_vote_stakes( bank ), fork_id );
     410           0 :   int   iter_kind  = FD_VOTE_STAKES_ITER_T_2;
     411           0 :   if( FD_UNLIKELY( epoch!=fork_epoch ) ) {
     412           0 :     if( FD_UNLIKELY( !fork_epoch || epoch!=fork_epoch-1UL ) ) return USHORT_MAX;
     413           0 :     iter_kind = FD_VOTE_STAKES_ITER_T_3;
     414           0 :   }
     415             : 
     416           0 :   ushort rank = USHORT_MAX;
     417           0 :   fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
     418           0 :   uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
     419           0 :   for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, iter_kind, iter_mem );
     420           0 :        !fd_vote_stakes_iter_done( vote_stakes, fork_id, iter_kind, iter );
     421           0 :        fd_vote_stakes_iter_next( vote_stakes, fork_id, iter_kind, iter ) ) {
     422           0 :     fd_vote_stakes_ele_t ele[1];
     423           0 :     fd_vote_stakes_iter_ele( vote_stakes, fork_id, iter_kind, iter, ele );
     424           0 :     if( FD_UNLIKELY( fd_pubkey_eq( &ele->node_account, ctx->identity_pubkey ) ) ) { rank = ele->rank; break; }
     425           0 :   }
     426           0 :   ctx->voter_rank_cached = 1;
     427           0 :   ctx->voter_rank_epoch  = epoch;
     428           0 :   ctx->voter_rank        = rank;
     429           0 :   return rank;
     430           0 : }
     431             : 
     432             : /* fd_rank_is_current returns 1 if the rank's last observed vote is
     433             :    recent enough. */
     434             : 
     435             : static inline int
     436             : fd_rank_is_current( ulong last_settled,
     437           0 :                     ulong rank_last_voted ) {
     438           0 :   return fd_ulong_sat_sub( last_settled, rank_last_voted )<DELINQUENT_VALIDATOR_SLOT_DISTANCE;
     439           0 : }
     440             : 
     441             : /* fd_delinquent_epoch_roll clears the rank table when the epoch
     442             :    advances and raises the settled watermark.  Ranks are remapped across
     443             :    the rollover, so the previous epoch's figure no longer describes the
     444             :    current stake set and reads unknown until a sample repopulates the
     445             :    table.  The watermark cannot regress. */
     446             : 
     447             : static void
     448             : fd_delinquent_epoch_roll( fd_replay_tile_t * ctx,
     449             :                           ulong              reward_slot,
     450           0 :                           ulong              reward_epoch ) {
     451           0 :   if( FD_UNLIKELY( ctx->ag_last_voted_epoch==ULONG_MAX ||
     452           0 :                    reward_epoch>ctx->ag_last_voted_epoch ) ) {
     453           0 :     fd_memset( ctx->ag_last_voted, 0, sizeof(ctx->ag_last_voted) );
     454           0 :     ctx->ag_last_voted_epoch = reward_epoch;
     455           0 :     ctx->delinquent_known    = 0;
     456           0 :     ctx->ag_stamp_since      = ULONG_MAX;
     457           0 :   }
     458           0 :   ctx->ag_last_settled = fd_ulong_max( ctx->ag_last_settled, reward_slot );
     459           0 : }
     460             : 
     461             : /* fd_update_delinquent stamps the reward cert's signers into
     462             :    ag_last_voted and recomputes delinquent stake. */
     463             : 
     464             : static void
     465             : fd_update_delinquent( fd_replay_tile_t *   ctx,
     466             :                       fd_bank_t const *    bank,
     467             :                       ulong                reward_slot,
     468             :                       ulong                reward_epoch,
     469           0 :                       fd_bls_set_t const * reward_set ) {
     470           0 :   if( FD_UNLIKELY( !ctx->wait_info ) ) return; /* not in this topology */
     471             : 
     472           0 :   ushort fork_id    = bank->vote_stakes_fork_id;
     473           0 :   ulong  fork_epoch = fd_vote_stakes_fork_epoch( fd_bank_vote_stakes( bank ), fork_id );
     474             : 
     475             :   /* Roll on the fork's epoch, not the cert's: the cert trails the bank
     476             :      by FD_NUM_SLOTS_FOR_REWARD slots, so it still names the old epoch
     477             :      over the first slots of a new one. */
     478           0 :   fd_delinquent_epoch_roll( ctx, ctx->ag_last_settled, fork_epoch );
     479             : 
     480             :   /* Only T-2 exposes stake and is_valid. */
     481           0 :   if( FD_UNLIKELY( reward_epoch!=fork_epoch ) ) return;
     482             : 
     483             :   /* Ranks are epoch-scoped, and forks can complete an epoch-E block
     484             :      after an epoch-E+1 one. */
     485           0 :   if( FD_UNLIKELY( ctx->ag_last_voted_epoch!=ULONG_MAX &&
     486           0 :                    reward_epoch<ctx->ag_last_voted_epoch ) ) return;
     487             : 
     488             :   /* Raise the watermark, so stamps age out as certs settle. */
     489           0 :   fd_delinquent_epoch_roll( ctx, reward_slot, reward_epoch );
     490             : 
     491             :   /* An empty cert is absence of evidence, so it stamps nothing.  With no
     492             :      figure yet there is nothing to age either, but once one is known the
     493             :      walk below must still run: the roll above advanced ag_last_settled,
     494             :      so existing stamps age out even while certs are absent. */
     495           0 :   if( FD_UNLIKELY( fd_bls_set_is_null( reward_set ) && !ctx->delinquent_known ) ) return;
     496             : 
     497           0 :   if( FD_UNLIKELY( ctx->ag_stamp_since==ULONG_MAX ) ) ctx->ag_stamp_since = reward_slot;
     498             : 
     499             :   /* Stamp straight off the cert: O(signers), no vote stakes walk.  No
     500             :      need to filter: entries the walk skips are never read. */
     501           0 :   for( ulong rank=fd_bls_set_const_iter_init( reward_set );
     502           0 :        !fd_bls_set_const_iter_done( rank );
     503           0 :        rank=fd_bls_set_const_iter_next( reward_set, rank ) ) {
     504           0 :     if( FD_UNLIKELY( rank>=FD_VOTOR_VAT_MAX ) ) break; /* FD_BLS_SET_MAX is sized separately */
     505           0 :     ctx->ag_last_voted[ rank ] = fd_ulong_max( ctx->ag_last_voted[ rank ], reward_slot );
     506           0 :   }
     507             : 
     508           0 :   if( FD_LIKELY( ctx->delinquent_known &&
     509           0 :                  reward_slot<ctx->delinquent_sample_slot+DELINQUENT_SAMPLE_SLOTS ) ) return;
     510           0 :   if( FD_UNLIKELY( reward_slot<ctx->ag_stamp_since+DELINQUENT_SAMPLE_SLOTS ) ) return;
     511             :   /* Slots arrive out of order across forks, so hold the watermark. */
     512           0 :   ctx->delinquent_sample_slot = fd_ulong_max( ctx->delinquent_sample_slot, reward_slot );
     513             : 
     514           0 :   fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
     515           0 :   ulong              delinquent  = 0UL;
     516           0 :   ulong              total       = 0UL;
     517             : 
     518           0 :   uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
     519           0 :   for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter_mem );
     520           0 :        !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter );
     521           0 :        fd_vote_stakes_iter_next( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter ) ) {
     522           0 :     fd_vote_stakes_ele_t ele[1];
     523           0 :     fd_vote_stakes_iter_ele( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter, ele );
     524           0 :     ulong  stake = ele->stake;
     525           0 :     ushort rank  = ele->rank;
     526           0 :     if( FD_UNLIKELY( !ele->is_valid || rank==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ) ) continue;
     527           0 :     FD_TEST( rank<FD_VOTOR_VAT_MAX );
     528             : 
     529           0 :     total += stake;
     530           0 :     if( FD_UNLIKELY( !fd_rank_is_current( ctx->ag_last_settled, ctx->ag_last_voted[ rank ] ) ) ) {
     531           0 :       delinquent += stake;
     532           0 :     }
     533           0 :   }
     534             : 
     535           0 :   ctx->delinquent_stake_lamports     = delinquent;
     536           0 :   ctx->cluster_active_stake_lamports = total;
     537             : 
     538             :   /* Over-counts delinquency, so a partial union blocks rather than
     539             :      passes. */
     540           0 :   ctx->delinquent_known = 1;
     541           0 : }
     542             : 
     543             : static int
     544             : replay_reward_cert_voted( fd_replay_tile_t * ctx,
     545             :                           fd_bank_t *        bank,
     546             :                           ushort *           rank_out,
     547           0 :                           ushort *           count_out ) {
     548           0 :   *rank_out = USHORT_MAX;
     549           0 :   *count_out = USHORT_MAX;
     550           0 :   if( FD_UNLIKELY( bank->f.slot<FD_NUM_SLOTS_FOR_REWARD ) ) return 0;
     551             : 
     552           0 :   ulong  reward_slot  = bank->f.slot-FD_NUM_SLOTS_FOR_REWARD;
     553           0 :   ulong  reward_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, reward_slot, NULL );
     554           0 :   ushort rank         = replay_voter_rank( ctx, bank, reward_epoch );
     555           0 :   *rank_out = rank;
     556             : 
     557           0 :   fd_block_footer_t const * footer = bank==ctx->leader_bank ? ctx->leader_footer : fd_sched_get_footer( ctx->sched, bank->idx );
     558           0 :   if( FD_UNLIKELY( !footer ) ) return 0;
     559             : 
     560             :   /* A validator included in both reward certificates is counted once. */
     561           0 :   fd_bls_set_t reward_set[ fd_bls_set_word_cnt ];
     562           0 :   fd_bls_set_null( reward_set );
     563           0 :   if( FD_UNLIKELY( footer->has_skip_reward_cert ) ) fd_bls_set_union( reward_set, reward_set, footer->skip_reward_cert.signer_set  );
     564           0 :   if( FD_LIKELY( footer->has_notar_reward_cert ) ) fd_bls_set_union( reward_set, reward_set, footer->notar_reward_cert.signer_set );
     565           0 :   *count_out = (ushort)fd_bls_set_cnt( reward_set );
     566             : 
     567           0 :   fd_update_delinquent( ctx, bank, reward_slot, reward_epoch, reward_set );
     568             : 
     569           0 :   if( FD_UNLIKELY( rank==USHORT_MAX ) ) return 0;
     570             : 
     571             :   /* bits at or past the cert's nbits are left clear at decode, so the
     572             :      set test alone bounds the rank */
     573           0 :   int in_cert = fd_bls_set_test( reward_set, rank );
     574             : 
     575           0 :   if( FD_UNLIKELY( in_cert && ( ctx->metrics.voted_slot==ULONG_MAX || reward_slot>ctx->metrics.voted_slot ) ) ) ctx->metrics.voted_slot = reward_slot;
     576           0 :   return in_cert;
     577           0 : }
     578             : 
     579             : static void
     580             : publish_epoch_info( fd_replay_tile_t *  ctx,
     581             :                     fd_stem_context_t * stem,
     582             :                     fd_bank_t *         bank,
     583           0 :                     int                 next_epoch ) {
     584           0 :   fd_epoch_schedule_t const * schedule = &bank->f.epoch_schedule;
     585           0 :   ulong epoch = fd_slot_to_epoch( schedule, bank->f.slot, NULL ) + fd_ulong_if( next_epoch, 1UL, 0UL );
     586             : 
     587           0 :   fd_features_t const * features = &bank->f.features;
     588             : 
     589           0 :   fd_runtime_stack_t * runtime_stack = ctx->runtime_stack;
     590             : 
     591           0 :   fd_epoch_info_msg_t * epoch_info_msg = fd_chunk_to_laddr( ctx->epoch_out->mem, ctx->epoch_out->chunk );
     592             : 
     593           0 :   epoch_info_msg->staked_vote_cnt   = next_epoch ? runtime_stack->epoch_weights.next_stake_weights_cnt : runtime_stack->epoch_weights.stake_weights_cnt;
     594           0 :   epoch_info_msg->staked_id_cnt     = next_epoch ? runtime_stack->epoch_weights.next_id_weights_cnt    : runtime_stack->epoch_weights.id_weights_cnt;
     595           0 :   epoch_info_msg->epoch_schedule    = *schedule;
     596           0 :   epoch_info_msg->features          = *features;
     597           0 :   epoch_info_msg->epoch             = epoch;
     598           0 :   epoch_info_msg->start_slot        = fd_epoch_slot0( schedule, epoch );
     599           0 :   epoch_info_msg->slot_cnt          = fd_epoch_slot_cnt( schedule, epoch );
     600           0 :   epoch_info_msg->ns_per_slot       = fd_slot_params_at_slot( bank, epoch_info_msg->start_slot ).ns_per_slot;
     601             : 
     602           0 :   fd_vote_stake_weight_t * stake_weights = fd_type_pun( epoch_info_msg + 1 );
     603           0 :   fd_vote_stake_weight_t * src_stake_weights = next_epoch ? runtime_stack->epoch_weights.next_stake_weights : runtime_stack->epoch_weights.stake_weights;
     604           0 :   memcpy( stake_weights, src_stake_weights, epoch_info_msg->staked_vote_cnt * sizeof(fd_vote_stake_weight_t) );
     605             : 
     606           0 :   fd_stake_weight_t * id_weights = fd_epoch_info_msg_id_weights( epoch_info_msg );
     607           0 :   fd_stake_weight_t * src_id_weights = next_epoch ? runtime_stack->epoch_weights.next_id_weights : runtime_stack->epoch_weights.id_weights;
     608           0 :   fd_memcpy( id_weights, src_id_weights, epoch_info_msg->staked_id_cnt * sizeof(fd_stake_weight_t) );
     609             : 
     610           0 :   ulong epoch_info_sz = fd_epoch_info_msg_sz( epoch_info_msg->staked_vote_cnt, epoch_info_msg->staked_id_cnt );
     611           0 :   ulong epoch_info_sig = 4UL;
     612           0 :   fd_stem_publish( stem, ctx->epoch_out->idx, epoch_info_sig, ctx->epoch_out->chunk, epoch_info_sz, 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
     613           0 :   ctx->epoch_out->chunk = fd_dcache_compact_next( ctx->epoch_out->chunk, epoch_info_sz, ctx->epoch_out->chunk0, ctx->epoch_out->wmark );
     614             : 
     615           0 :   fd_multi_epoch_leaders_epoch_msg_init( ctx->mleaders, epoch_info_msg );
     616           0 :   fd_multi_epoch_leaders_epoch_msg_fini( ctx->mleaders );
     617             : 
     618           0 :   ctx->next_leader_query_start = ULONG_MAX;
     619           0 : }
     620             : 
     621             : static void
     622             : publish_replay_out( fd_replay_tile_t *  ctx,
     623             :                     fd_stem_context_t * stem,
     624             :                     ulong               sig,
     625           0 :                     ulong               sz ) {
     626           0 :   ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
     627           0 :   if( FD_UNLIKELY( sig!=REPLAY_SIG_TXN_EXECUTED && sig!=REPLAY_SIG_SLOT_STATES ) ) {
     628           0 :     fd_replay_out_link_t * out = sig==REPLAY_SIG_MISSING_FEC ? ctx->rotor_out : ctx->slot_out;
     629           0 :     if( FD_LIKELY( out->idx!=ULONG_MAX ) ) { /* backtest has no rotor */
     630           0 :       fd_memcpy( fd_chunk_to_laddr( out->mem, out->chunk ), fd_chunk_to_laddr_const( ctx->replay_out->mem, ctx->replay_out->chunk ), sz );
     631           0 :       fd_stem_publish( stem, out->idx, sig, out->chunk, sz, 0UL, 0UL, tspub );
     632           0 :       out->chunk = fd_dcache_compact_next( out->chunk, sz, out->chunk0, out->wmark );
     633           0 :     }
     634           0 :   }
     635           0 :   fd_stem_publish( stem, ctx->replay_out->idx, sig, ctx->replay_out->chunk, sz, 0UL, 0UL, tspub );
     636           0 :   ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sz, ctx->replay_out->chunk0, ctx->replay_out->wmark );
     637           0 : }
     638             : 
     639             : /**********************************************************************/
     640             : /* Transaction execution state machine helpers                        */
     641             : /**********************************************************************/
     642             : 
     643             : static inline void
     644             : timing_slot_release( fd_replay_tile_t * ctx,
     645           0 :                      ulong              bank_idx ) {
     646           0 :   ulong tslot = ctx->timing_slot_of_bank[ bank_idx ];
     647           0 :   if( FD_LIKELY( tslot!=fd_timing_slot_pool_idx_null( ctx->timing_slot_pool ) ) ) {
     648           0 :     fd_timing_slot_pool_idx_release( ctx->timing_slot_pool, tslot );
     649           0 :     ctx->timing_slot_of_bank[ bank_idx ] = fd_timing_slot_pool_idx_null( ctx->timing_slot_pool );
     650           0 :   }
     651           0 : }
     652             : 
     653             : static void
     654             : replay_block_start( fd_replay_tile_t * ctx,
     655             :                     ulong              bank_idx,
     656             :                     ulong              parent_bank_idx,
     657             :                     ulong              slot,
     658           0 :                     int                lthash_inband ) {
     659           0 :   long before = fd_clock_tile_now( ctx->clock );
     660             : 
     661           0 :   fd_bank_t * bank = fd_banks_bank_query( ctx->banks, bank_idx );
     662           0 :   FD_CHECK_CRIT( bank, "invariant violation: bank is NULL" );
     663           0 :   FD_CHECK_CRIT( bank->state==FD_BANK_STATE_INIT, "invariant violation: bank is not in correct state" );
     664             : 
     665           0 :   bank->preparation_begin_nanos = before;
     666             : 
     667           0 :   FD_TEST( ctx->timing_slot_of_bank[ bank_idx ]==fd_timing_slot_pool_idx_null( ctx->timing_slot_pool ) );
     668           0 :   if( FD_LIKELY( fd_timing_slot_pool_free( ctx->timing_slot_pool ) ) ) {
     669           0 :     ulong tslot = fd_timing_slot_pool_idx_acquire( ctx->timing_slot_pool );
     670           0 :     fd_timing_slot_pool_ele( ctx->timing_slot_pool, tslot )->cnt = 0UL;
     671           0 :     ctx->timing_slot_of_bank[ bank_idx ] = tslot;
     672           0 :   }
     673             : 
     674           0 :   fd_bank_t * parent_bank = fd_banks_bank_query( ctx->banks, parent_bank_idx );
     675           0 :   FD_CHECK_CRIT( parent_bank, "invariant violation: parent bank is NULL" );
     676           0 :   FD_CHECK_CRIT( parent_bank->state==FD_BANK_STATE_FROZEN || parent_bank->state==FD_BANK_STATE_PRUNABLE, "invariant violation: parent bank is not in correct state" );
     677             : 
     678             :   /* Clone the bank from the parent.  We must special case the first
     679             :      slot that is executed as the snapshot does not provide a parent
     680             :      block id. */
     681             : 
     682           0 :   bank = fd_banks_clone_from_parent( ctx->banks, bank_idx );
     683           0 :   if( FD_UNLIKELY( !bank ) ) {
     684           0 :     FD_LOG_CRIT(( "invariant violation: bank is NULL for bank index %lu", bank_idx ));
     685           0 :   }
     686           0 :   fd_pubkey_t const * leader = fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, slot );
     687           0 :   FD_TEST( leader );
     688           0 :   bank->f.slot               = slot;
     689           0 :   bank->leader               = *leader;
     690           0 :   bank->txncache_fork_id     = fd_txncache_attach_child ( ctx->txncache,  parent_bank->txncache_fork_id  );
     691           0 :   bank->progcache_fork_id    = fd_progcache_attach_child( ctx->progcache, parent_bank->progcache_fork_id );
     692           0 :   bank->accdb_fork_id        = fd_accdb_attach_child    ( ctx->accdb,     parent_bank->accdb_fork_id     );
     693           0 :   bank->parent_accdb_fork_id = parent_bank->accdb_fork_id;
     694             : 
     695           0 :   ulong new_epoch  = fd_slot_to_epoch( &parent_bank->f.epoch_schedule, slot, NULL );
     696           0 :   ulong root_epoch = fd_slot_to_epoch( &parent_bank->f.epoch_schedule, ctx->published_root_slot, NULL );
     697           0 :   if( FD_UNLIKELY( new_epoch>root_epoch+1UL ) ) {
     698           0 :     FD_LOG_CRIT(( "firedancer replay does not support replaying more than one epoch ahead of the current root" ));
     699           0 :   }
     700             : 
     701           0 :   FD_TEST( bank->lthash_mode==FD_BANK_LTHASH_MODE_INBAND );
     702           0 :   if( FD_LIKELY( !lthash_inband ) ) {
     703           0 :     ctx->lthash_rec->cnt = 0UL;
     704           0 :     bank->lthash_rec     = ctx->lthash_rec;
     705           0 :     bank->lthash_mode    = FD_BANK_LTHASH_MODE_OOB_RECORD;
     706           0 :   }
     707             : 
     708             :   /* Update required runtime state and handle potential boundary. */
     709             : 
     710           0 :   int is_epoch_boundary = 0;
     711           0 :   fd_runtime_block_execute_prepare( ctx->banks, bank, ctx->accdb, ctx->runtime_stack, ctx->capture_ctx, &is_epoch_boundary );
     712           0 :   FD_CHECK_CRIT( !is_epoch_boundary || FD_FEATURE_ACTIVE_BANK( bank, alpenglow ), "alpenglow feature is not active" );
     713             : 
     714           0 :   if( FD_LIKELY( !lthash_inband ) ) {
     715           0 :     fd_sched_block_start_writes( ctx->sched, bank_idx, fd_type_pun_const( ctx->lthash_rec->keys ), ctx->lthash_rec->cnt );
     716           0 :     bank->lthash_rec  = NULL;
     717           0 :     bank->lthash_mode = FD_BANK_LTHASH_MODE_OOB_SKIP;
     718           0 :   }
     719             : 
     720           0 :   ulong max_tick_height;
     721           0 :   if( FD_UNLIKELY( FD_RUNTIME_EXECUTE_SUCCESS!=fd_runtime_compute_max_tick_height( parent_bank->f.ticks_per_slot, slot, &max_tick_height ) ) ) {
     722           0 :     FD_LOG_CRIT(( "couldn't compute tick height/max tick height slot %lu ticks_per_slot %lu", slot, parent_bank->f.ticks_per_slot ));
     723           0 :   }
     724           0 :   bank->f.max_tick_height = max_tick_height;
     725             :   /* in alpenglow, we expect only one tick per block.  Instead of
     726             :      adjusting max tick height, we match agave behavior by setting
     727             :      tick height to max tick height - 1. These fields must stay in
     728             :      line with agave behavior. */
     729           0 :   bank->f.tick_height = bank->f.max_tick_height - 1UL;
     730           0 :   fd_sched_set_poh_params( ctx->sched, bank->idx, bank->f.tick_height, bank->f.max_tick_height, &parent_bank->f.poh );
     731           0 :   fd_sched_set_flh_active( ctx->sched, bank->idx, FD_FEATURE_ACTIVE_BANK( bank, alpenglow_fast_leader_handover ) );
     732             : 
     733           0 :   FD_LOG_DEBUG(( "replay_block_start: bank_idx=%lu slot=%lu parent_bank_idx=%lu", bank_idx, slot, parent_bank_idx ));
     734           0 : }
     735             : 
     736             : static void
     737           0 : cost_tracker_snap( fd_bank_t * bank, fd_replay_slot_completed_t * slot_info ) {
     738           0 :   if( FD_LIKELY( bank->cost_tracker_fork_id!=USHORT_MAX ) ) {
     739           0 :     fd_cost_tracker_t const * cost_tracker = fd_cost_tracker_store_pin( fd_bank_cost_tracker( bank ), bank->cost_tracker_fork_id );
     740           0 :     if( FD_UNLIKELY( cost_tracker->block_cost_limit==0UL ) ) {
     741           0 :       memset( &slot_info->cost_tracker, -1 /* ULONG_MAX */, sizeof(slot_info->cost_tracker) );
     742           0 :     } else {
     743           0 :       slot_info->cost_tracker.block_cost                   = cost_tracker->block_cost;
     744           0 :       slot_info->cost_tracker.allocated_accounts_data_size = cost_tracker->allocated_accounts_data_size;
     745           0 :       slot_info->cost_tracker.block_cost_limit             = cost_tracker->block_cost_limit;
     746           0 :       slot_info->cost_tracker.account_cost_limit           = cost_tracker->account_cost_limit;
     747           0 :     }
     748           0 :     fd_cost_tracker_store_unpin( fd_bank_cost_tracker( bank ), bank->cost_tracker_fork_id );
     749           0 :   } else {
     750           0 :     memset( &slot_info->cost_tracker, -1 /* ULONG_MAX */, sizeof(slot_info->cost_tracker) );
     751           0 :   }
     752           0 :   slot_info->cost_tracker.pool_idx = fd_ulong_if( bank->cost_tracker_fork_id==USHORT_MAX, ULONG_MAX, bank->cost_tracker_fork_id );
     753           0 : }
     754             : 
     755             : static int
     756           0 : sched_dead_reason_to_event( int sched_reason ) {
     757           0 :   switch( sched_reason ) {
     758           0 :     case FD_SCHED_DEAD_REASON_UNPARSEABLE_CONTENT:         return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_UNPARSEABLE_CONTENT;
     759           0 :     case FD_SCHED_DEAD_REASON_SHORT_BLOCK:                 return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_SHORT_BLOCK;
     760           0 :     case FD_SCHED_DEAD_REASON_TOO_MANY_TXNS:               return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TOO_MANY_TXNS;
     761           0 :     case FD_SCHED_DEAD_REASON_TOO_MANY_MICROBLOCKS:        return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TOO_MANY_MICROBLOCKS;
     762           0 :     case FD_SCHED_DEAD_REASON_DUPLICATE_ACCOUNT:           return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_DUPLICATE_ACCOUNT;
     763           0 :     case FD_SCHED_DEAD_REASON_TRAILING_ENTRY:              return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TRAILING_ENTRY;
     764           0 :     case FD_SCHED_DEAD_REASON_TOO_MANY_TICKS:              return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TOO_MANY_TICKS;
     765           0 :     case FD_SCHED_DEAD_REASON_TOO_FEW_TICKS:               return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TOO_FEW_TICKS;
     766           0 :     case FD_SCHED_DEAD_REASON_TICK_HASH_MISMATCH:          return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TICK_HASH_MISMATCH;
     767           0 :     case FD_SCHED_DEAD_REASON_ENTRY_HASH_MISMATCH:         return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ENTRY_HASH_MISMATCH;
     768           0 :     case FD_SCHED_DEAD_REASON_ENTRY_HASH_MISMATCH_INGEST:  return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ENTRY_HASH_MISMATCH_INGEST;
     769           0 :     case FD_SCHED_DEAD_REASON_DEAD_ANCESTOR:               return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_PARENT_DEAD;
     770           0 :     case FD_SCHED_DEAD_REASON_BAD_BLOCK_MARKER:            return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_BAD_BLOCK_MARKER;
     771           0 :     case FD_SCHED_DEAD_REASON_ALPENGLOW_HASH_CNT:          return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ALPENGLOW_HASH_CNT;
     772           0 :     case FD_SCHED_DEAD_REASON_MISSING_PARENT_MARKER:       return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_MISSING_PARENT_MARKER;
     773           0 :     case FD_SCHED_DEAD_REASON_MULTIPLE_BLOCK_HEADERS:      return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_MULTIPLE_BLOCK_HEADERS;
     774           0 :     case FD_SCHED_DEAD_REASON_GENESIS_CERT_OUT_OF_ORDER:   return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_GENESIS_CERT_OUT_OF_ORDER;
     775           0 :     case FD_SCHED_DEAD_REASON_MULTIPLE_BLOCK_FOOTERS:      return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_MULTIPLE_BLOCK_FOOTERS;
     776           0 :     case FD_SCHED_DEAD_REASON_ENTRY_AFTER_BLOCK_FOOTER:    return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ENTRY_AFTER_BLOCK_FOOTER;
     777           0 :     case FD_SCHED_DEAD_REASON_INVALID_ALPENTICK_POSITION:  return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_INVALID_ALPENTICK_POSITION;
     778           0 :     case FD_SCHED_DEAD_REASON_MISSING_BLOCK_FOOTER:        return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_MISSING_BLOCK_FOOTER;
     779           0 :     case FD_SCHED_DEAD_REASON_SPURIOUS_UPDATE_PARENT:      return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_SPURIOUS_UPDATE_PARENT;
     780           0 :     case FD_SCHED_DEAD_REASON_TOO_MANY_WRITABLE_ACCOUNTS:  return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TOO_MANY_WRITABLE_ACCOUNTS;
     781           0 :     case FD_SCHED_DEAD_REASON_MULTIPLE_UPDATE_PARENTS:     return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_MULTIPLE_UPDATE_PARENTS;
     782           0 :     case FD_SCHED_DEAD_REASON_UPDATE_PARENT_MISMATCH:      return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_UPDATE_PARENT_MISMATCH;
     783           0 :     default: FD_LOG_CRIT(( "unmapped scheduler dead reason %d", sched_reason ));
     784           0 :   }
     785           0 : }
     786             : 
     787             : static int
     788           0 : sched_block_dead_reason_to_event( fd_replay_tile_t * ctx, ulong bank_idx ) {
     789           0 :   if( FD_UNLIKELY( fd_sched_block_is_discarded( ctx->sched, bank_idx ) ) ) return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD;
     790           0 :   return sched_dead_reason_to_event( fd_sched_get_dead_reason( ctx->sched, bank_idx ) );
     791           0 : }
     792             : 
     793             : /* The stats array is keyed by bank idx. */
     794             : 
     795             : static void
     796             : block_completed_event_fill_reception( fd_replay_tile_t *           ctx,
     797             :                                       fd_event_block_completed_t * ev,
     798           0 :                                       ulong                        bank_idx ) {
     799           0 :   ev->lowest_verified_fec_index    = 0U;
     800           0 :   ev->last_completed_fec_set_index = UINT_MAX;
     801             : 
     802           0 :     fd_bank_t const * bank = bank_idx==ULONG_MAX ? NULL : fd_banks_bank_query( ctx->banks, bank_idx );
     803           0 :     if( FD_UNLIKELY( !bank ) ) return;
     804             : 
     805           0 :     fd_block_id_ele_t const * ele = &ctx->block_id_arr[ bank_idx ];
     806           0 :     ev->fec_set_count = ele->fec_cnt;
     807             : 
     808           0 :     if( FD_LIKELY( bank->parent_idx!=ULONG_MAX ) ) {
     809           0 :       fd_block_id_ele_t const * parent = &ctx->block_id_arr[ bank->parent_idx ];
     810           0 :       fd_memcpy( ev->parent_block_id, parent->dmr.uc, sizeof(ev->parent_block_id) );
     811           0 :       if( !ev->parent_slot ) ev->parent_slot = parent->slot;
     812           0 :     }
     813             : 
     814             :     /* A mismatched bank_seq means the entry belongs to a previous
     815             :        occupant of this bank idx, i.e. we have no stats for this block. */
     816           0 :     fd_reception_stats_t const * s = &ctx->reception_stats[ bank_idx ];
     817           0 :     if( FD_UNLIKELY( s->bank_seq!=bank->bank_seq ) ) return;
     818           0 :     ev->votor_repaired = !!s->votor_repaired;
     819           0 : }
     820             : 
     821             : static void
     822             : block_completed_event_fill_bank( fd_replay_tile_t *           ctx,
     823             :                                  fd_event_block_completed_t * ev,
     824           0 :                                  fd_bank_t *                  bank ) {
     825           0 :   fd_bank_t * parent_bank = fd_banks_get_parent( ctx->banks, bank );
     826             : 
     827           0 :   int   prepared = bank->preparation_begin_nanos!=0L;
     828           0 :   ulong slot     = prepared ? bank->f.slot : ctx->block_id_arr[ bank->idx ].slot;
     829             : 
     830           0 :   ev->bank_seq    = bank->bank_seq;
     831           0 :   ev->slot        = slot;
     832           0 :   ev->parent_slot = prepared                    ? bank->f.parent_slot
     833           0 :                   : bank->parent_idx!=ULONG_MAX ? ctx->block_id_arr[ bank->parent_idx ].slot
     834           0 :                   :                               0UL;
     835           0 :   if(      FD_LIKELY( prepared )                ) ev->epoch = bank->f.epoch;
     836           0 :   else if( FD_LIKELY( ctx->notified_root_bank ) ) ev->epoch = fd_slot_to_epoch( &ctx->notified_root_bank->f.epoch_schedule, slot, NULL );
     837           0 :   fd_block_id_ele_t const * ele = &ctx->block_id_arr[ bank->idx ];
     838           0 :   fd_memcpy( ev->block_id, ele->block_id_seen ? ele->dmr.uc : ele->latest_mr.uc, sizeof(ev->block_id) );
     839           0 :   if( FD_LIKELY( bank->block_completed_nanos ) ) fd_memcpy( ev->bank_hash, bank->f.bank_hash.uc, sizeof(ev->bank_hash) );
     840             : 
     841           0 :   ev->first_fec_set_received_time      = bank->is_leader ? 0UL : (ulong)bank->first_fec_set_received_nanos;
     842           0 :   ev->preparation_begin_time           = (ulong)bank->preparation_begin_nanos;
     843           0 :   ev->first_transaction_scheduled_time = (ulong)bank->first_transaction_scheduled_nanos;
     844           0 :   ev->last_transaction_finished_time   = (ulong)bank->last_transaction_finished_nanos;
     845           0 :   ev->block_completed_time             = (ulong)bank->block_completed_nanos;
     846           0 :   ev->parent_block_completed_time      = parent_bank ? (ulong)parent_bank->block_completed_nanos : 0UL;
     847             : 
     848           0 :   if( FD_UNLIKELY( bank->cost_tracker_fork_id!=USHORT_MAX ) ) {
     849           0 :     fd_cost_tracker_t const * ct = fd_cost_tracker_store_pin( fd_bank_cost_tracker( bank ), bank->cost_tracker_fork_id );
     850           0 :     ev->cost_tracker_block_cost                   = ct->block_cost;
     851           0 :     ev->cost_tracker_allocated_accounts_data_size = ct->allocated_accounts_data_size;
     852           0 :     ev->cost_tracker_block_cost_limit             = ct->block_cost_limit;
     853           0 :     ev->cost_tracker_account_cost_limit           = ct->account_cost_limit;
     854           0 :     fd_cost_tracker_store_unpin( fd_bank_cost_tracker( bank ), bank->cost_tracker_fork_id );
     855           0 :   }
     856             : 
     857           0 :   ev->bank_idx = bank->idx;
     858           0 :   if( FD_LIKELY( prepared ) ) {
     859           0 :     ev->txncache_fork_id            = bank->txncache_fork_id.val;
     860           0 :     ev->progcache_fork_id           = bank->progcache_fork_id;
     861           0 :     ev->accdb_fork_id               = bank->accdb_fork_id.val;
     862           0 :     ev->vote_stakes_fork_id         = bank->vote_stakes_fork_id;
     863           0 :     ev->stake_rewards_fork_id       = bank->stake_rewards_fork_id;
     864           0 :     ev->epoch_credits_fork_id       = bank->epoch_credits_fork_id;
     865           0 :     ev->stake_delegations_fork_id   = bank->stake_delegations_fork_id;
     866           0 :     ev->cost_tracker_pool_idx       = fd_ulong_if( bank->cost_tracker_fork_id==USHORT_MAX, ULONG_MAX, bank->cost_tracker_fork_id );
     867           0 :   }
     868             : 
     869           0 :   block_completed_event_fill_reception( ctx, ev, bank->idx );
     870           0 : }
     871             : 
     872             : static int
     873           0 : pack_end_reason_to_event( int reason ) {
     874           0 :   switch( reason ) {
     875           0 :     case FD_PACK_END_SLOT_REASON_TIME:       return FD_EVENT_BLOCK_COMPLETED_PACK_END_REASON_TIME;
     876           0 :     case FD_PACK_END_SLOT_REASON_MICROBLOCK: return FD_EVENT_BLOCK_COMPLETED_PACK_END_REASON_MICROBLOCK_LIMIT;
     877           0 :     case FD_PACK_END_SLOT_REASON_ABANDONED:  return FD_EVENT_BLOCK_COMPLETED_PACK_END_REASON_ABANDONED;
     878           0 :     default:                                 FD_LOG_CRIT(( "unmapped pack end reason %d", reason ));
     879           0 :   }
     880           0 : }
     881             : 
     882             : static void
     883             : block_completed_event_fill_leader( fd_replay_tile_t *           ctx,
     884             :                                    fd_event_block_completed_t * ev,
     885           0 :                                    fd_bank_t *                  bank ) {
     886           0 :   FD_TEST( ctx->leader_stats.slot==bank->f.slot );
     887             : 
     888           0 :   ev->became_leader_time     = (ulong)ctx->leader_stats.became_leader_nanos;
     889           0 :   ev->leader_slot_start_time = (ulong)ctx->leader_stats.leader_slot_start_nanos;
     890             : 
     891           0 :   ev->first_fec_set_received_time = (ulong)ctx->leader_stats.first_fec_returned_nanos;
     892           0 :   ev->recovered_shred_count = 0UL;
     893             : 
     894           0 :   ev->microblock_count = ctx->leader_stats.microblock_count;
     895           0 :   ev->pack_block_cost  = ctx->leader_stats.pack_block_cost;
     896           0 :   ev->pack_vote_cost   = 0UL;
     897           0 :   ev->pack_data_bytes  = ctx->leader_stats.pack_data_bytes;
     898           0 :   ev->bundle_txn_count = ctx->leader_stats.bundle_txn_count;
     899           0 :   ev->pack_start_time  = (ulong)ctx->leader_stats.pack_start_nanos;
     900           0 :   ev->pack_end_time    = (ulong)ctx->leader_stats.pack_end_nanos;
     901           0 :   ev->pack_end_reason  = pack_end_reason_to_event( ctx->leader_stats.pack_end_reason );
     902           0 : }
     903             : 
     904             : static void
     905             : block_completed_event_fill_leader_txn_timing( fd_replay_tile_t *           ctx,
     906             :                                               fd_event_block_completed_t * ev,
     907           0 :                                               fd_bank_t *                  bank ) {
     908           0 :   ulong tidx = ctx->leader_stats.timing_table_idx;
     909           0 :   if( FD_UNLIKELY( !ctx->leader_txn_timing || tidx>=FD_LEADER_TXN_TIMING_TABLE_CNT ) ) return;
     910           0 :   fd_leader_txn_timing_table_t const * table = fd_leader_txn_timing_table_const( ctx->leader_txn_timing, tidx, ctx->max_txn_per_slot );
     911           0 :   if( FD_UNLIKELY( table->slot!=bank->f.slot ) ) return;
     912             : 
     913           0 :   ulong cnt = fd_ulong_min( table->cnt, FD_EVENT_BLOCK_COMPLETED_TXN_TIMING_MAX );
     914           0 :   long  first_dispatch = LONG_MAX;
     915           0 :   for( ulong i=0UL; i<cnt; i++ ) {
     916           0 :     fd_leader_txn_timing_rec_t const * rec = &table->rec[ i ];
     917           0 :     first_dispatch = fd_long_min( first_dispatch, rec->dispatched_ticks );
     918           0 :     ev->txn_timing[ i ] = (fd_event_block_completed_txn_timing_t){
     919           0 :       .received_time   = (ulong)rec->received_ns,
     920           0 :       .dispatched_time = rec->dispatched_ticks==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, rec->dispatched_ticks ),
     921           0 :       .replayed_time   = rec->replayed_ticks  ==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, rec->replayed_ticks   ),
     922           0 :       .poh_mixed_time  = rec->poh_mixed_ticks ==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, rec->poh_mixed_ticks  ),
     923           0 :     };
     924           0 :   }
     925           0 :   ev->txn_timing_cnt = cnt;
     926             : 
     927           0 :   ev->first_transaction_scheduled_time = first_dispatch==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, first_dispatch );
     928           0 : }
     929             : 
     930             : static void
     931             : report_block_completed( fd_replay_tile_t *                 ctx,
     932             :                         fd_bank_t *                        bank,
     933             :                         int                                is_leader,
     934           0 :                         fd_replay_slot_completed_t const * slot_info ) {
     935           0 :   if( FD_LIKELY( !fd_event_tl ) ) return;
     936             : 
     937           0 :   fd_event_block_completed_t * ev = ctx->block_completed_event;
     938           0 :   memset( ev, 0, FD_EVENT_BLOCK_COMPLETED_PREFIX_SZ );
     939             : 
     940           0 :   block_completed_event_fill_bank( ctx, ev, bank );
     941             : 
     942           0 :   if( FD_LIKELY( slot_info->cost_tracker.block_cost_limit!=ULONG_MAX ) ) {
     943           0 :     ev->cost_tracker_block_cost                   = slot_info->cost_tracker.block_cost;
     944           0 :     ev->cost_tracker_allocated_accounts_data_size = slot_info->cost_tracker.allocated_accounts_data_size;
     945           0 :     ev->cost_tracker_block_cost_limit             = slot_info->cost_tracker.block_cost_limit;
     946           0 :     ev->cost_tracker_account_cost_limit           = slot_info->cost_tracker.account_cost_limit;
     947           0 :   }
     948           0 :   ev->cost_tracker_pool_idx = slot_info->cost_tracker.pool_idx;
     949             : 
     950           0 :   ev->root_slot    = ctx->consensus_root_slot;
     951           0 :   ev->storage_slot = ctx->published_root_slot;
     952           0 :   ev->caught_up            = !!ctx->caught_up;
     953           0 :   ev->turbine_slot         = ctx->catch_up_max_fec_slot==ULONG_MAX ? 0UL : ctx->catch_up_max_fec_slot;
     954           0 :   ev->fork_width           = ctx->banks->curr_fork_width;
     955           0 :   ev->snapshot_in_progress = !!ctx->snapmk.active;
     956           0 :   ev->live_bank_count      = fd_banks_pool_used_cnt( ctx->banks );
     957           0 :   ev->is_leader    = is_leader;
     958             : 
     959           0 :   ev->pack_end_reason = FD_EVENT_BLOCK_COMPLETED_PACK_END_REASON_NOT_LEADER;
     960           0 :   if( FD_UNLIKELY( is_leader ) ) block_completed_event_fill_leader( ctx, ev, bank );
     961             : 
     962           0 :   ev->dead             = 0;
     963           0 :   ev->dead_reason      = FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD;
     964           0 :   ev->dead_time        = 0UL;
     965           0 :   ev->abandoned        = 0;
     966           0 :   ev->abandoned_reason = FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED;
     967           0 :   ev->abandoned_time   = 0UL;
     968             : 
     969           0 :   if( FD_UNLIKELY( is_leader ) ) {
     970           0 :     block_completed_event_fill_leader_txn_timing( ctx, ev, bank );
     971           0 :   } else {
     972           0 :     ulong tslot = ctx->timing_slot_of_bank[ bank->idx ];
     973           0 :     if( FD_LIKELY( tslot!=fd_timing_slot_pool_idx_null( ctx->timing_slot_pool ) ) ) {
     974           0 :       fd_replay_txn_timing_slot_t const * slot = fd_timing_slot_pool_ele( ctx->timing_slot_pool, tslot );
     975           0 :       fd_replay_txn_timing_t const *      rec  = ctx->timing_rec + tslot*ctx->max_txn_per_slot;
     976           0 :       ulong cnt = fd_ulong_min( slot->cnt, FD_EVENT_BLOCK_COMPLETED_TXN_TIMING_MAX );
     977           0 :       for( ulong i=0UL; i<cnt; i++ ) {
     978           0 :         fd_replay_txn_timing_t const * t = &rec[ i ];
     979           0 :         ev->txn_timing[ i ] = (fd_event_block_completed_txn_timing_t){
     980           0 :           .received_time             = (ulong)t->received_ns, /* already wallclock */
     981           0 :           .parsed_time               = t->parsed_ticks        ==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, t->parsed_ticks         ),
     982           0 :           .sigverify_dispatched_time = t->sigverify_disp_ticks==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, t->sigverify_disp_ticks ),
     983           0 :           .sigverify_done_time       = t->sigverify_done_ticks==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, t->sigverify_done_ticks ),
     984           0 :           .dispatched_time           = t->exec_disp_ticks     ==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, t->exec_disp_ticks      ),
     985           0 :           .replayed_time             = t->exec_done_ticks     ==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, t->exec_done_ticks      ),
     986           0 :         };
     987           0 :       }
     988           0 :       ev->txn_timing_cnt = cnt;
     989           0 :     }
     990           0 :   }
     991             : 
     992           0 :   if( FD_UNLIKELY( !ev->first_transaction_scheduled_time ) ) ev->first_transaction_scheduled_time = ev->last_transaction_finished_time;
     993             : 
     994           0 :   fd_event_report_block_completed( ev );
     995           0 : }
     996             : 
     997             : static void
     998             : update_metric_identity_balance( fd_replay_tile_t *  ctx,
     999             :                                 fd_accdb_fork_id_t  fork_id,
    1000           0 :                                 fd_pubkey_t const * identity ) {
    1001           0 :   ulong identity_balance = fd_accdb_lamports( ctx->accdb, fork_id, identity->uc );
    1002           0 :   FD_MGAUGE_SET( REPLAY, IDENTITY_BALANCE_LAMPORTS, identity_balance );
    1003           0 : }
    1004             : 
    1005             : static void
    1006             : update_metric_vote_account( fd_replay_tile_t *  ctx,
    1007             :                             fd_bank_t const *   bank,
    1008             :                             fd_accdb_fork_id_t  fork_id,
    1009           0 :                             fd_pubkey_t const * vote_key ) {
    1010           0 :   ulong epoch_credits = 0UL;
    1011           0 :   fd_acc_t ro = fd_accdb_read_one( ctx->accdb, fork_id, vote_key->uc );
    1012             :   /* Unstaked voters are never admitted; don't flag them.  Once set,
    1013             :      sticky until an epoch boundary actually admits the account. */
    1014           0 :   int admitted = fd_vote_stakes_query_t_1( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, vote_key, NULL );
    1015             :   /* SIMD-0123: delegator rewards do not count towards the balance
    1016             :      https://github.com/anza-xyz/agave/blob/v4.4/vote/src/vote_account.rs#L225-L234 */
    1017           0 :   ulong available_balance = ro.lamports;
    1018           0 :   if( FD_FEATURE_ACTIVE_BANK( bank, block_revenue_sharing ) ) {
    1019           0 :     ulong pending_delegator_rewards = 0UL;
    1020           0 :     if( FD_LIKELY( !fd_vote_account_pending_delegator_rewards( ro.data, ro.data_len, &pending_delegator_rewards ) ) ) {
    1021           0 :       available_balance = fd_ulong_sat_sub( ro.lamports, pending_delegator_rewards );
    1022           0 :     }
    1023           0 :   }
    1024           0 :   if( FD_UNLIKELY( !fd_stakes_vote_account_is_admissible( bank, available_balance, ro.owner, ro.data, ro.data_len ) ) ) {
    1025           0 :     ctx->vote_account_inadmissible = ctx->vote_account_staked || admitted;
    1026           0 :   } else if( FD_UNLIKELY( ctx->vote_account_inadmissible ) ) {
    1027           0 :     ctx->vote_account_inadmissible = ctx->vote_account_staked && !admitted;
    1028           0 :   }
    1029             : 
    1030           0 :   if( FD_LIKELY( ro.lamports ) ) {
    1031           0 :     fd_vote_state_versioned_t vsv[1];
    1032           0 :     if( FD_LIKELY( fd_vote_state_versioned_deserialize( vsv, ro.data, ro.data_len ) ) ) {
    1033           0 :       fd_vote_epoch_credits_t const * last_ec = fd_vsv_get_last_epoch_credits( vsv );
    1034           0 :       if( last_ec && last_ec->epoch==bank->f.epoch ) {
    1035           0 :         epoch_credits = fd_ulong_sat_sub( last_ec->credits, last_ec->prev_credits ); /* credits is the lifetime total */
    1036           0 :       }
    1037           0 :     }
    1038           0 :   }
    1039           0 :   fd_accdb_unread_one( ctx->accdb, &ro );
    1040             : 
    1041           0 :   FD_MGAUGE_SET( REPLAY, EPOCH_CREDITS,                epoch_credits                         );
    1042           0 :   FD_MGAUGE_SET( REPLAY, IS_VOTE_ACCOUNT_INADMISSIBLE, (ulong)ctx->vote_account_inadmissible );
    1043           0 : }
    1044             : 
    1045             : static void
    1046             : update_metric_active_stake( fd_bank_t const *   bank,
    1047           0 :                             fd_pubkey_t const * vote_key ) {
    1048           0 :   ulong my_active_stake = 0UL;
    1049             : 
    1050           0 :   fd_vote_stakes_ele_t ele[1];
    1051           0 :   if( fd_vote_stakes_query_t_1( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, vote_key, ele ) ) my_active_stake = ele->stake;
    1052             : 
    1053           0 :   FD_MGAUGE_SET( REPLAY, ACTIVE_STAKE_LAMPORTS, my_active_stake );
    1054           0 : }
    1055             : 
    1056             : static void
    1057             : update_metric_balances( fd_replay_tile_t * ctx,
    1058           0 :                         fd_bank_t *        bank ) {
    1059           0 :   fd_accdb_fork_id_t fork_id = bank->accdb_fork_id;
    1060           0 :   fd_node_info_t node_info[1]; fd_node_info_read( node_info, ctx->node_info );
    1061           0 :   if( !fd_pubkey_check_zero( &node_info->identity ) ) {
    1062           0 :     update_metric_identity_balance( ctx, fork_id, &node_info->identity );
    1063           0 :   }
    1064             : 
    1065           0 :   if( !fd_pubkey_check_zero( &node_info->vote_account ) ) {
    1066           0 :     update_metric_vote_account( ctx, bank, fork_id, &node_info->vote_account );
    1067           0 :     update_metric_active_stake(      bank,          &node_info->vote_account );
    1068           0 :   }
    1069           0 :   FD_MGAUGE_SET( REPLAY, CLUSTER_ACTIVE_STAKE_LAMPORTS, bank->f.total_epoch_stake );
    1070           0 : }
    1071             : 
    1072             : /* try_advance_root attempts to advance root to finalized_block_id.  For
    1073             :    something to be rooted, it must be BOTH finalized AND replayed.  This
    1074             :    walks up the bank lineage in the block id map to find and set root.
    1075             : 
    1076             :    ON FINALIZED
    1077             : 
    1078             :    If finalized is ahead of replayed => root does not advance
    1079             :    If replayed is ahead of finalized => root advances
    1080             : 
    1081             :    ON REPLAYED
    1082             : 
    1083             :    If finalized is ahead of replayed => root advances
    1084             :    If replayed is ahead of finalized => root does not advance
    1085             : 
    1086             :    consensus_root moves straight to the finalized block, and
    1087             :    try_notify_consensus_root hands it off as REPLAY_SIG_ROOT_ADVANCED. */
    1088             : 
    1089             : static void
    1090             : try_advance_root( fd_replay_tile_t * ctx,
    1091           0 :                   fd_block_id_t      finalized_block_id ) {
    1092             : 
    1093           0 :   if( FD_UNLIKELY( finalized_block_id.slot<=ctx->consensus_root_slot ) ) return;
    1094             : 
    1095           0 :   if( FD_UNLIKELY( fd_bhm_map_ele_query( ctx->bhm_map, &finalized_block_id, NULL, ctx->bhm_pool ) ) ) {
    1096           0 :     FD_BASE58_ENCODE_32_BYTES( finalized_block_id.hash.uc, block_id_b58 );
    1097           0 :     FD_LOG_ERR(( "HARD FORK DETECTED for slot %lu block ID `%s`", finalized_block_id.slot, block_id_b58 ));
    1098           0 :   }
    1099             : 
    1100           0 :   fd_block_id_t ancestor_block_id = finalized_block_id;
    1101           0 :   fd_bank_t *   finalized_bank    = NULL;
    1102             : 
    1103           0 :   while( FD_LIKELY( ancestor_block_id.slot>ctx->consensus_root_slot ) ) {
    1104           0 :     fd_block_id_ele_t * ele  = fd_block_id_map_ele_query( ctx->block_id_map, &ancestor_block_id, NULL, ctx->block_id_arr );
    1105           0 :     fd_bank_t *         bank = ele ? fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, ele ) ) : NULL;
    1106           0 :     if( FD_UNLIKELY( !bank || bank->bank_seq!=ele->bank_seq || bank->state!=FD_BANK_STATE_FROZEN || bank->parent_idx==ULONG_MAX ) ) break; /* not replayed */
    1107           0 :     if( !finalized_bank ) finalized_bank = bank;
    1108           0 :     ancestor_block_id = ctx->block_id_arr[ bank->parent_idx ].block_info;
    1109           0 :   }
    1110             : 
    1111           0 :   if( FD_LIKELY( ancestor_block_id.slot==ctx->consensus_root_slot && !memcmp( &ancestor_block_id.hash, ctx->consensus_root.uc, sizeof(fd_hash_t) ) ) ) {
    1112           0 :     ctx->consensus_root_slot = finalized_block_id.slot;
    1113           0 :     memcpy( ctx->consensus_root.uc, &finalized_block_id.hash, sizeof(fd_hash_t) );
    1114           0 :     if( FD_UNLIKELY( ctx->next_leader_slot!=ULONG_MAX && finalized_block_id.slot>ctx->votor_leader->parent_slot ) ) ctx->next_leader_slot = ULONG_MAX;
    1115           0 :     update_metric_balances( ctx, finalized_bank ); /* Alpenglow has no optimistic confirmation */
    1116           0 :     return;
    1117           0 :   }
    1118             : 
    1119             :     /* When a block id is finalized ahead of replay, we need to cache it
    1120             :        and process it when replay catches up.  Certificates can arrive
    1121             :        out of order, so lo keeps the oldest and hi the newest. */
    1122             : 
    1123           0 :   if( FD_UNLIKELY( finalized_block_id.slot<ctx->finalized_block_id_lo.slot || ctx->finalized_block_id_lo.slot<=ctx->consensus_root_slot ) ) {
    1124           0 :     if( FD_UNLIKELY( ctx->finalized_block_id_lo.slot>ctx->finalized_block_id_hi.slot ) ) fd_swap( ctx->finalized_block_id_lo, ctx->finalized_block_id_hi );
    1125           0 :     ctx->finalized_block_id_lo = finalized_block_id;
    1126           0 :   } else if( FD_LIKELY( finalized_block_id.slot>ctx->finalized_block_id_hi.slot ) ) {
    1127           0 :     ctx->finalized_block_id_hi = finalized_block_id;
    1128           0 :   }
    1129           0 : }
    1130             : 
    1131             : static void
    1132             : publish_slot_completed( fd_replay_tile_t *        ctx,
    1133             :                         fd_stem_context_t *       stem,
    1134             :                         fd_bank_t *               bank,
    1135             :                         int                       is_initial,
    1136             :                         int                       is_leader,
    1137             :                         ulong                     execution_fees_pre_settle,
    1138             :                         ulong                     priority_fees_pre_settle,
    1139           0 :                         fd_block_footer_t const * footer ) {
    1140             : 
    1141           0 :   ulong slot = bank->f.slot;
    1142             : 
    1143           0 :   if( FD_UNLIKELY( slot>=ctx->reset_slot ) ) {
    1144           0 :     ctx->reset_slot = slot;
    1145             :     /* Do not touch next_leader_slot: try_fini_leader uses ULONG_MAX
    1146             :        to continue the leader window. */
    1147           0 :     refresh_epoch_info( ctx, bank, slot );
    1148           0 :   }
    1149             : 
    1150           0 :   if( FD_UNLIKELY( is_initial ) ) bank->block_completed_nanos = fd_clock_tile_now( ctx->clock );
    1151             : 
    1152           0 :   fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ bank->idx ];
    1153             : 
    1154             :   /* HACKY: hacky way of checking if we should send a null parent block
    1155             :      id */
    1156           0 :   fd_hash_t block_id        = block_id_ele->dmr;
    1157           0 :   fd_hash_t parent_block_id = {0};
    1158           0 :   if( FD_LIKELY( !is_initial ) ) {
    1159           0 :     fd_block_id_ele_t const * parent_ele = &ctx->block_id_arr[ bank->parent_idx ];
    1160           0 :     parent_block_id = parent_ele->dmr;
    1161           0 :   }
    1162             : 
    1163           0 :   fd_hash_t const * bank_hash  = &bank->f.bank_hash;
    1164           0 :   fd_hash_t const * block_hash = fd_blockhashes_peek_last_hash( &bank->f.block_hash_queue );
    1165           0 :   FD_TEST( block_hash );
    1166             : 
    1167           0 :   if( FD_LIKELY( !is_initial ) ) fd_txncache_finalize_fork( ctx->txncache, bank->txncache_fork_id, 0UL, block_hash->uc );
    1168             : 
    1169           0 :   fd_epoch_schedule_t const * epoch_schedule = &bank->f.epoch_schedule;
    1170           0 :   ulong slot_idx;
    1171           0 :   ulong epoch = fd_slot_to_epoch( epoch_schedule, slot, &slot_idx );
    1172             : 
    1173           0 :   ctx->metrics.slots_total++;
    1174           0 :   ctx->metrics.transactions_total = bank->f.parent_txn_count + bank->f.txn_count;
    1175             : 
    1176             :   /* Caught up once replay completes a slot within a few slots of the
    1177             :      cluster tip.  Require the tip to have advanced a few times first so
    1178             :      a brief view of the tip right after boot does not count. */
    1179           0 :   if( FD_UNLIKELY( !ctx->caught_up && !is_initial &&
    1180           0 :                    ctx->catch_up_tip_advance_cnt>=12UL &&
    1181           0 :                    ctx->catch_up_max_fec_slot<slot+3UL ) ) {
    1182           0 :     ctx->caught_up = 1;
    1183           0 :     FD_MGAUGE_SET( REPLAY, IS_CAUGHT_UP, 1UL );
    1184           0 :     metrics_trigger( ctx, FD_METRICS_SAMPLE_REASON_CAUGHT_UP, slot );
    1185           0 :     double boot_secs = (double)(fd_log_wallclock()-ctx->boot_timestamp_nanos)/1e9;
    1186           0 :     FD_LOG_NOTICE(( "caught up to cluster at slot %s%lu%s %s(%.1f seconds since boot)%s",
    1187           0 :                     fd_log_style_bold(), slot, fd_log_style_normal(),
    1188           0 :                     fd_log_style_dim(), boot_secs, fd_log_style_normal() ));
    1189           0 :   }
    1190             : 
    1191           0 :   fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
    1192           0 :   slot_info->slot                  = slot;
    1193           0 :   slot_info->root_slot             = ctx->consensus_root_slot;
    1194           0 :   slot_info->storage_slot          = ctx->published_root_slot;
    1195           0 :   slot_info->epoch                 = epoch;
    1196           0 :   slot_info->slot_in_epoch         = slot_idx;
    1197           0 :   slot_info->slots_per_epoch       = fd_epoch_slot_cnt( epoch_schedule, epoch );
    1198           0 :   slot_info->block_height          = bank->f.block_height;
    1199           0 :   slot_info->parent_slot           = bank->f.parent_slot;
    1200           0 :   slot_info->block_id              = block_id;
    1201           0 :   slot_info->parent_block_id       = parent_block_id;
    1202           0 :   slot_info->bank_hash             = *bank_hash;
    1203           0 :   slot_info->block_hash            = *block_hash;
    1204           0 :   slot_info->transaction_count     = bank->f.parent_txn_count + bank->f.txn_count;
    1205             : 
    1206           0 :   fd_inflation_t inflation = bank->f.inflation;
    1207           0 :   slot_info->inflation.foundation      = inflation.foundation;
    1208           0 :   slot_info->inflation.foundation_term = inflation.foundation_term;
    1209           0 :   slot_info->inflation.terminal        = inflation.terminal;
    1210           0 :   slot_info->inflation.initial         = inflation.initial;
    1211           0 :   slot_info->inflation.taper           = inflation.taper;
    1212             : 
    1213           0 :   fd_rent_t rent = bank->f.rent;
    1214           0 :   slot_info->rent.burn_percent            = rent.burn_percent;
    1215           0 :   slot_info->rent.lamports_per_uint8_year = rent.lamports_per_uint8_year;
    1216           0 :   slot_info->rent.exemption_threshold     = rent.exemption_threshold;
    1217             : 
    1218           0 :   slot_info->first_fec_set_received_nanos      = bank->first_fec_set_received_nanos;
    1219           0 :   slot_info->preparation_begin_nanos           = bank->preparation_begin_nanos;
    1220           0 :   slot_info->first_transaction_scheduled_nanos = bank->first_transaction_scheduled_nanos;
    1221           0 :   slot_info->last_transaction_finished_nanos   = bank->last_transaction_finished_nanos;
    1222           0 :   slot_info->completion_time_nanos             = fd_clock_tile_now( ctx->clock );
    1223           0 :   slot_info->footer                            = footer ? *footer : (fd_block_footer_t){0};
    1224           0 :   if( !slot_info->first_transaction_scheduled_nanos ) { /* edge case: empty slot */
    1225           0 :     slot_info->first_transaction_scheduled_nanos = slot_info->last_transaction_finished_nanos;
    1226           0 :   }
    1227             : 
    1228             :   /* refcnt should be incremented by 1 for each consumer that uses
    1229             :      `bank_idx`.  Each consumer should decrement the bank's refcnt once
    1230             :      they are done using the bank. */
    1231           0 :   if( FD_LIKELY( ctx->rpc_enabled ) ) bank->refcnt++; /* rpc tile */
    1232           0 :   slot_info->bank_idx = bank->idx;
    1233           0 :   slot_info->bank_seq = bank->bank_seq;
    1234           0 :   slot_info->accdb_fork_id = bank->accdb_fork_id;
    1235             : 
    1236           0 :   fd_bank_t * parent_bank = fd_banks_get_parent( ctx->banks, bank );
    1237           0 :   slot_info->parent_bank_idx = parent_bank ? parent_bank->idx      : ULONG_MAX;
    1238           0 :   slot_info->parent_bank_seq = parent_bank ? parent_bank->bank_seq : ULONG_MAX;
    1239           0 :   if( FD_LIKELY( parent_bank ) ) {
    1240           0 :     ulong total_txn_cnt          = bank->f.txn_count;
    1241           0 :     ulong nonvote_txn_cnt        = bank->f.nonvote_txn_count;
    1242           0 :     ulong failed_txn_cnt         = bank->f.failed_txn_count;
    1243           0 :     ulong nonvote_failed_txn_cnt = bank->f.nonvote_failed_txn_count;
    1244             : 
    1245           0 :     slot_info->nonvote_success = nonvote_txn_cnt - nonvote_failed_txn_cnt;
    1246           0 :     slot_info->nonvote_failed  = nonvote_failed_txn_cnt;
    1247           0 :     slot_info->vote_failed     = failed_txn_cnt - nonvote_failed_txn_cnt;
    1248           0 :     slot_info->vote_success    = total_txn_cnt - nonvote_txn_cnt - slot_info->vote_failed;
    1249           0 :   } else {
    1250           0 :     slot_info->vote_failed     = ULONG_MAX;
    1251           0 :     slot_info->vote_success    = ULONG_MAX;
    1252           0 :     slot_info->nonvote_success = ULONG_MAX;
    1253           0 :     slot_info->nonvote_failed  = ULONG_MAX;
    1254           0 :   }
    1255             : 
    1256           0 :   slot_info->is_leader = is_leader;
    1257           0 :   slot_info->transaction_fee = execution_fees_pre_settle;
    1258           0 :   slot_info->transaction_fee -= (slot_info->transaction_fee>>1); /* burn */
    1259           0 :   slot_info->priority_fee = priority_fees_pre_settle;
    1260           0 :   slot_info->tips = bank->f.tips;
    1261           0 :   slot_info->shred_cnt = bank->f.shred_cnt;
    1262             : 
    1263           0 :   slot_info->voted = replay_reward_cert_voted( ctx, bank, &slot_info->voted_rank, &slot_info->vote_count );
    1264             : 
    1265           0 :   slot_info->vote_balance    = ULONG_MAX;
    1266           0 :   slot_info->vote_commission = USHORT_MAX;
    1267           0 :   if( FD_UNLIKELY( ctx->has_vote_account && slot%512UL==0UL ) ) {
    1268           0 :     fd_acc_t acc = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, ctx->vote_account->uc );
    1269           0 :     if( FD_LIKELY( acc.lamports ) ) {
    1270           0 :       slot_info->vote_balance = acc.lamports;
    1271             : 
    1272           0 :       ushort bps;
    1273           0 :       if( FD_LIKELY( !fd_vote_account_commission_bps( acc.data, acc.data_len, FD_FEATURE_ACTIVE_BANK( bank, commission_rate_in_basis_points ), &bps ) ) ) slot_info->vote_commission = bps;
    1274           0 :     }
    1275           0 :     fd_accdb_unread_one( ctx->accdb, &acc );
    1276           0 :   }
    1277             : 
    1278           0 :   publish_replay_out( ctx, stem, REPLAY_SIG_SLOT_COMPLETED, sizeof(fd_replay_slot_completed_t) );
    1279             : 
    1280           0 :   fd_replay_next_leader_t * next_leader = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
    1281           0 :   next_leader->slot             = ctx->reset_slot;
    1282           0 :   next_leader->next_leader_slot = ctx->leader_bank ? ctx->leader_bank->f.slot :
    1283           0 :                                   query_next_leader_slot( ctx, fd_ulong_max( ctx->reset_slot+1UL, fd_ulong_if( ctx->highwater_leader_slot==ULONG_MAX, 0UL, ctx->highwater_leader_slot+1UL ) ) );
    1284           0 :   publish_replay_out( ctx, stem, REPLAY_SIG_NEXT_LEADER, sizeof(fd_replay_next_leader_t) );
    1285             : 
    1286             :   /* Skip the telemetry event for the initial boot block (snapshot /
    1287             :      genesis): it was not replayed. */
    1288           0 :   if( FD_LIKELY( !is_initial ) ) report_block_completed( ctx, bank, is_leader, slot_info );
    1289           0 :   timing_slot_release( ctx, bank->idx );
    1290             : 
    1291           0 :   if( FD_UNLIKELY( slot==ctx->finalized_block_id_lo.slot && !memcmp( block_id.uc, &ctx->finalized_block_id_lo.hash, sizeof(fd_hash_t) ) ) ) { /* finalized ahead of replay, replay caught up */
    1292           0 :     try_advance_root( ctx, ctx->finalized_block_id_lo );
    1293           0 :     ctx->finalized_block_id_lo = ctx->finalized_block_id_hi; /* hi becomes lo once lo is reached */
    1294           0 :   }
    1295           0 : }
    1296             : 
    1297             : static void
    1298             : report_block_incomplete( fd_replay_tile_t * ctx,
    1299             :                          ulong              slot,
    1300             :                          fd_hash_t const *  block_id,
    1301             :                          fd_bank_t *        bank,
    1302             :                          int                dead_reason,
    1303           0 :                          int                abandoned_reason ) {
    1304           0 :   if( FD_LIKELY( !fd_event_tl ) ) return;
    1305             : 
    1306           0 :   fd_event_block_completed_t * ev = ctx->block_completed_event;
    1307           0 :   memset( ev, 0, FD_EVENT_BLOCK_COMPLETED_PREFIX_SZ );
    1308           0 :   if( FD_LIKELY( bank ) ) {
    1309           0 :     block_completed_event_fill_bank( ctx, ev, bank );
    1310           0 :     ev->is_leader = !!bank->is_leader;
    1311           0 :   } else {
    1312           0 :     ev->slot = slot;
    1313           0 :     if( FD_LIKELY( ctx->notified_root_bank ) ) ev->epoch = fd_slot_to_epoch( &ctx->notified_root_bank->f.epoch_schedule, slot, NULL );
    1314           0 :     fd_memcpy( ev->block_id, block_id->uc, sizeof(ev->block_id) );
    1315           0 :     block_completed_event_fill_reception( ctx, ev, ULONG_MAX );
    1316           0 :   }
    1317           0 :   ev->root_slot    = ctx->consensus_root_slot;
    1318           0 :   ev->storage_slot = ctx->published_root_slot;
    1319           0 :   ev->caught_up            = !!ctx->caught_up;
    1320           0 :   ev->turbine_slot         = ctx->catch_up_max_fec_slot==ULONG_MAX ? 0UL : ctx->catch_up_max_fec_slot;
    1321           0 :   ev->fork_width           = ctx->banks->curr_fork_width;
    1322           0 :   ev->snapshot_in_progress = !!ctx->snapmk.active;
    1323           0 :   ev->live_bank_count      = fd_banks_pool_used_cnt( ctx->banks );
    1324             : 
    1325           0 :   int  dead      = dead_reason     !=FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD;
    1326           0 :   int  abandoned = abandoned_reason!=FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED;
    1327           0 :   long now       = fd_clock_tile_now( ctx->clock );
    1328             : 
    1329           0 :   ev->dead             = dead;
    1330           0 :   ev->dead_reason      = dead_reason;
    1331           0 :   ev->dead_time        = dead      ? (ulong)now : 0UL;
    1332           0 :   ev->abandoned        = abandoned;
    1333           0 :   ev->abandoned_reason = abandoned_reason;
    1334           0 :   ev->abandoned_time   = abandoned ? (ulong)now : 0UL;
    1335             : 
    1336           0 :   ev->pack_end_reason = FD_EVENT_BLOCK_COMPLETED_PACK_END_REASON_NOT_LEADER;
    1337           0 :   if( FD_UNLIKELY( bank && bank->is_leader ) ) {
    1338           0 :     block_completed_event_fill_leader( ctx, ev, bank );
    1339           0 :     block_completed_event_fill_leader_txn_timing( ctx, ev, bank );
    1340           0 :   }
    1341             : 
    1342           0 :   fd_event_report_block_completed( ev );
    1343           0 : }
    1344             : 
    1345             : static void
    1346             : publish_slot_dead( fd_replay_tile_t *        ctx,
    1347             :                    fd_stem_context_t *       stem,
    1348             :                    ulong                     slot,
    1349             :                    fd_hash_t const *         block_id,
    1350             :                    uint                      latest_fec_idx,
    1351           0 :                    fd_block_footer_t const * footer ) {
    1352           0 :   fd_replay_slot_dead_t * slot_dead = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
    1353           0 :   slot_dead->slot                   = slot;
    1354           0 :   slot_dead->block_id               = *block_id;
    1355           0 :   slot_dead->latest_fec_idx         = latest_fec_idx;
    1356           0 :   slot_dead->footer                 = footer ? *footer : (fd_block_footer_t){0};
    1357           0 :   publish_replay_out( ctx, stem, REPLAY_SIG_SLOT_DEAD, sizeof(fd_replay_slot_dead_t) );
    1358           0 : }
    1359             : 
    1360             : static void
    1361             : publish_txn_executed( fd_replay_tile_t *  ctx,
    1362             :                       fd_stem_context_t * stem,
    1363             :                       ulong               bank_idx,
    1364           0 :                       ulong               txn_idx ) {
    1365           0 :   fd_sched_txn_info_t * txn_info = fd_sched_get_txn_info( ctx->sched, txn_idx );
    1366             : 
    1367           0 :   FD_TEST( txn_info->index_in_slot<ctx->max_txn_per_slot );
    1368           0 :   ulong tslot = ctx->timing_slot_of_bank[ bank_idx ];
    1369           0 :   if( FD_LIKELY( tslot!=fd_timing_slot_pool_idx_null( ctx->timing_slot_pool ) ) ) {
    1370           0 :     fd_replay_txn_timing_slot_t * slot = fd_timing_slot_pool_ele( ctx->timing_slot_pool, tslot );
    1371           0 :     fd_replay_txn_timing_t * t = ctx->timing_rec + tslot*ctx->max_txn_per_slot + txn_info->index_in_slot;
    1372           0 :     t->received_ns          = txn_info->received_ns;
    1373           0 :     t->parsed_ticks         = txn_info->tick_parsed;
    1374           0 :     t->sigverify_disp_ticks = txn_info->tick_sigverify_disp;
    1375           0 :     t->sigverify_done_ticks = txn_info->tick_sigverify_done;
    1376           0 :     t->exec_disp_ticks      = txn_info->tick_exec_disp;
    1377           0 :     t->exec_done_ticks      = txn_info->tick_exec_done;
    1378           0 :     slot->cnt = fd_ulong_max( slot->cnt, txn_info->index_in_slot+1UL );
    1379           0 :   }
    1380             : 
    1381           0 :   fd_replay_txn_executed_t * txn_executed = fd_type_pun( fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk ) );
    1382           0 :   *txn_executed->txn = *fd_sched_get_txn( ctx->sched, txn_idx );
    1383           0 :   txn_executed->txn_err = txn_info->txn_err;
    1384           0 :   txn_executed->is_committable = !!(txn_info->flags&FD_SCHED_TXN_IS_COMMITTABLE);
    1385           0 :   txn_executed->is_fees_only = !!(txn_info->flags&FD_SCHED_TXN_IS_FEES_ONLY);
    1386           0 :   txn_executed->is_noop = !!(txn_info->flags&FD_SCHED_TXN_IS_NOOP);
    1387           0 :   txn_executed->tick_parsed = txn_info->tick_parsed;
    1388           0 :   txn_executed->tick_sigverify_disp = txn_info->tick_sigverify_disp;
    1389           0 :   txn_executed->tick_sigverify_done = txn_info->tick_sigverify_done;
    1390           0 :   txn_executed->tick_exec_disp = txn_info->tick_exec_disp;
    1391           0 :   txn_executed->tick_exec_done = txn_info->tick_exec_done;
    1392             : 
    1393           0 :   txn_executed->tick_load_start = txn_info->tick_load_start;
    1394           0 :   txn_executed->tick_check_start = txn_info->tick_check_start;
    1395           0 :   txn_executed->tick_exec_start = txn_info->tick_exec_start;
    1396           0 :   txn_executed->tick_commit_start = txn_info->tick_commit_start;
    1397           0 :   txn_executed->tick_commit_end = txn_info->tick_commit_end;
    1398             : 
    1399           0 :   txn_executed->slot = txn_info->slot;
    1400           0 :   txn_executed->bank_seq = txn_info->bank_seq;
    1401           0 :   txn_executed->index_in_slot = txn_info->index_in_slot;
    1402           0 :   txn_executed->exec_tile_idx = txn_info->exec_tile_idx;
    1403           0 :   txn_executed->sigverify_exec_tile_idx = txn_info->sigverify_exec_tile_idx;
    1404           0 :   txn_executed->compute_units_consumed = txn_info->compute_units_consumed;
    1405           0 :   txn_executed->max_compute_units = txn_info->max_compute_units;
    1406           0 :   txn_executed->transaction_fee = txn_info->transaction_fee;
    1407           0 :   txn_executed->priority_fee = txn_info->priority_fee;
    1408           0 :   txn_executed->tips = txn_info->tips;
    1409           0 :   publish_replay_out( ctx, stem, REPLAY_SIG_TXN_EXECUTED, sizeof(*txn_executed) );
    1410           0 : }
    1411             : 
    1412             : /* Emit the runtime_block event for a finalized bank (replayed or
    1413             :    leader-produced).  The fee arguments are the bank's values snapshotted
    1414             :    before fd_runtime_block_execute_finalize, which settles (zeroes) the
    1415             :    fee fields and applies the tip commission. */
    1416             : 
    1417             : static void
    1418             : replay_runtime_block_emit( fd_replay_tile_t * ctx,
    1419             :                            fd_bank_t *        bank,
    1420             :                            ulong              execution_fees,
    1421             :                            ulong              priority_fees,
    1422           0 :                            ulong              tips ) {
    1423           0 :   fd_block_id_ele_t const * ele        = &ctx->block_id_arr[ bank->idx ];
    1424           0 :   fd_hash_t         const * block_id   = &ele->dmr;
    1425           0 :   fd_hash_t parent_block_id = {0};
    1426           0 :   if( FD_LIKELY( bank->parent_idx!=ULONG_MAX ) ) {
    1427           0 :     fd_block_id_ele_t const * parent_ele = &ctx->block_id_arr[ bank->parent_idx ];
    1428           0 :     parent_block_id = parent_ele->dmr;
    1429           0 :   }
    1430           0 :   fd_pubkey_t leader = {0};
    1431           0 :   fd_pubkey_t const * _leader = fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, bank->f.slot );
    1432           0 :   if( FD_LIKELY( _leader ) ) leader = *_leader;
    1433           0 :   fd_sol_sysvar_clock_t clock = {0};
    1434           0 :   if( FD_UNLIKELY( !fd_sysvar_clock_read( ctx->accdb, bank->accdb_fork_id, &clock ) ) ) FD_LOG_ERR(( "failed to read clock sysvar for slot %lu", bank->f.slot ));
    1435           0 :   fd_event_runtime_block_emit( bank, block_id->uc, parent_block_id.uc, leader.uc,
    1436           0 :                                execution_fees, priority_fees, tips, bank->f.shred_cnt, &clock,
    1437           0 :                                ctx->fec_chain + bank->idx*FD_FEC_BLK_MAX,
    1438           0 :                                ctx->block_id_arr[ bank->idx ].fec_cnt );
    1439           0 : }
    1440             : 
    1441             : static void
    1442             : mark_bank_dead( fd_replay_tile_t *        ctx,
    1443             :                 fd_stem_context_t *       stem,
    1444             :                 ulong                     bank_idx,
    1445             :                 int                       dead_reason,
    1446             :                 int                       abandoned_reason,
    1447             :                 fd_block_footer_t const * footer );
    1448             : 
    1449             : 
    1450             : /**********************************************************************/
    1451             : /* Leader bank management                                             */
    1452             : /**********************************************************************/
    1453             : 
    1454             : static fd_bank_t *
    1455             : prepare_leader_bank( fd_replay_tile_t * ctx,
    1456             :                      fd_bank_t *        parent_bank,
    1457             :                      ulong              slot,
    1458           0 :                      long               now ) {
    1459             : 
    1460             :   /* Make sure that we are not already leader. */
    1461           0 :   FD_TEST( ctx->leader_bank==NULL );
    1462             : 
    1463           0 :   ctx->leader_bank = fd_banks_new_bank( ctx->banks, parent_bank->idx, now, 1 );
    1464           0 :   if( FD_UNLIKELY( !ctx->leader_bank ) ) {
    1465           0 :     FD_LOG_CRIT(( "invariant violation: leader bank is NULL for slot %lu", slot ));
    1466           0 :   }
    1467             : 
    1468           0 :   ctx->leader_bank = fd_banks_clone_from_parent( ctx->banks, ctx->leader_bank->idx );
    1469           0 :   if( FD_UNLIKELY( !ctx->leader_bank ) ) {
    1470           0 :     FD_LOG_CRIT(( "invariant violation: bank is NULL for slot %lu", slot ));
    1471           0 :   }
    1472             : 
    1473             :   /* pin the cost tracker for the duration of the leader slot */
    1474           0 :   fd_cost_tracker_store_pin( fd_bank_cost_tracker( ctx->leader_bank ), ctx->leader_bank->cost_tracker_fork_id );
    1475             : 
    1476           0 :   ctx->leader_bank->preparation_begin_nanos = now;
    1477             : 
    1478           0 :   ctx->leader_bank->f.slot = slot;
    1479           0 :   ctx->leader_bank->leader = ctx->identity_pubkey[ 0 ];
    1480             : 
    1481             :   /* Clear the previous occupant of this reused bank idx from whichever
    1482             :      map it is currently keyed in. */
    1483           0 :   fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ ctx->leader_bank->idx ];
    1484           0 :   if( FD_LIKELY( fd_block_id_map_ele_query( ctx->block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr )==block_id_ele ) ) {
    1485           0 :     FD_TEST( fd_block_id_map_ele_remove( ctx->block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr ) );
    1486           0 :   }
    1487             : 
    1488           0 :   block_id_ele->block_id_seen  = 0;
    1489           0 :   block_id_ele->slot           = slot;
    1490           0 :   block_id_ele->bank_seq       = ctx->leader_bank->bank_seq;
    1491           0 :   block_id_ele->latest_fec_idx = 0U;
    1492           0 :   block_id_ele->fec_cnt        = 0U;
    1493           0 :   memset( &block_id_ele->latest_mr,  0, sizeof(fd_hash_t) );
    1494           0 :   memset( &block_id_ele->dmr,        0, sizeof(fd_hash_t) );
    1495             : 
    1496           0 :   ctx->leader_bank->txncache_fork_id     = fd_txncache_attach_child ( ctx->txncache,  parent_bank->txncache_fork_id  );
    1497           0 :   ctx->leader_bank->progcache_fork_id    = fd_progcache_attach_child( ctx->progcache, parent_bank->progcache_fork_id );
    1498           0 :   ctx->leader_bank->accdb_fork_id        = fd_accdb_attach_child    ( ctx->accdb,     parent_bank->accdb_fork_id     );
    1499           0 :   ctx->leader_bank->parent_accdb_fork_id = parent_bank->accdb_fork_id;
    1500             : 
    1501           0 :   int is_epoch_boundary = 0;
    1502           0 :   fd_runtime_block_execute_prepare( ctx->banks, ctx->leader_bank, ctx->accdb, ctx->runtime_stack, ctx->capture_ctx, &is_epoch_boundary );
    1503           0 :   FD_CHECK_CRIT( !is_epoch_boundary || FD_FEATURE_ACTIVE_BANK( ctx->leader_bank, alpenglow ), "alpenglow feature is not active" );
    1504             : 
    1505           0 :   ulong max_tick_height;
    1506           0 :   if( FD_UNLIKELY( FD_RUNTIME_EXECUTE_SUCCESS!=fd_runtime_compute_max_tick_height( parent_bank->f.ticks_per_slot, slot, &max_tick_height ) ) ) {
    1507           0 :     FD_LOG_CRIT(( "couldn't compute tick height/max tick height slot %lu ticks_per_slot %lu", slot, parent_bank->f.ticks_per_slot ));
    1508           0 :   }
    1509           0 :   ctx->leader_bank->f.max_tick_height = max_tick_height;
    1510             : 
    1511             :   /* Now that a bank has been created for the leader slot, increment the
    1512             :      reference count until we are done with the leader slot. */
    1513           0 :   ctx->leader_bank->refcnt++;
    1514             : 
    1515           0 :   return ctx->leader_bank;
    1516           0 : }
    1517             : 
    1518             : static inline void
    1519           0 : maybe_switch_identity( fd_replay_tile_t * ctx ) {
    1520             : 
    1521           0 :   if( FD_LIKELY( fd_keyswitch_state_query( ctx->keyswitch )!=FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) return;
    1522             : 
    1523             :   /* Switch identity */
    1524             : 
    1525           0 :   FD_LOG_DEBUG(( "keyswitch: switching identity" ));
    1526             : 
    1527           0 :   memcpy( ctx->identity_pubkey, ctx->keyswitch->bytes, 32UL );
    1528           0 :   ctx->voter_rank_cached = 0;
    1529           0 :   ctx->identity_dirty = 1;
    1530             : 
    1531           0 :   ctx->metrics.voted_slot = ULONG_MAX;
    1532             : 
    1533           0 :   fd_node_info_write_begin( ctx->node_info );
    1534           0 :   ctx->node_info->info.identity = *ctx->identity_pubkey;
    1535           0 :   fd_node_info_write_end  ( ctx->node_info );
    1536             : 
    1537           0 :   ctx->next_leader_query_start = ULONG_MAX;
    1538             : 
    1539           0 :   ctx->identity_vote_rooted = 0;
    1540           0 :   ctx->identity_idx++;
    1541           0 :   fd_vote_tracker_reset( ctx->vote_tracker );
    1542             : 
    1543             :   /* identity_vote_rooted was just reset, so the gate rests on the flag
    1544             :      alone and a surviving auto would read as armed (see fd_wfs.h). */
    1545           0 :   if( FD_UNLIKELY( ctx->wait_for_vote_to_start_leader==FD_BOOLAU_AUTO ) ) {
    1546           0 :     FD_LOG_CRIT(( "wait_for_vote_to_start_leader unresolved (auto) at identity rotation" ));
    1547           0 :   }
    1548             : 
    1549             :   /* Save the current sequence so the voter knows what sequence to
    1550             :      consume up to. */
    1551           0 :   ctx->keyswitch->result = fd_mcache_seq_query( ctx->slot_out_seq );
    1552           0 :   fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
    1553           0 : }
    1554             : 
    1555             : static void
    1556             : construct_footer_certs( fd_replay_tile_t const * ctx,
    1557             :                         ulong                    leader_slot,
    1558             :                         ulong                    migration_slot,
    1559           0 :                         fd_block_footer_t *      footer ) {
    1560           0 :   fd_votor_certed_t const * fin = ctx->votor_final;
    1561           0 :   int use_fast = fin->slot!=ULONG_MAX && fin->kind==FD_CERT_KIND_FAST_FINAL;
    1562           0 :   int use_slow = fin->slot!=ULONG_MAX && fin->kind==FD_CERT_KIND_FINAL;
    1563             : 
    1564           0 :   footer->has_fast_final_cert = use_fast;
    1565           0 :   footer->has_final_cert      = use_slow;
    1566           0 :   if( footer->has_fast_final_cert ) {
    1567           0 :     footer->has_fast_final_cert = fd_block_footer_cert_from_agg( &footer->fast_final_cert, fin->slot, fin->block_id.uc, &fin->agg );
    1568           0 :   }
    1569           0 :   if( footer->has_final_cert ) {
    1570           0 :     footer->has_final_cert = fd_block_footer_cert_from_agg( &footer->final_cert, fin->slot, NULL,             &fin->agg  ) &&
    1571           0 :                              fd_block_footer_cert_from_agg( &footer->notar_cert, fin->slot, fin->block_id.uc, &fin->agg2 );
    1572           0 :   }
    1573             : 
    1574           0 :   int reward_ok = migration_slot!=ULONG_MAX &&
    1575           0 :                   leader_slot>=migration_slot+FD_NUM_SLOTS_FOR_REWARD+1UL;
    1576           0 :   if( FD_LIKELY( reward_ok ) ) {
    1577           0 :     ulong                     reward_slot = leader_slot-FD_NUM_SLOTS_FOR_REWARD;
    1578           0 :     fd_votor_reward_t const * reward      = &ctx->votor_reward[ reward_slot%(FD_NUM_SLOTS_FOR_REWARD+FD_VOTOR_SLOTS_PER_WINDOW+1UL) ];
    1579           0 :     footer->has_notar_reward_cert = reward->slot==reward_slot && !fd_bls_set_is_null( reward->agg_notar.set ) && fd_block_footer_cert_from_agg( &footer->notar_reward_cert, reward_slot, reward->block_id.uc, &reward->agg_notar );
    1580           0 :     footer->has_skip_reward_cert  = reward->slot==reward_slot && !fd_bls_set_is_null( reward->agg_skip.set  ) && fd_block_footer_cert_from_agg( &footer->skip_reward_cert,  reward_slot, NULL,                &reward->agg_skip  );
    1581           0 :   }
    1582           0 : }
    1583             : 
    1584             : static void
    1585             : publish_leader_footer( fd_replay_tile_t *        ctx,
    1586             :                        fd_stem_context_t *       stem,
    1587             :                        ulong                     slot,
    1588           0 :                        fd_block_footer_t const * footer ) {
    1589           0 :   fd_replay_leader_footer_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
    1590           0 :   msg->slot   = slot;
    1591           0 :   msg->footer = *footer;
    1592             : 
    1593           0 :   ulong sz = sizeof(fd_replay_leader_footer_t);
    1594           0 :   publish_replay_out( ctx, stem, REPLAY_SIG_LEADER_FOOTER, sz );
    1595           0 : }
    1596             : 
    1597             : static inline int
    1598             : try_become_leader( fd_replay_tile_t *  ctx,
    1599             :                    fd_stem_context_t * stem,
    1600             :                    uint                reason,
    1601           0 :                    ulong               end_slot ) {
    1602             : 
    1603           0 :   if( FD_LIKELY( ctx->next_leader_slot==ULONG_MAX ||
    1604           0 :                  ctx->is_leader ||
    1605             :                  /* (!ctx->identity_vote_rooted && ctx->wait_for_vote_to_start_leader) ||  // deprecated */
    1606           0 :                  ctx->replay_out->idx==ULONG_MAX ||
    1607           0 :                  !ctx->wfs_complete ) ) {
    1608           0 :     return 0;
    1609           0 :   }
    1610             : 
    1611           0 :   ulong             parent_slot     = ctx->votor_leader->parent_slot;
    1612           0 :   fd_hash_t const * parent_block_id = &ctx->votor_leader->parent_block_id;
    1613           0 :   fd_block_id_t     parent_key      = fd_block_id( parent_slot, parent_block_id->uc );
    1614             : 
    1615           0 :   fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &parent_key, NULL, ctx->block_id_arr );
    1616           0 :   if( FD_UNLIKELY( !block_id_ele ) ) return 0;
    1617           0 :   fd_bank_t * reset_bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) );
    1618           0 :   if( FD_UNLIKELY( !reset_bank || reset_bank->bank_seq!=block_id_ele->bank_seq || reset_bank->state!=FD_BANK_STATE_FROZEN ) ) return 0;
    1619             : 
    1620             :   /* A finalization above the parent, or a root that excludes it, means
    1621             :      we should give up our leader slot. */
    1622           0 :   if( FD_UNLIKELY( ( ctx->votor_final->slot!=ULONG_MAX && ctx->votor_final->slot>parent_slot ) ||
    1623           0 :                    ( ctx->consensus_root_slot!=ULONG_MAX && ctx->consensus_root_slot>parent_slot ) ||
    1624           0 :                    ( ctx->consensus_root_slot==parent_slot && !fd_hash_eq( &ctx->consensus_root, parent_block_id ) ) ) ) {
    1625           0 :     ctx->next_leader_slot = ULONG_MAX;
    1626           0 :     return 0;
    1627           0 :   }
    1628             : 
    1629             :   /* Certs made a later window ParentReady after votor sent this one:
    1630             :      the cluster skipped it. */
    1631           0 :   if( FD_UNLIKELY( ctx->next_leader_slot<ctx->votor_parent_ready_slot ) ) {
    1632           0 :     ctx->metrics.leader_window_superseded++;
    1633           0 :     ctx->next_leader_slot = ULONG_MAX;
    1634           0 :     return 0;
    1635           0 :   }
    1636             : 
    1637           0 :   if( FD_UNLIKELY( !fd_banks_can_start_bank( ctx->banks ) ) ) return 0;
    1638           0 :   if( FD_UNLIKELY( ctx->halt_replay ) ) return 0;
    1639           0 :   if( !ctx->supports_leader ) return 0;
    1640             : 
    1641             :   /* Don't become leader if the slot is not scheduled for the identity.
    1642             :      This can only happen in cases where the identity just switched. */
    1643           0 :   fd_pubkey_t const * leader = fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, ctx->next_leader_slot );
    1644           0 :   if( FD_UNLIKELY( !leader || !fd_pubkey_eq( leader, ctx->identity_pubkey ) ) ) {
    1645           0 :     ctx->next_leader_slot = ULONG_MAX;
    1646           0 :     return 0;
    1647           0 :   }
    1648             : 
    1649             :   /* The cluster already produced a slot of this window, eg. our own
    1650             :      past slots replayed during catch-up after a restart. */
    1651           0 :   ulong window_end = ctx->next_leader_slot - ctx->next_leader_slot%FD_VOTOR_SLOTS_PER_WINDOW + FD_VOTOR_SLOTS_PER_WINDOW;
    1652           0 :   for( ulong idx=0UL; idx<ctx->block_id_len; idx++ ) {
    1653           0 :     fd_block_id_ele_t const * ele = &ctx->block_id_arr[ idx ];
    1654           0 :     if( FD_LIKELY( ele->slot<ctx->next_leader_slot || ele->slot>=window_end ) ) continue;
    1655           0 :     fd_bank_t const * bank = fd_banks_bank_query( ctx->banks, idx );
    1656           0 :     if( FD_UNLIKELY( bank && bank->bank_seq==ele->bank_seq && !bank->is_leader ) ) {
    1657           0 :       ctx->metrics.leader_slot_already_produced++;
    1658           0 :       ctx->next_leader_slot = ULONG_MAX;
    1659           0 :       return 0;
    1660           0 :     }
    1661           0 :   }
    1662             : 
    1663             :   /* In Alpenglow, the "reset" block is signaled by ParentReady (a state
    1664             :      transition in the Votor consensus logic).  ParentReady can occur
    1665             :      ahead of replay, so we reset here, once we know we are certain we
    1666             :      can build off the ParentReady bank.  The parent is published to
    1667             :      the leader pipeline as part of the became leader message below. */
    1668             : 
    1669           0 :   ctx->reset_slot = parent_slot;
    1670             : 
    1671           0 :   FD_TEST( ctx->next_leader_slot>ctx->reset_slot );
    1672           0 :   long now = fd_tickcount();
    1673             : 
    1674           0 :   fd_hash_t parent_cmr = block_id_ele->latest_mr;
    1675           0 :   fd_hash_t parent_dmr = *parent_block_id;
    1676             : 
    1677           0 :   fd_blockhashes_t const * block_hash_queue = &reset_bank->f.block_hash_queue;
    1678           0 :   fd_hash_t const * last_hash = fd_blockhashes_peek_last_hash( block_hash_queue );
    1679           0 :   FD_TEST( last_hash );
    1680           0 :   fd_hash_t parent_blockhash = *last_hash;
    1681             : 
    1682           0 :   long now_nanos = fd_clock_epoch_y( ctx->clock->epoch, now );
    1683             : 
    1684           0 :   ctx->is_leader = 1;
    1685           0 :   ctx->recv_poh  = 0;
    1686             : 
    1687           0 :   memset( &ctx->leader_stats, 0, sizeof(ctx->leader_stats) );
    1688           0 :   ctx->leader_stats.slot                = ctx->next_leader_slot;
    1689           0 :   ctx->leader_stats.timing_table_idx    = ULONG_MAX;
    1690           0 :   ctx->leader_stats.became_leader_nanos = now_nanos;
    1691           0 :   ctx->leader_stats.leader_slot_start_nanos = now_nanos;
    1692             : 
    1693           0 :   FD_TEST( ctx->highwater_leader_slot==ULONG_MAX || ctx->highwater_leader_slot<ctx->next_leader_slot );
    1694           0 :   ctx->highwater_leader_slot = ctx->next_leader_slot;
    1695             : 
    1696           0 :   FD_LOG_INFO(( "becoming leader for slot %lu, parent slot is %lu", ctx->next_leader_slot, ctx->reset_slot ));
    1697             : 
    1698           0 :   fd_bank_t * bank = prepare_leader_bank( ctx, reset_bank, ctx->next_leader_slot, now_nanos );
    1699             : 
    1700           0 :   fd_bundle_crank_tip_payment_config_t config[1] = { 0 };
    1701           0 :   fd_pubkey_t tip_receiver_owner = {0};
    1702             : 
    1703           0 :   if( FD_UNLIKELY( ctx->bundle.enabled ) ) {
    1704           0 :     fd_acct_addr_t tip_payment_config[1];
    1705           0 :     fd_acct_addr_t tip_receiver[1];
    1706           0 :     fd_bundle_crank_get_addresses( ctx->bundle.gen, bank->f.epoch, tip_payment_config, tip_receiver );
    1707             : 
    1708           0 :     fd_acc_t tip_config_acc = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, tip_payment_config->b );
    1709           0 :     if( FD_UNLIKELY( !tip_config_acc.lamports ) ) {
    1710           0 :       FD_BASE58_ENCODE_32_BYTES( tip_payment_config->b, tip_config_acc_b58 );
    1711           0 :       FD_LOG_WARNING(( "tip payment config account %s does not exist", tip_config_acc_b58 ));
    1712           0 :       fd_accdb_unread_one( ctx->accdb, &tip_config_acc );
    1713           0 :     } else if( FD_UNLIKELY( tip_config_acc.data_len<sizeof(fd_bundle_crank_tip_payment_config_t) ) ) {
    1714           0 :       FD_LOG_HEXDUMP_WARNING(( "invalid tip payment config account data", tip_config_acc.data, tip_config_acc.data_len ));
    1715           0 :       fd_accdb_unread_one( ctx->accdb, &tip_config_acc );
    1716           0 :     } else {
    1717           0 :       memcpy( config, tip_config_acc.data, sizeof(fd_bundle_crank_tip_payment_config_t) );
    1718           0 :       fd_accdb_unread_one( ctx->accdb, &tip_config_acc );
    1719           0 :     }
    1720             : 
    1721             :     /* It is possible that the tip receiver account does not exist yet
    1722             :        if it is the first time in an epoch. */
    1723           0 :     fd_acc_t tip_receiver_acc = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, tip_receiver->b );
    1724           0 :     if( FD_LIKELY( tip_receiver_acc.lamports ) ) {
    1725           0 :       fd_memcpy( tip_receiver_owner.uc, tip_receiver_acc.owner, 32UL );
    1726           0 :     }
    1727           0 :     fd_accdb_unread_one( ctx->accdb, &tip_receiver_acc );
    1728           0 :   }
    1729             : 
    1730             : 
    1731             :   /* Like Agave's block_timeout, the block ends relative to the window's
    1732             :      ParentReady.  If that is already past, end at once rather than run
    1733             :      into votor's skip timeout. */
    1734           0 :   long slot_end_ns = ctx->votor_leader->parent_ready_ns
    1735           0 :                    + (long)( ( ctx->next_leader_slot%FD_VOTOR_SLOTS_PER_WINDOW+1UL )*bank->f.slot_params.ns_per_slot )
    1736           0 :                    - (long)FD_TARGET_SLOT_ADJUSTMENT_NS - FD_TIME_TO_COMPLETE_BROADCAST_NS;
    1737             : 
    1738           0 :   fd_became_leader_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
    1739           0 :   msg->slot                = ctx->next_leader_slot;
    1740           0 :   msg->parent_slot         = ctx->reset_slot;
    1741           0 :   msg->parent_cmr          = parent_cmr;
    1742           0 :   msg->parent_dmr          = parent_dmr;
    1743           0 :   msg->parent_blockhash    = parent_blockhash;
    1744           0 :   msg->block_height        = bank->f.block_height;
    1745           0 :   msg->slot_start_ns       = now_nanos;
    1746           0 :   msg->slot_end_ns         = fd_long_max( slot_end_ns, now_nanos );
    1747           0 :   msg->bank_idx            = bank->idx;
    1748           0 :   msg->bank_seq            = bank->bank_seq;
    1749           0 :   msg->ticks_per_slot      = bank->f.ticks_per_slot;
    1750           0 :   msg->tick_duration_ns    = bank->f.slot_params.ns_per_slot_adjusted/msg->ticks_per_slot;
    1751           0 :   msg->bundle->config[0]   = config[0];
    1752           0 :   memcpy( msg->bundle->last_blockhash,     bank->f.poh.hash,      sizeof(fd_hash_t)   );
    1753           0 :   memcpy( msg->bundle->tip_receiver_owner, tip_receiver_owner.uc, sizeof(fd_pubkey_t) );
    1754             : 
    1755           0 :   msg->max_microblocks_in_slot = FD_POH_ALPENGLOW_MAX_MICROBLOCKS_PER_SLOT;
    1756             : 
    1757           0 :   msg->epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, ctx->next_leader_slot, NULL );
    1758             : 
    1759           0 :   fd_cost_tracker_t const * cost_tracker = fd_cost_tracker_store_peek( fd_bank_cost_tracker( bank ), bank->cost_tracker_fork_id );
    1760             : 
    1761           0 :   msg->limits.slot_max_cost                     = cost_tracker->block_cost_limit;
    1762           0 :   msg->limits.slot_max_write_cost_per_acct      = cost_tracker->account_cost_limit;
    1763           0 :   msg->limits.slot_max_allocated_data_per_block = cost_tracker->data_size_limit;
    1764           0 :   msg->limits.slot_max_data_shreds              = bank->f.slot_params.max_shred_idx;
    1765             : 
    1766           0 :   publish_replay_out( ctx, stem, REPLAY_SIG_BECAME_LEADER, sizeof(fd_became_leader_t) );
    1767           0 :   metrics_trigger( ctx, reason, reason==FD_METRICS_SAMPLE_REASON_LEADER_START ? msg->slot : end_slot );
    1768             : 
    1769           0 :   ctx->leader_window_start_ns = ctx->votor_leader->parent_ready_ns;
    1770           0 :   ctx->next_leader_slot       = ULONG_MAX;
    1771             : 
    1772           0 :   return 1;
    1773           0 : }
    1774             : 
    1775             : /* replay_block_complete checks the footer's bank hash, then freezes and
    1776             :    publishes a finalized bank, whose footer certs all verified.  Returns
    1777             :    1 if it marked the bank dead. */
    1778             : 
    1779             : static int
    1780             : replay_block_complete( fd_replay_tile_t *        ctx,
    1781             :                        fd_stem_context_t *       stem,
    1782             :                        fd_bank_t *               bank,
    1783             :                        fd_block_footer_t const * footer,
    1784             :                        ulong                     execution_fees_pre_settle,
    1785           0 :                        ulong                     priority_fees_pre_settle ) {
    1786           0 :   fd_hash_t const * footer_bank_hash = &footer->bank_hash;
    1787           0 :   if( FD_UNLIKELY( memcmp( footer_bank_hash->uc, bank->f.bank_hash.uc, sizeof(fd_hash_t) ) ) ) {
    1788           0 :     FD_BASE58_ENCODE_32_BYTES( footer_bank_hash->uc,   footer_bank_hash_b58   );
    1789           0 :     FD_BASE58_ENCODE_32_BYTES( bank->f.bank_hash.uc, executed_bank_hash_b58 );
    1790           0 :     FD_LOG_WARNING(( "slot %lu: bank hash mismatch, footer declares %s but executed %s. ", bank->f.slot, footer_bank_hash_b58, executed_bank_hash_b58 ));
    1791             : 
    1792             :     /* A bank hash mismatch detected now is only considered a true BHM
    1793             :        if the block_id also becomes finalized.  If the finalization
    1794             :        already arrived, ahead of replay, check the parked
    1795             :        finalized_block_id_lo/hi.  Rotor may also have detected the block
    1796             :        is finalized, on its delivery or a redelivery, so check the
    1797             :        block_id_ele. */
    1798           0 :     fd_block_id_t const * block_id = &ctx->block_id_arr[ bank->idx ].block_info;
    1799           0 :     if( FD_UNLIKELY( fd_block_id_eq( block_id, &ctx->finalized_block_id_lo ) || fd_block_id_eq( block_id, &ctx->finalized_block_id_hi )
    1800           0 :                      || ctx->block_id_arr[ bank->idx ].finalized ) ) {
    1801           0 :       FD_BASE58_ENCODE_32_BYTES( block_id->hash.uc, block_id_b58 );
    1802           0 :       FD_LOG_ERR(( "HARD FORK DETECTED for slot %lu block ID `%s`", block_id->slot, block_id_b58 ));
    1803           0 :     }
    1804             : 
    1805           0 :     if( FD_LIKELY( !fd_bhm_map_ele_query( ctx->bhm_map, block_id, NULL, ctx->bhm_pool ) ) ) {
    1806           0 :       if( FD_UNLIKELY( !fd_bhm_pool_free( ctx->bhm_pool ) ) ) {
    1807             :         /* Pool exhausted, evict the oldest entry. */
    1808           0 :         fd_bhm_ele_t * oldest = fd_bhm_dlist_ele_pop_head( ctx->bhm_dlist, ctx->bhm_pool );
    1809           0 :         FD_TEST( fd_bhm_map_ele_remove( ctx->bhm_map, &oldest->block_id, NULL, ctx->bhm_pool )==oldest );
    1810           0 :         fd_bhm_pool_ele_release( ctx->bhm_pool, oldest );
    1811           0 :       }
    1812             : 
    1813           0 :       fd_bhm_ele_t * ele = fd_bhm_pool_ele_acquire( ctx->bhm_pool );
    1814           0 :       ele->block_id = *block_id;
    1815           0 :       FD_TEST( fd_bhm_map_ele_insert( ctx->bhm_map, ele, ctx->bhm_pool ) );
    1816           0 :       fd_bhm_dlist_ele_push_tail( ctx->bhm_dlist, ele, ctx->bhm_pool );
    1817           0 :     }
    1818             : 
    1819           0 :     mark_bank_dead( ctx, stem, bank->idx, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_BAD_FOOTER, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED, footer );
    1820           0 :     return 1;
    1821           0 :   } else {
    1822           0 :     FD_BASE58_ENCODE_32_BYTES( footer_bank_hash->uc,   footer_bank_hash_b58 );
    1823           0 :     FD_BASE58_ENCODE_32_BYTES( bank->f.bank_hash.uc, executed_bank_hash_b58 );
    1824           0 :     FD_LOG_INFO(( "slot %lu: bank hash matches, footer declares %s, executed %s", bank->f.slot, footer_bank_hash_b58, executed_bank_hash_b58 ));
    1825           0 :   }
    1826             : 
    1827             :   /* Copy out cost tracker fields before freezing */
    1828           0 :   fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
    1829           0 :   cost_tracker_snap( bank, slot_info );
    1830             : 
    1831             :   /* fetch identity balance infrequently and after set-identity. */
    1832           0 :   slot_info->identity_balance = ULONG_MAX;
    1833           0 :   if( FD_UNLIKELY( ctx->identity_dirty || bank->f.slot%4096UL==0UL ) ) {
    1834           0 :     ctx->identity_dirty = 0;
    1835           0 :     slot_info->identity_balance = fd_accdb_lamports( ctx->accdb, bank->accdb_fork_id, ctx->identity_pubkey->uc );
    1836           0 :   }
    1837             : 
    1838             :   /* Mark the bank as frozen. */
    1839           0 :   fd_block_id_ele_t const * block_id_ele = &ctx->block_id_arr[ bank->idx ];
    1840           0 :   bank->f.block_id = block_id_ele->dmr;
    1841           0 :   fd_banks_mark_bank_frozen( bank );
    1842           0 :   bank->block_completed_nanos = fd_clock_tile_now( ctx->clock );
    1843             : 
    1844             :   /**********************************************************************/
    1845             :   /* Bank hash comparison, and halt if there's a mismatch after replay  */
    1846             :   /**********************************************************************/
    1847             : 
    1848             :   /* Must be last so we can measure completion time correctly, even
    1849             :      though we could technically do this before the hash cmp. */
    1850           0 :   publish_slot_completed( ctx, stem, bank, 0, 0 /* is_leader */, execution_fees_pre_settle, priority_fees_pre_settle, footer );
    1851             : 
    1852             :   /* The footer's finalization cert can also finalize. */
    1853           0 :   if( footer->has_fast_final_cert ) { try_advance_root( ctx, fd_block_id( footer->fast_final_cert.slot, footer->fast_final_cert.block_id.uc ) ); }
    1854           0 :   else if( footer->has_final_cert ) { try_advance_root( ctx, fd_block_id( footer->final_cert.slot,      footer->notar_cert.block_id.uc      ) ); }
    1855             : 
    1856             :   /* If enabled, dump the block to a file and reset the dumping
    1857             :      context state */
    1858           0 :   if( FD_UNLIKELY( ctx->dump_proto_ctx && ctx->dump_proto_ctx->dump_block_to_pb ) ) {
    1859           0 :     fd_dump_block_to_protobuf( ctx->block_dump_ctx, ctx->banks, bank, ctx->accdb, ctx->dump_proto_ctx, ctx->runtime_stack );
    1860           0 :     fd_block_dump_context_reset( ctx->block_dump_ctx );
    1861           0 :   }
    1862           0 :   return 0;
    1863           0 : }
    1864             : 
    1865             : /* Finishes a block whose footer tasks ran on exec tiles, once all of
    1866             :    them have returned.  This updates the leader's vote account with the
    1867             :    reward slices' summed leader credits and the bank hash.  Returns 1 if
    1868             :    it marked the bank dead. */
    1869             : 
    1870             : static int
    1871             : replay_block_end( fd_replay_tile_t *        ctx,
    1872             :                   fd_stem_context_t *       stem,
    1873             :                   fd_bank_t *               bank,
    1874             :                   fd_block_footer_t const * footer,
    1875             :                   ulong                     execution_fees_pre_settle,
    1876           0 :                   ulong                     priority_fees_pre_settle ) {
    1877           0 :   fd_replay_footer_end_t * end = ctx->footer_end + bank->idx;
    1878           0 :   fd_runtime_block_footer_end( bank, ctx->accdb, ctx->footer_cache, &end->plan, end->leader_credits );
    1879           0 :   return replay_block_complete( ctx, stem, bank, footer, execution_fees_pre_settle, priority_fees_pre_settle );
    1880           0 : }
    1881             : 
    1882             : /* Sends one footer cert of the bank to an exec tile as a cert verify
    1883             :    task.  The bank holds a reference and counts the footer task until
    1884             :    the tile reports back. */
    1885             : 
    1886             : static void
    1887             : replay_cert_publish( fd_replay_tile_t *             ctx,
    1888             :                      fd_stem_context_t *            stem,
    1889             :                      fd_bank_t *                    bank,
    1890             :                      fd_block_footer_cert_t const * cert,
    1891             :                      uint                           tag,
    1892             :                      ulong                          quorum,
    1893           0 :                      ulong                          t ) {
    1894           0 :   FD_TEST( ctx->shred_version );
    1895           0 :   ctx->cert_payload_sz[ t ]  = fd_alpenglow_cert_payload( cert, tag, ctx->shred_version, ctx->cert_payload[ t ] );
    1896             : 
    1897           0 :   fd_bls_hash_t const * hm = fd_footer_cache_hash_query( ctx->footer_cache, ctx->cert_payload[ t ], ctx->cert_payload_sz[ t ]  );
    1898             : 
    1899           0 :   fd_replay_out_link_t * exec_out = &ctx->exec_out[ t ];
    1900           0 :   fd_execrp_cert_verify_msg_t * msg = fd_chunk_to_laddr( exec_out->mem, exec_out->chunk );
    1901           0 :   msg->bank_idx      = bank->idx;
    1902           0 :   msg->quorum        = quorum;
    1903           0 :   msg->tag           = tag;
    1904           0 :   msg->shred_version = ctx->shred_version;
    1905           0 :   msg->has_hm        = !!hm;
    1906           0 :   if( hm ) msg->hm   = *hm;
    1907           0 :   *msg->cert         = *cert;
    1908             : 
    1909           0 :   bank->refcnt++;
    1910           0 :   bank->footer_task_cnt++;
    1911             : 
    1912           0 :   fd_stem_publish( stem, exec_out->idx, (FD_EXECRP_TT_CERT_VERIFY<<32) | t, exec_out->chunk, sizeof(fd_execrp_cert_verify_msg_t), 0UL, 0UL, 0UL );
    1913           0 :   exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*msg), exec_out->chunk0, exec_out->wmark );
    1914           0 : }
    1915             : 
    1916             : static int
    1917             : replay_block_finalize( fd_replay_tile_t *  ctx,
    1918             :                        fd_stem_context_t * stem,
    1919           0 :                        fd_bank_t *         bank ) {
    1920           0 :   bank->last_transaction_finished_nanos = fd_clock_tile_now( ctx->clock );
    1921             : 
    1922             :   /* Set poh hash in bank. */
    1923           0 :   fd_hash_t * poh = fd_sched_get_poh( ctx->sched, bank->idx );
    1924           0 :   bank->f.poh = *poh;
    1925             : 
    1926             :   /* Set shred count in bank. */
    1927           0 :   bank->f.shred_cnt = fd_sched_get_shred_cnt( ctx->sched, bank->idx );
    1928             : 
    1929           0 :   ulong execution_fees_pre_settle = bank->f.execution_fees;
    1930           0 :   ulong priority_fees_pre_settle  = bank->f.priority_fees;
    1931           0 :   ulong tips_pre_settle           = bank->f.tips;
    1932             : 
    1933           0 :   fd_block_footer_t const * footer = NULL;
    1934           0 :   footer = fd_sched_get_footer( ctx->sched, bank->idx );
    1935             : 
    1936             :   /* Revert to in-band hashing for the end-of-block writers (sysvars,
    1937             :      fee settlement, incinerator, footer).  With the delta folded in,
    1938             :      the bank's lthash covers every account as it currently is on this
    1939             :      fork, so it is correct to subtract the current fork version on
    1940             :      write.  An in-band block's lthash is complete as it is. */
    1941           0 :   if( FD_LIKELY( bank->lthash_mode==FD_BANK_LTHASH_MODE_OOB_SKIP ) ) {
    1942           0 :     fd_lthash_value_t * lthash = fd_bank_lthash_locking_modify( bank );
    1943           0 :     fd_lthash_add( lthash, fd_sched_lthash_delta( ctx->sched, bank->idx ) );
    1944           0 :     fd_bank_lthash_end_locking_modify( bank );
    1945           0 :     bank->lthash_mode = FD_BANK_LTHASH_MODE_INBAND;
    1946           0 :   } else {
    1947           0 :     FD_TEST( bank->lthash_mode==FD_BANK_LTHASH_MODE_INBAND );
    1948           0 :   }
    1949             : 
    1950             :   /* Footer certs not already out on exec tiles go to idle ones now.
    1951             :      The rest verify here.  The footer's vote rewards go to the tiles
    1952             :      still idle as slices, while this tile freezes the sysvars.  The
    1953             :      fees settle after the slices, since the leader's collector may be a
    1954             :      signer's vote account.  The bank fully completes on its last footer
    1955             :      task done. */
    1956           0 :   fd_block_footer_cert_t const * cert  [ FD_ALPENGLOW_FOOTER_CERT_MAX ];
    1957           0 :   uint                           tag   [ FD_ALPENGLOW_FOOTER_CERT_MAX ];
    1958           0 :   ulong                          quorum[ FD_ALPENGLOW_FOOTER_CERT_MAX ];
    1959             : 
    1960           0 :   fd_replay_footer_end_t * end = ctx->footer_end + bank->idx;
    1961           0 :   ulong cnt = 0UL;
    1962           0 :   if( footer && !ctx->capture_ctx && !ctx->report_runtime_diffs ) {
    1963           0 :     cnt = fd_alpenglow_footer_certs( footer, cert, tag, quorum );
    1964           0 :     ulong tiles = fd_sched_footer_dispatch( ctx->sched, bank->idx, cnt-end->cert_next );
    1965           0 :     for( ; tiles; end->cert_next++, tiles=fd_ulong_pop_lsb( tiles ) ) {
    1966           0 :       replay_cert_publish( ctx, stem, bank, cert[ end->cert_next ], tag[ end->cert_next ], quorum[ end->cert_next ], (ulong)fd_ulong_find_lsb( tiles ) );
    1967           0 :     }
    1968           0 :   }
    1969             : 
    1970           0 :   if( FD_UNLIKELY( !end->cert_next ) ) {
    1971             :     /* No cert went to a tile.  Finish the block serially right here,
    1972             :        footer and all.  The footer cache stays out of it while another
    1973             :        bank's slices may be writing its memo. */
    1974           0 :     if( FD_UNLIKELY( fd_runtime_block_execute_finalize( bank, ctx->accdb, ctx->capture_ctx, footer, ctx->shred_version, ctx->rewards_in_flight_cnt ? NULL : ctx->footer_cache ) ) ) goto bad_footer;
    1975           0 :     if( FD_UNLIKELY( ctx->report_runtime_diffs ) ) replay_runtime_block_emit( ctx, bank, execution_fees_pre_settle, priority_fees_pre_settle, tips_pre_settle );
    1976           0 :     return replay_block_complete( ctx, stem, bank, footer, execution_fees_pre_settle, priority_fees_pre_settle );
    1977           0 :   }
    1978             : 
    1979           0 :   bank->execution_fees_pre_settle = execution_fees_pre_settle;
    1980           0 :   bank->priority_fees_pre_settle  = priority_fees_pre_settle;
    1981           0 :   for( ulong i=end->cert_next; i<cnt; i++ ) {
    1982           0 :     if( FD_UNLIKELY( fd_alpenglow_cert_verify( bank, cert[ i ], tag[ i ], quorum[ i ], ctx->shred_version, ctx->footer_cache, NULL, NULL ) ) ) goto bad_footer;
    1983           0 :   }
    1984             : 
    1985           0 :   if( FD_UNLIKELY( fd_runtime_block_footer_begin( bank, ctx->accdb, footer, &end->plan ) ) ) goto bad_footer;
    1986             :   /* The footer cache memo has one slot per rank shared by every bank,
    1987             :      and a slice keeps pointers into a slot until it has hashed the
    1988             :      image there.  Slices of another bank, dead or not, may still be out
    1989             :      on tiles, so this footer stays off the memo while any are. */
    1990           0 :   end->plan.use_memo = !ctx->rewards_in_flight_cnt;
    1991           0 :   if( FD_LIKELY( end->plan.has_reward || end->plan.has_final ) ) {
    1992           0 :     ulong slice_tiles = fd_sched_footer_dispatch( ctx->sched, bank->idx, ULONG_MAX );
    1993           0 :     ulong slice_cnt   = (ulong)fd_ulong_popcnt( slice_tiles );
    1994           0 :     if( FD_UNLIKELY( !slice_cnt ) ) {
    1995             :       /* No idle exec tile.  Run the one slice here. */
    1996           0 :       fd_lthash_value_t delta[1];
    1997           0 :       fd_lthash_zero( delta );
    1998           0 :       if( FD_UNLIKELY( fd_alpenglow_rewards_apply_slice( bank, ctx->accdb, NULL, ctx->footer_cache, &end->plan, 0UL, 1UL, delta, &end->leader_credits ) ) ) goto bad_footer;
    1999           0 :       fd_lthash_add( fd_bank_lthash_locking_modify( bank ), delta );
    2000           0 :       fd_bank_lthash_end_locking_modify( bank );
    2001           0 :     }
    2002           0 :     for( ulong i=0UL; slice_tiles; i++, slice_tiles=fd_ulong_pop_lsb( slice_tiles ) ) {
    2003           0 :       ulong t = (ulong)fd_ulong_find_lsb( slice_tiles );
    2004             : 
    2005           0 :       fd_replay_out_link_t * exec_out = &ctx->exec_out[ t ];
    2006           0 :       fd_execrp_rewards_msg_t * msg = fd_chunk_to_laddr( exec_out->mem, exec_out->chunk );
    2007           0 :       msg->bank_idx  = bank->idx;
    2008           0 :       msg->slice_idx = i;
    2009           0 :       msg->slice_cnt = slice_cnt;
    2010           0 :       msg->plan      = end->plan;
    2011             : 
    2012           0 :       bank->refcnt++;
    2013             : 
    2014           0 :       fd_stem_publish( stem, exec_out->idx, (FD_EXECRP_TT_REWARDS<<32) | t, exec_out->chunk, sizeof(fd_execrp_rewards_msg_t), 0UL, 0UL, 0UL );
    2015           0 :       exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*msg), exec_out->chunk0, exec_out->wmark );
    2016           0 :     }
    2017           0 :     bank->footer_task_cnt      += slice_cnt;
    2018           0 :     ctx->rewards_in_flight_cnt += slice_cnt;
    2019           0 :   }
    2020           0 :   fd_runtime_freeze_sysvars( bank, ctx->accdb, NULL );
    2021             : 
    2022           0 :   end->finalized = 1;
    2023           0 :   if( FD_LIKELY( bank->footer_task_cnt ) ) return 0;
    2024           0 :   return replay_block_end( ctx, stem, bank, footer, execution_fees_pre_settle, priority_fees_pre_settle );
    2025             : 
    2026           0 : bad_footer:
    2027           0 :   mark_bank_dead( ctx, stem, bank->idx, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_BAD_FOOTER, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED, NULL );
    2028           0 :   return 1;
    2029           0 : }
    2030             : 
    2031             : static int
    2032             : try_fini_leader( fd_replay_tile_t *  ctx,
    2033           0 :                  fd_stem_context_t * stem ) {
    2034             : 
    2035             :   /* If we are leader, we can only unbecome the leader iff we have
    2036             :      received the poh hash from the poh tile and block id from reasm.
    2037             :      The block id entry is claimed for the leader slot in
    2038             :      prepare_leader_bank, so a slot mismatch here means the claim
    2039             :      discipline broke. */
    2040             : 
    2041           0 :   if( FD_LIKELY( !ctx->is_leader ) ) return 0;
    2042           0 :   if( !ctx->recv_poh ) return 0;
    2043             : 
    2044             :   /* The consensus root only advances through FROZEN banks and the
    2045             :      leader bank is frozen only below, so a root at or past our slot
    2046             :      can never include the leader bank.
    2047             : 
    2048             :      We can't include votor-only messages, because it's possible our
    2049             :      block was retransmitted but replay hasn't processed the FEC yet
    2050             :      (due to bespoke repair <=> replay concurrency).  */
    2051             : 
    2052           0 :   if( FD_UNLIKELY( !ctx->block_id_arr[ ctx->leader_bank->idx ].block_id_seen &&
    2053           0 :                    ctx->consensus_root_slot!=ULONG_MAX &&
    2054           0 :                    ctx->consensus_root_slot>=ctx->leader_bank->f.slot ) ) {
    2055           0 :     ulong bank_idx = ctx->leader_bank->idx;
    2056           0 :     metrics_trigger( ctx, FD_METRICS_SAMPLE_REASON_LEADER_END, ctx->leader_bank->f.slot );
    2057           0 :     ctx->leader_bank->refcnt--;
    2058           0 :     ctx->leader_bank = NULL;
    2059           0 :     ctx->recv_poh    = 0;
    2060           0 :     ctx->is_leader   = 0;
    2061           0 :     mark_bank_dead( ctx, stem, bank_idx, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED, NULL );
    2062           0 :     return 1;
    2063           0 :   }
    2064             : 
    2065           0 :   if( !ctx->block_id_arr[ ctx->leader_bank->idx ].block_id_seen ) return 0;
    2066           0 :   FD_TEST( ctx->block_id_arr[ ctx->leader_bank->idx ].slot==ctx->leader_bank->f.slot );
    2067             : 
    2068           0 :   ulong curr_slot = ctx->leader_bank->f.slot;
    2069             : 
    2070           0 :   ulong execution_fees_pre_settle;
    2071           0 :   ulong priority_fees_pre_settle;
    2072           0 :   ulong tips_pre_settle;
    2073             : 
    2074             :   /* Already finalized above, when the footer was published. */
    2075             : 
    2076           0 :   execution_fees_pre_settle = ctx->leader_execution_fees;
    2077           0 :   priority_fees_pre_settle  = ctx->leader_priority_fees;
    2078           0 :   tips_pre_settle           = ctx->leader_tips;
    2079             : 
    2080           0 :   ctx->leader_bank->f.shred_cnt = (ulong)ctx->block_id_arr[ ctx->leader_bank->idx ].fec_cnt*FD_FEC_SHRED_CNT;
    2081             : 
    2082           0 :   if( FD_UNLIKELY( ctx->report_runtime_diffs ) ) replay_runtime_block_emit( ctx, ctx->leader_bank, execution_fees_pre_settle, priority_fees_pre_settle, tips_pre_settle );
    2083             : 
    2084           0 :   fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
    2085           0 :   cost_tracker_snap( ctx->leader_bank, slot_info );
    2086           0 :   slot_info->identity_balance = ULONG_MAX;
    2087           0 :   if( FD_UNLIKELY( ctx->identity_dirty || curr_slot%4096UL==0UL ) ) {
    2088           0 :     ctx->identity_dirty         = 0;
    2089           0 :     slot_info->identity_balance = fd_accdb_lamports( ctx->accdb, ctx->leader_bank->accdb_fork_id, ctx->identity_pubkey->uc );
    2090           0 :   }
    2091             : 
    2092           0 :   ctx->leader_bank->f.block_id = ctx->block_id_arr[ ctx->leader_bank->idx ].dmr;
    2093           0 :   fd_banks_mark_bank_frozen( ctx->leader_bank );
    2094           0 :   ctx->leader_bank->block_completed_nanos = fd_clock_tile_now( ctx->clock );
    2095             : 
    2096           0 :   publish_slot_completed( ctx, stem, ctx->leader_bank, 0, 1 /* is_leader */, execution_fees_pre_settle, priority_fees_pre_settle, ctx->leader_footer );
    2097             : 
    2098             :   /* The reference on the bank is finally no longer needed. */
    2099           0 :   ctx->leader_bank->refcnt--;
    2100             : 
    2101           0 :   fd_bank_t * completed = ctx->leader_bank;
    2102             : 
    2103             :   /* We are no longer leader so we can clear the bank index we use for
    2104             :      being the leader. */
    2105           0 :   ctx->leader_bank = NULL;
    2106           0 :   ctx->recv_poh    = 0;
    2107           0 :   ctx->is_leader   = 0;
    2108             : 
    2109           0 :   if( FD_UNLIKELY( ctx->next_leader_slot==ULONG_MAX && (curr_slot+1UL)%FD_VOTOR_SLOTS_PER_WINDOW ) ) {
    2110           0 :     *ctx->votor_leader = (fd_votor_leader_t){
    2111           0 :       .slot            = curr_slot+1UL,
    2112           0 :       .parent_slot     = curr_slot,
    2113           0 :       .parent_block_id = ctx->block_id_arr[ completed->idx ].dmr,
    2114           0 :       .parent_ready_ns = ctx->leader_window_start_ns
    2115           0 :     };
    2116           0 :     ctx->next_leader_slot = curr_slot+1UL;
    2117           0 :     if( try_become_leader( ctx, stem, FD_METRICS_SAMPLE_REASON_LEADER_END_START, curr_slot ) ) return 1;
    2118           0 :   }
    2119             : 
    2120           0 :   metrics_trigger( ctx, FD_METRICS_SAMPLE_REASON_LEADER_END, curr_slot );
    2121           0 :   return 1;
    2122           0 : }
    2123             : 
    2124             : static void
    2125             : publish_root_advanced( fd_replay_tile_t *  ctx,
    2126             :                        fd_stem_context_t * stem,
    2127           0 :                        fd_bank_t *         bank ) {
    2128             : 
    2129             :   /* If the new consensus root is in the next epoch from the one the
    2130             :      replay tile currently holds, send the next epoch's leader schedule.
    2131             :      We can't use the new root's parent slot safely here. */
    2132           0 :   if( FD_UNLIKELY( bank->f.epoch>fd_slot_to_epoch( &bank->f.epoch_schedule, ctx->notified_root_slot, NULL ) ) ) {
    2133           0 :     fd_runtime_update_next_leaders( bank, ctx->runtime_stack );
    2134           0 :     publish_epoch_info( ctx, stem, bank, 1 );
    2135           0 :   }
    2136             : 
    2137           0 :   if( ctx->rpc_enabled ) {
    2138           0 :     bank->refcnt++;
    2139           0 :     FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for rpc", bank->idx, bank->f.slot, bank->refcnt ));
    2140             : 
    2141             :     /* Under Alpenglow Agave treats a rooted bank as both confirmed
    2142             :        and finalized.  The rpc tile holds an independent reference for
    2143             :        each alias. */
    2144           0 :     bank->refcnt++;
    2145           0 :     FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for rpc (confirmed alias)", bank->idx, bank->f.slot, bank->refcnt ));
    2146           0 :   }
    2147             : 
    2148             :   /* Increment the reference count on the consensus root bank to account
    2149             :      for the number of resolv tiles that are waiting on it. */
    2150           0 :   bank->refcnt += ctx->resolv_tile_cnt;
    2151           0 :   FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for resolv", bank->idx, bank->f.slot, bank->refcnt ));
    2152             : 
    2153           0 :   fd_replay_root_advanced_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
    2154             :   /* f.* first: gcc 11 SLP otherwise materialises the 27 KB idx..f.slot gap element-wise */
    2155           0 :   msg->slot      = bank->f.slot;
    2156           0 :   msg->bank_hash = bank->f.bank_hash;
    2157           0 :   msg->block_id  = bank->f.block_id;
    2158           0 :   msg->bank_idx  = bank->idx;
    2159           0 :   msg->bank_seq  = bank->bank_seq;
    2160             : 
    2161           0 :   publish_replay_out( ctx, stem, REPLAY_SIG_ROOT_ADVANCED, sizeof(fd_replay_root_advanced_t) );
    2162             : 
    2163             :   /* Drop every FEC set below the new root from the store. */
    2164             : 
    2165           0 :   fd_store_publish( ctx->store, ctx->map_join, bank->f.slot );
    2166           0 : }
    2167             : 
    2168             : /* publish_boot_slot announces the snapshot slot, which the validator
    2169             :    did not replay, as completed and rooted. */
    2170             : 
    2171             : static void
    2172             : publish_boot_slot( fd_replay_tile_t *  ctx,
    2173             :                    fd_stem_context_t * stem,
    2174           0 :                    fd_bank_t *         bank ) {
    2175           0 :   fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
    2176           0 :   cost_tracker_snap( bank, slot_info );
    2177           0 :   slot_info->identity_balance = fd_accdb_lamports( ctx->accdb, bank->accdb_fork_id, ctx->identity_pubkey->uc );
    2178             : 
    2179           0 :   fd_block_footer_t footer = { .bank_hash = bank->f.bank_hash }; /* dummy footer for the snapshot slot */
    2180           0 :   publish_slot_completed( ctx, stem, bank, 1, 0 /* is_leader */, 0, 0, &footer );
    2181           0 :   publish_root_advanced( ctx, stem, bank );
    2182           0 : }
    2183             : 
    2184             : /* Determine the default slot params to use for slots where no
    2185             :    reduce_slot_time feature gate is in effect.  This is important for
    2186             :    the inflation calculations, which use the slot times for historical
    2187             :    slots as input.  Therefore we need the same semantics as Agave, even
    2188             :    after the reduce_slot_time feature gates are active.
    2189             :    https://github.com/anza-xyz/agave/blob/4f8937b9568f7672ce0edcf67501666a95df015b/runtime/src/bank.rs#L2919-L2942 */
    2190             : static fd_slot_params_t
    2191             : restore_default_slot_params( fd_bank_t const *    bank,
    2192           0 :                              fd_genesis_t const * genesis ) {
    2193           0 :   fd_slot_params_t genesis_params = fd_slot_params_from_genesis( genesis );
    2194             : 
    2195             :   /* A reduction is effective if any reduce_slot_time gate with a slot
    2196             :      time no greater than the genesis slot time is effective.
    2197             :      https://github.com/anza-xyz/agave/blob/4f8937b9568f7672ce0edcf67501666a95df015b/runtime/src/slot_params.rs#L332-L349 */
    2198           0 :   int reduction_effective = fd_slot_params_any_reduction_effective( &bank->f.features,
    2199           0 :                                                                     &bank->f.epoch_schedule,
    2200           0 :                                                                     bank->f.slot,
    2201           0 :                                                                     genesis_params.ns_per_slot );
    2202             : 
    2203             :   /* If a reduction is effective, the restored bank's slot params no
    2204             :      longer describe the pre-reduction slots, so use the genesis slot
    2205             :      params as the default. */
    2206           0 :   if( reduction_effective ) return genesis_params;
    2207             : 
    2208             :   /* If a reduction is not effective, use the slot params from the
    2209             :      manifest.  These may differ from genesis for values changed by
    2210             :      older feature gates (e.g. hashes_per_tick). */
    2211           0 :   return bank->f.slot_params;
    2212           0 : }
    2213             : 
    2214             : /* refresh_vote_account_staked recomputes vote_account_staked from the
    2215             :    root stake delegations and seeds vote_account_inadmissible if stake
    2216             :    effective in the root's epoch was not admitted at its boundary.
    2217             :    Admitted implies staked, so only a non-admitted vote account pays
    2218             :    for the scan.  Called at boot and when the root crosses an epoch. */
    2219             : 
    2220             : static void
    2221             : refresh_vote_account_staked( fd_replay_tile_t * ctx,
    2222           0 :                              fd_bank_t *        bank ) {
    2223           0 :   fd_node_info_t node_info[1]; fd_node_info_read( node_info, ctx->node_info );
    2224           0 :   fd_pubkey_t const * vote_key = &node_info->vote_account;
    2225           0 :   int has_vote_key = !fd_pubkey_check_zero( vote_key );
    2226           0 :   int admitted     = has_vote_key && fd_vote_stakes_query_t_1( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, vote_key, NULL );
    2227           0 :   ctx->vote_account_staked = admitted;
    2228           0 :   if( FD_LIKELY( admitted || !has_vote_key ) ) return;
    2229             : 
    2230           0 :   uchar                      stake_history_data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
    2231           0 :   fd_stake_history_t         stake_history_[1];
    2232           0 :   fd_stake_history_t const * stake_history = fd_sysvar_stake_history_read( ctx->accdb, bank->accdb_fork_id, stake_history_data, stake_history_ );
    2233           0 :   fd_stake_history_entry_t   status        = fd_stake_delegations_vote_account_status(
    2234           0 :       fd_banks_stake_delegations_root_query( ctx->banks ), vote_key, bank->f.epoch, stake_history,
    2235           0 :       &bank->f.warmup_cooldown_rate_epoch );
    2236           0 :   ctx->vote_account_staked       = status.effective || status.activating;
    2237           0 :   ctx->vote_account_inadmissible = !!status.effective;
    2238           0 : }
    2239             : 
    2240             : static void
    2241             : init_after_snapshot( fd_replay_tile_t *  ctx,
    2242           0 :                      fd_stem_context_t * stem ) {
    2243             :   /* snapin built the root stake delegations while writing the accounts
    2244             :      db index.  Refresh finalizes them in memory. */
    2245           0 :   fd_bank_t * bank = fd_banks_bank_query( ctx->banks, FD_REPLAY_BOOT_BANK_IDX );
    2246           0 :   if( FD_UNLIKELY( !bank ) ) {
    2247           0 :     FD_LOG_CRIT(( "invariant violation: replay bank is NULL at bank index %lu", FD_REPLAY_BOOT_BANK_IDX ));
    2248           0 :   }
    2249             : 
    2250           0 :   char const * one_offs[ 16UL ];
    2251           0 :   for( ulong i=0UL; i<ctx->enable_features_cnt; i++ ) one_offs[ i ] = ctx->enable_features[ i ];
    2252           0 :   fd_features_enable_one_offs( &bank->f.features, one_offs, (uint)ctx->enable_features_cnt, 0UL );
    2253           0 :   FD_CHECK_CRIT( FD_FEATURE_ACTIVE_BANK( bank, alpenglow ), "alpenglow feature is not active" );
    2254           0 :   bank->f.alpenglow_migration_slot = fd_alpenglow_migration_slot( bank, ctx->accdb );
    2255             : 
    2256             :   /* Set slot params based on the feature gates in the snapshot,
    2257             :      and assert that these are consistent with the values from the
    2258             :      manifest. These assertions match Agave:
    2259             :      https://github.com/anza-xyz/agave/blob/v4.2/runtime/src/bank.rs#L4839-L4869 */
    2260           0 :   fd_slot_params_t manifest_params = bank->f.slot_params;
    2261           0 :   bank->f.slot_params_default      = restore_default_slot_params( bank, ctx->genesis );
    2262           0 :   bank->f.slot_params              = fd_slot_params_at_slot( bank, bank->f.slot );
    2263           0 :   FD_TEST( bank->f.slot_params.ns_per_slot    == manifest_params.ns_per_slot  );
    2264           0 :   FD_TEST( bank->f.slot_params.slots_per_year == manifest_params.slots_per_year );
    2265             : 
    2266           0 :   fd_runtime_update_next_leaders( bank, ctx->runtime_stack );
    2267           0 :   fd_runtime_update_leaders( bank, ctx->runtime_stack );
    2268             : 
    2269             :   /* Typically, when we cross an epoch boundary during normal
    2270             :      operation, we publish the stake weights for the new epoch.  But
    2271             :      since we are starting from a snapshot, we need to publish two
    2272             :      epochs worth of stake weights: the previous epoch (which is
    2273             :      needed for voting on the current epoch), and the current epoch
    2274             :      (which is needed for voting on the next epoch). */
    2275           0 :   publish_epoch_info( ctx, stem, bank, 0 );
    2276           0 :   publish_epoch_info( ctx, stem, bank, 1 );
    2277           0 :   fd_pubkey_t const * leader = fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, bank->f.slot );
    2278           0 :   FD_TEST( leader );
    2279           0 :   bank->leader = *leader;
    2280             : 
    2281             :   /* Agave asserts that the manifest leader_id matches the computed slot
    2282             :      leader.  At slot 0, Agave uses the highest staked node instead.
    2283             :      https://github.com/anza-xyz/agave/blob/v4.2.0/runtime/src/bank.rs#L2056-L2082 */
    2284           0 :   if( FD_UNLIKELY( bank->f.slot && !fd_pubkey_eq( leader, &ctx->manifest_leader_id ) ) ) {
    2285           0 :     FD_BASE58_ENCODE_32_BYTES( ctx->manifest_leader_id.uc, manifest_b58 );
    2286           0 :     FD_BASE58_ENCODE_32_BYTES( leader->uc,                 computed_b58 );
    2287           0 :     FD_LOG_ERR(( "invalid snapshot: leader_id %s does not match computed slot leader %s at slot %lu",
    2288           0 :                  manifest_b58, computed_b58, bank->f.slot ));
    2289           0 :   }
    2290             : 
    2291           0 :   fd_progcache_reset( ctx->progcache );
    2292           0 :   bank->progcache_fork_id = fd_progcache_fork_id_initial();
    2293             : 
    2294           0 :   bank->f.warmup_cooldown_rate_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, bank->f.features.reduce_stake_warmup_cooldown, NULL );
    2295           0 :   fd_stake_delegations_t * root_delegations = fd_banks_stake_delegations_root_query( ctx->banks );
    2296           0 :   uchar              stake_history_data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
    2297           0 :   fd_stake_history_t stake_history_[1];
    2298           0 :   fd_stake_history_t const * stake_history = fd_sysvar_stake_history_read( ctx->accdb, bank->accdb_fork_id, stake_history_data, stake_history_ );
    2299           0 :   fd_stake_delegations_refresh(
    2300           0 :       root_delegations,
    2301           0 :       bank->f.epoch,
    2302           0 :       stake_history, /* may be NULL */
    2303           0 :       &bank->f.warmup_cooldown_rate_epoch,
    2304           0 :       FD_FEATURE_ACTIVE_BANK( bank, remove_inactive_stakes ) );
    2305           0 :   fd_stake_history_entry_t totals = fd_stake_delegations_totals( root_delegations );
    2306           0 :   bank->f.total_effective_stake    = totals.effective;
    2307           0 :   bank->f.total_activating_stake   = totals.activating;
    2308           0 :   bank->f.total_deactivating_stake = totals.deactivating;
    2309             : 
    2310             :   /* Emit the stake-delegations boot baseline from the finalized root
    2311             :      cache (snapshot accepted and refreshed, or genesis loaded), so the
    2312             :      rows match the post-reconciliation cache rather than the raw
    2313             :      account stream. */
    2314           0 :   if( FD_UNLIKELY( ctx->report_runtime_diffs ) ) {
    2315           0 :     fd_stake_delegations_iter_t iter_[1];
    2316           0 :     for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, root_delegations );
    2317           0 :          !fd_stake_delegations_iter_done( iter );
    2318           0 :          fd_stake_delegations_iter_next( iter ) ) {
    2319           0 :       fd_stake_delegation_t const * d = fd_stake_delegations_iter_ele( iter );
    2320           0 :       ulong ae = d->activation_epoch  ==(ushort)USHORT_MAX ? ULONG_MAX : (ulong)d->activation_epoch;
    2321           0 :       ulong de = d->deactivation_epoch==(ushort)USHORT_MAX ? ULONG_MAX : (ulong)d->deactivation_epoch;
    2322           0 :       fd_event_runtime_stake_delegation_bootup_emit( bank->f.slot, bank->f.epoch, d->stake_account.uc,
    2323           0 :                                                      d->vote_account.uc, d->stake, ae, de, d->credits_observed );
    2324           0 :     }
    2325           0 :   }
    2326             : 
    2327           0 :   fd_vote_stakes_refresh( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, ctx->accdb, bank->accdb_fork_id );
    2328             : 
    2329           0 :   refresh_vote_account_staked( ctx, bank );
    2330             : 
    2331             :   /* After both snapshots have been loaded in, we can determine if we should
    2332             :      start distributing rewards. */
    2333             : 
    2334           0 :   fd_rewards_recalculate_partitioned_rewards( bank, ctx->accdb, ctx->runtime_stack, ctx->capture_ctx );
    2335             : 
    2336             :   /* Signals fd_startup_gate */
    2337           0 :   FD_MGAUGE_SET( REPLAY, IS_RUNTIME_LOADED, 1UL );
    2338           0 :   metrics_trigger( ctx, FD_METRICS_SAMPLE_REASON_REPLAY_READY, bank->f.slot );
    2339           0 : }
    2340             : 
    2341             : static ulong
    2342             : enforce_nanosecond_clock_bounds( fd_replay_tile_t * ctx,
    2343             :                                  fd_bank_t *        bank,
    2344           0 :                                  ulong              now_nanos ) {
    2345           0 :   long lo, hi;
    2346           0 :   fd_alpenglow_producer_time_bounds( bank, ctx->accdb, &lo, &hi );
    2347           0 :   return (ulong)fd_long_max( lo, fd_long_min( hi, (long)fd_ulong_min( now_nanos, (ulong)LONG_MAX ) ) );
    2348           0 : }
    2349             : 
    2350             : static void
    2351             : process_motor_done( fd_replay_tile_t *                 ctx,
    2352             :                     fd_stem_context_t *                stem,
    2353           0 :                     fd_poh_leader_slot_ended_t const * slot_ended ) {
    2354             : 
    2355           0 :   FD_TEST( ctx->is_booted );
    2356           0 :   FD_TEST( ctx->is_leader );
    2357           0 :   FD_TEST( ctx->leader_bank!=NULL );
    2358             : 
    2359           0 :   FD_TEST( ctx->highwater_leader_slot>=slot_ended->slot );
    2360           0 :   FD_TEST( ctx->next_leader_slot>ctx->highwater_leader_slot );
    2361             : 
    2362           0 :   if( FD_LIKELY( ctx->leader_stats.slot==slot_ended->slot ) ) {
    2363           0 :     ctx->leader_stats.microblock_count = slot_ended->microblock_count;
    2364           0 :     ctx->leader_stats.pack_block_cost  = slot_ended->pack_block_cost;
    2365           0 :     ctx->leader_stats.pack_data_bytes  = slot_ended->pack_data_bytes;
    2366           0 :     ctx->leader_stats.bundle_txn_count = slot_ended->bundle_txn_count;
    2367           0 :     ctx->leader_stats.pack_end_reason  = slot_ended->pack_end_reason;
    2368           0 :     ctx->leader_stats.pack_start_nanos = slot_ended->pack_start_ns;
    2369           0 :     ctx->leader_stats.pack_end_nanos   = slot_ended->pack_end_ns;
    2370           0 :     ctx->leader_stats.timing_table_idx = slot_ended->timing_table_idx;
    2371           0 :   }
    2372             : 
    2373             :   /* unpin when we know leader bank done executing txns */
    2374           0 :   fd_cost_tracker_store_unpin( fd_bank_cost_tracker( ctx->leader_bank ), ctx->leader_bank->cost_tracker_fork_id );
    2375             : 
    2376           0 :   if( FD_UNLIKELY( !slot_ended->completed ) ) {
    2377             :     /* The leader slot was aborted by a reset mid-production.  The
    2378             :        block-complete entry was never emitted, so no slot-complete FEC
    2379             :        (and thus no block id) will ever arrive. */
    2380           0 :     ulong bank_idx = ctx->leader_bank->idx;
    2381           0 :     metrics_trigger( ctx, FD_METRICS_SAMPLE_REASON_LEADER_END, slot_ended->slot );
    2382           0 :     ctx->leader_bank->refcnt--;
    2383           0 :     ctx->leader_bank = NULL;
    2384           0 :     ctx->recv_poh    = 0;
    2385           0 :     ctx->is_leader   = 0;
    2386           0 :     mark_bank_dead( ctx, stem, bank_idx, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_RESET, NULL );
    2387           0 :     return;
    2388           0 :   }
    2389             : 
    2390             :   /* Update the poh hash in the bank.  We will want to maintain a refcnt
    2391             :      on the bank until we have received the block id for the block after
    2392             :      it has been shredded. */
    2393             : 
    2394           0 :   memcpy( &ctx->leader_bank->f.poh, slot_ended->blockhash, sizeof(fd_hash_t) );
    2395             : 
    2396           0 :   ctx->recv_poh = 1;
    2397             : 
    2398           0 :   ctx->leader_bank->last_transaction_finished_nanos = fd_clock_tile_now( ctx->clock );
    2399           0 :   fd_sched_block_add_done( ctx->sched, ctx->leader_bank->idx, ctx->leader_bank->parent_idx, ctx->leader_bank->f.slot );
    2400             : 
    2401             :   /* cache fees before runtime zeros */
    2402             : 
    2403           0 :   ctx->leader_execution_fees = ctx->leader_bank->f.execution_fees;
    2404           0 :   ctx->leader_priority_fees  = ctx->leader_bank->f.priority_fees;
    2405           0 :   ctx->leader_tips           = ctx->leader_bank->f.tips;
    2406             : 
    2407           0 :   fd_block_footer_t * footer = ctx->leader_footer;
    2408           0 :   fd_memset( footer, 0, sizeof(fd_block_footer_t) );
    2409           0 :   footer->block_producer_time_nanos = enforce_nanosecond_clock_bounds( ctx, ctx->leader_bank, (ulong)fd_clock_tile_now( ctx->clock ) );
    2410             : 
    2411           0 :   ulong migration_slot = fd_alpenglow_migration_slot( ctx->leader_bank, ctx->accdb );
    2412           0 :   construct_footer_certs( ctx, ctx->leader_bank->f.slot, migration_slot, footer );
    2413             : 
    2414             :   /* The block goes out regardless: the certs are already committed to
    2415             :      the bank hash, so there is nothing left to fall back to. */
    2416             :   /* The footer cache stays out of this while another bank's slices may
    2417             :      be writing its memo. */
    2418           0 :   if( FD_UNLIKELY( fd_runtime_block_execute_finalize( ctx->leader_bank, ctx->accdb, ctx->capture_ctx, footer, ctx->shred_version, ctx->rewards_in_flight_cnt ? NULL : ctx->footer_cache ) ) ) {
    2419           0 :     FD_LOG_WARNING(( "slot %lu: our own block footer certs did not apply; the block we produce will be dead to the cluster", ctx->leader_bank->f.slot ));
    2420           0 :   }
    2421           0 :   footer->bank_hash = ctx->leader_bank->f.bank_hash;
    2422             : 
    2423           0 :   publish_leader_footer( ctx, stem, ctx->leader_bank->f.slot, footer );
    2424           0 : }
    2425             : 
    2426             : static void
    2427             : boot_genesis( fd_replay_tile_t *        ctx,
    2428             :               fd_stem_context_t *       stem,
    2429           0 :               fd_genesis_meta_t const * meta ) {
    2430             : 
    2431           0 :   if( FD_UNLIKELY( ctx->wfs_enabled ) ) {
    2432           0 :     FD_LOG_ERR(( "wait_for_supermajority is enabled but the validator is booting from genesis. "
    2433           0 :                  "Remove [consensus.wait_for_supermajority_at_slot] and "
    2434           0 :                  "[consensus.wait_for_supermajority_with_bank_hash] configuration and restart." ));
    2435           0 :   }
    2436             : 
    2437             :   /* Firedancer only boots Alpenglow clusters from genesis.  The
    2438             :      genesis certificate certifies slot 0 with a zero block id. */
    2439           0 :   fd_memset( &ctx->initial_block_id, 0, sizeof(fd_hash_t) );
    2440             : 
    2441             :   /* If we are bootstrapping, we can't wait to wait for our identity
    2442             :      vote to be rooted as this creates a circular dependency. */
    2443           0 :   ctx->identity_vote_rooted = 1;
    2444             : 
    2445           0 :   ctx->caught_up = 1;
    2446           0 :   FD_MGAUGE_SET( REPLAY, IS_CAUGHT_UP, 1UL );
    2447           0 :   metrics_trigger( ctx, FD_METRICS_SAMPLE_REASON_CAUGHT_UP, 0UL );
    2448             : 
    2449           0 :   ctx->hard_fork_cnt = 0UL;
    2450             : 
    2451           0 :   uchar const * genesis_blob = (uchar const *)( meta+1 );
    2452           0 :   FD_TEST( meta->bootstrap && meta->has_lthash );
    2453           0 :   FD_TEST( fd_genesis_parse( ctx->genesis, genesis_blob, meta->blob_sz ) );
    2454             : 
    2455           0 :   fd_bank_t * bank = fd_banks_init_bank( ctx->banks );
    2456           0 :   FD_TEST( bank );
    2457           0 :   bank->f.slot = 0UL;
    2458           0 :   FD_TEST( bank->idx==FD_REPLAY_BOOT_BANK_IDX );
    2459             : 
    2460           0 :   FD_TEST( meta->accdb_root.val!=USHORT_MAX );
    2461           0 :   bank->accdb_fork_id = meta->accdb_root;
    2462           0 :   bank->parent_accdb_fork_id = bank->accdb_fork_id;
    2463             : 
    2464           0 :   fd_runtime_read_genesis( ctx->banks, bank, ctx->accdb, NULL, &meta->genesis_hash, &meta->lthash, ctx->genesis, genesis_blob, ctx->runtime_stack );
    2465           0 :   FD_TEST( fd_sysvar_cache_validate( bank, ctx->accdb ) );
    2466             : 
    2467           0 :   fd_txncache_reset( ctx->txncache );
    2468           0 :   bank->txncache_fork_id  = fd_txncache_attach_child ( ctx->txncache, (fd_txncache_fork_id_t){USHORT_MAX} );
    2469           0 :   bank->progcache_fork_id = fd_progcache_attach_child( ctx->progcache, fd_progcache_fork_id_initial()     );
    2470             : 
    2471           0 :   fd_hash_t const * block_hash = fd_blockhashes_peek_last_hash( &bank->f.block_hash_queue );
    2472           0 :   fd_txncache_finalize_fork( ctx->txncache, bank->txncache_fork_id, 0UL, block_hash->uc );
    2473             : 
    2474             :   /* We call this after fd_runtime_read_genesis, which sets up the
    2475             :      slot_bank needed in blockstore_init. */
    2476           0 :   init_after_snapshot( ctx, stem );
    2477             : 
    2478           0 :   ctx->published_root_slot = 0UL;
    2479           0 :   fd_sched_block_add_done( ctx->sched, bank->idx, ULONG_MAX, 0UL );
    2480             : 
    2481           0 :   bank->f.block_height = 1UL;
    2482             : 
    2483           0 :   ctx->consensus_root          = ctx->initial_block_id;
    2484           0 :   ctx->consensus_root_slot     = 0UL;
    2485           0 :   ctx->finalized_block_id_lo   = fd_block_id( 0UL, ctx->initial_block_id.uc );
    2486           0 :   ctx->finalized_block_id_hi   = ctx->finalized_block_id_lo;
    2487           0 :   ctx->notified_root           = ctx->initial_block_id;
    2488           0 :   ctx->notified_root_slot      = 0UL;
    2489           0 :   ctx->notified_root_bank      = bank;
    2490           0 :   ctx->published_root_slot     = 0UL;
    2491           0 :   ctx->published_root_bank_idx = 0UL;
    2492           0 :   if( FD_UNLIKELY( ctx->snapmk.full_interval_blocks ) ) {
    2493           0 :     ulong interval = ctx->snapmk.full_interval_blocks;
    2494           0 :     ctx->snapmk.next_full_block_height = ((bank->f.block_height/interval)+1UL)*interval;
    2495           0 :   }
    2496           0 :   if( FD_UNLIKELY( ctx->snapmk.incremental_interval_blocks ) ) {
    2497           0 :     ulong interval = ctx->snapmk.incremental_interval_blocks;
    2498           0 :     ctx->snapmk.next_incremental_block_height = ((bank->f.block_height/interval)+1UL)*interval;
    2499           0 :   }
    2500             : 
    2501           0 :   ctx->reset_slot      = 0UL;
    2502           0 :   ctx->epoch_end_slot  = 0UL;
    2503           0 :   ctx->slots_per_epoch = 0UL;
    2504           0 :   ctx->ns_per_slot     = 0UL;
    2505             : 
    2506           0 :   ctx->has_cluster_type = 1;
    2507             : 
    2508           0 :   ctx->is_booted = 1;
    2509             : 
    2510           0 :   fd_hash_t initial_block_id = ctx->initial_block_id;
    2511             : 
    2512           0 :   fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ 0 ];
    2513           0 :   block_id_ele->latest_mr      = initial_block_id;
    2514           0 :   block_id_ele->dmr            = initial_block_id;
    2515           0 :   block_id_ele->slot           = 0UL;
    2516           0 :   block_id_ele->bank_seq       = bank->bank_seq;
    2517           0 :   block_id_ele->block_id_seen  = 1;
    2518           0 :   block_id_ele->latest_fec_idx = 0U;
    2519           0 :   block_id_ele->block_info     = fd_block_id( 0UL, initial_block_id.uc );
    2520           0 :   bank->f.block_id             = initial_block_id;
    2521           0 :   FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, block_id_ele, ctx->block_id_arr ) );
    2522             : 
    2523           0 :   fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
    2524           0 :   cost_tracker_snap( bank, slot_info );
    2525             : 
    2526           0 :   slot_info->identity_balance = fd_accdb_lamports( ctx->accdb, bank->accdb_fork_id, ctx->identity_pubkey->uc );
    2527             : 
    2528           0 :   FD_LOG_INFO(( "replay ready at slot %lu (%.3f s after snapshot done, %.3f s since boot)",
    2529           0 :                 0UL, 0.0, (double)(fd_log_wallclock()-ctx->boot_timestamp_nanos)/1e9 ));
    2530             : 
    2531           0 :   fd_block_footer_t footer = { .bank_hash = bank->f.bank_hash  }; /* dummy footer for genesis slot */
    2532           0 :   publish_slot_completed( ctx, stem, bank, 1, 0 /* is_leader */, 0, 0, &footer );
    2533           0 :   publish_root_advanced( ctx, stem, bank );
    2534             : 
    2535           0 : }
    2536             : 
    2537             : static inline void
    2538           0 : maybe_verify_cluster_type( fd_replay_tile_t * ctx ) {
    2539           0 :   if( FD_UNLIKELY( !ctx->has_cluster_type || !ctx->has_genesis_hash ) ) {
    2540           0 :     return;
    2541           0 :   }
    2542             : 
    2543           0 :   FD_BASE58_ENCODE_32_BYTES( ctx->genesis_hash->uc, hash_cstr );
    2544           0 :   ulong cluster = fd_genesis_cluster_identify( hash_cstr );
    2545             :   /* Map pyth-related clusters to unknown. */
    2546           0 :   switch( cluster ) {
    2547           0 :     case FD_CLUSTER_PYTHNET:
    2548           0 :     case FD_CLUSTER_PYTHTEST:
    2549           0 :       cluster = FD_CLUSTER_UNKNOWN;
    2550           0 :   }
    2551             : 
    2552           0 :   if( FD_UNLIKELY( cluster!=ctx->cluster_type ) ) {
    2553           0 :     FD_LOG_ERR(( "Your genesis.bin file at `%s` has a genesis hash of `%s` which means the cluster is %s "
    2554           0 :                  "but the snapshot you loaded is for a different cluster %s. If you are trying to join the "
    2555           0 :                  "%s cluster, you can delete the genesis.bin file and restart the node to download the correct "
    2556           0 :                  "genesis file automatically.",
    2557           0 :                  ctx->genesis_path,
    2558           0 :                  hash_cstr,
    2559           0 :                  fd_genesis_cluster_name( cluster ),
    2560           0 :                  fd_genesis_cluster_name( ctx->cluster_type ),
    2561           0 :                  fd_genesis_cluster_name( cluster ) ));
    2562           0 :   }
    2563           0 : }
    2564             : 
    2565             : static void
    2566             : on_snapshot_message( fd_replay_tile_t *  ctx,
    2567             :                      fd_stem_context_t * stem,
    2568             :                      ulong               in_idx,
    2569             :                      ulong               chunk,
    2570           0 :                      ulong               sig ) {
    2571           0 :   ulong msg = fd_ssmsg_sig_message( sig );
    2572           0 :   if( FD_LIKELY( msg==FD_SSMSG_DONE ) ) {
    2573           0 :     long snapshot_done_nanos = fd_log_wallclock();
    2574             : 
    2575             :     /* An end of message notification indicates the snapshot is loaded.
    2576             :        Replay is able to start executing from this point onwards. */
    2577             :     /* TODO: replay should finish booting. Could make replay a
    2578             :        state machine and set the state here accordingly. */
    2579           0 :     ctx->is_booted = 1;
    2580             : 
    2581           0 :     fd_bank_t * bank = fd_banks_bank_query( ctx->banks, FD_REPLAY_BOOT_BANK_IDX );
    2582           0 :     if( FD_UNLIKELY( !bank ) ) {
    2583           0 :       FD_LOG_CRIT(( "invariant violation: bank is NULL for bank index %lu", FD_REPLAY_BOOT_BANK_IDX ));
    2584           0 :     }
    2585             : 
    2586           0 :     ulong snapshot_slot = bank->f.slot;
    2587             : 
    2588           0 :     fd_hash_t bank_hash = bank->f.bank_hash;
    2589           0 :     if( FD_UNLIKELY( ctx->wfs_enabled ) ) {
    2590             :       /* Classify boot against WFS (see fd_wfs.h). */
    2591           0 :       int wfs_mode = fd_wfs_mode( ctx->wait_for_supermajority_at_slot,
    2592           0 :                                   ctx->wfs_hash_is_zero,
    2593           0 :                                   (ulong)ctx->expected_shred_version,
    2594           0 :                                   snapshot_slot );
    2595           0 :       switch( wfs_mode ) {
    2596           0 :         case FD_WFS_MODE_MATCH: {
    2597           0 :           if( FD_UNLIKELY( memcmp( ctx->expected_bank_hash.uc, bank_hash.uc, sizeof(fd_hash_t) ) ) ) {
    2598           0 :             FD_BASE58_ENCODE_32_BYTES( ctx->expected_bank_hash.uc, expected_bank_hash_cstr );
    2599           0 :             FD_BASE58_ENCODE_32_BYTES( bank_hash.uc,               actual_bank_hash_cstr );
    2600           0 :             FD_LOG_ERR(( "[consensus.wait_for_supermajority_with_bank_hash] expected_bank_hash=%s does not match snapshot slot"
    2601           0 :                          "=%lu bank_hash=%s. If you are loading a snapshot from the network, check that the slot matches the "
    2602           0 :                          "cluster restart slot.", expected_bank_hash_cstr, snapshot_slot, actual_bank_hash_cstr ));
    2603           0 :           }
    2604             :           /* Booting at the WFS target does not require our vote to be rooted. */
    2605           0 :           if( FD_UNLIKELY( ctx->wait_for_vote_to_start_leader==FD_BOOLAU_AUTO ) ) {
    2606           0 :             FD_LOG_NOTICE(( "auto-overriding wait_for_vote_to_start_leader to false "
    2607           0 :                             "(WFS target slot %lu, boot slot %lu)",
    2608           0 :                             ctx->wait_for_supermajority_at_slot, snapshot_slot ));
    2609           0 :             ctx->wait_for_vote_to_start_leader = FD_BOOLAU_FALSE;
    2610           0 :           }
    2611           0 :           if( FD_LIKELY( !ctx->wfs_complete ) ) {
    2612           0 :             FD_LOG_NOTICE(( "waiting for supermajority at snapshot slot %lu", snapshot_slot ));
    2613           0 :           }
    2614           0 :           break;
    2615           0 :         }
    2616           0 :         case FD_WFS_MODE_ERROR: {
    2617           0 :           FD_LOG_ERR(( "snapshot slot %lu is behind [consensus.wait_for_supermajority_at_slot] %lu "
    2618           0 :                        "and no incremental snapshot bridged the gap. "
    2619           0 :                        "Provide a snapshot at that slot, or clear it and "
    2620           0 :                        "[consensus.wait_for_supermajority_with_bank_hash] to disable WFS.",
    2621           0 :                        snapshot_slot, ctx->wait_for_supermajority_at_slot ));
    2622           0 :           break;
    2623           0 :         }
    2624           0 :         case FD_WFS_MODE_NOOP: {
    2625           0 :           FD_LOG_WARNING(( "snapshot slot %lu is ahead of [consensus.wait_for_supermajority_at_slot] %lu, "
    2626           0 :                            "skipping wait for supermajority and bank hash verification",
    2627           0 :                            snapshot_slot, ctx->wait_for_supermajority_at_slot ));
    2628           0 :           ctx->wfs_complete = 1;
    2629           0 :           ctx->wait_for_vote_to_start_leader = wfs_resolve_leader_gate_auto( ctx->wait_for_vote_to_start_leader );
    2630           0 :           break;
    2631           0 :         }
    2632           0 :         default: {
    2633             :           /* DISABLED and UNRESOLVED are unreachable under wfs_enabled. */
    2634           0 :           FD_LOG_CRIT(( "unexpected WFS mode %d (%s) at boot slot %lu",
    2635           0 :                         wfs_mode, fd_wfs_mode_str( wfs_mode ), snapshot_slot ));
    2636           0 :         }
    2637           0 :       }
    2638             : 
    2639           0 :       if( FD_UNLIKELY( ctx->wait_for_vote_to_start_leader==FD_BOOLAU_AUTO ) ) {
    2640           0 :         FD_LOG_CRIT(( "wait_for_vote_to_start_leader unresolved (auto) after WFS boot classification" ));
    2641           0 :       }
    2642           0 :     }
    2643             : 
    2644             :     /* Manifest message must arrive before DONE */
    2645           0 :     if( FD_UNLIKELY( !ctx->has_expected_genesis_timestamp ) ) {
    2646           0 :       FD_LOG_CRIT(( "snapshot DONE received before manifest" ));
    2647           0 :     }
    2648             : 
    2649             :     /* FIXME: This is a hack when the block id of the snapshot slot
    2650             :        is not provided in the snapshot (Agave versions <4.1). A
    2651             :        possible solution is to get the block id of the snapshot slot
    2652             :        from repair. */
    2653           0 :     fd_hash_t manifest_block_id = ctx->has_manifest_block_id ? ctx->manifest_block_id : ctx->initial_block_id;
    2654             : 
    2655           0 :     FD_TEST( fd_sysvar_cache_validate( bank, ctx->accdb ) );
    2656             :     /* Agave zeroes manifest rent_params; reload from sysvar account */
    2657           0 :     FD_TEST( fd_sysvar_rent_read( ctx->accdb, bank->accdb_fork_id, &bank->f.rent ) );
    2658             : 
    2659           0 :     ctx->consensus_root          = manifest_block_id;
    2660           0 :     ctx->consensus_root_slot     = snapshot_slot;
    2661           0 :     ctx->finalized_block_id_lo   = fd_block_id( snapshot_slot, manifest_block_id.uc );
    2662           0 :     ctx->finalized_block_id_hi   = ctx->finalized_block_id_lo;
    2663           0 :     ctx->notified_root           = manifest_block_id;
    2664           0 :     ctx->notified_root_slot      = snapshot_slot;
    2665           0 :     ctx->notified_root_bank      = bank;
    2666           0 :     ctx->published_root_slot     = ctx->consensus_root_slot;
    2667           0 :     ctx->published_root_bank_idx = 0UL;
    2668           0 :     if( FD_UNLIKELY( ctx->snapmk.full_interval_blocks ) ) {
    2669           0 :       ulong interval = ctx->snapmk.full_interval_blocks;
    2670           0 :       ctx->snapmk.next_full_block_height = ((bank->f.block_height/interval)+1UL)*interval;
    2671           0 :     }
    2672           0 :     if( FD_UNLIKELY( ctx->snapmk.incremental_interval_blocks ) ) {
    2673           0 :       ulong interval = ctx->snapmk.incremental_interval_blocks;
    2674           0 :       ctx->snapmk.next_incremental_block_height = ((bank->f.block_height/interval)+1UL)*interval;
    2675           0 :     }
    2676             : 
    2677           0 :     ctx->reset_slot = snapshot_slot;
    2678           0 :     refresh_epoch_info( ctx, bank, snapshot_slot );
    2679             : 
    2680           0 :     fd_sched_block_add_done( ctx->sched, bank->idx, ULONG_MAX, snapshot_slot );
    2681           0 :     FD_TEST( bank->idx==0UL );
    2682             : 
    2683           0 :     fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ 0 ];
    2684           0 :     block_id_ele->latest_mr      = manifest_block_id;
    2685           0 :     block_id_ele->dmr            = manifest_block_id;
    2686           0 :     block_id_ele->slot           = snapshot_slot;
    2687           0 :     block_id_ele->bank_seq       = bank->bank_seq;
    2688           0 :     block_id_ele->block_id_seen  = 1;
    2689           0 :     block_id_ele->latest_fec_idx = 0U;
    2690           0 :     bank->f.block_id             = manifest_block_id;
    2691             : 
    2692           0 :     block_id_ele->block_info = fd_block_id( snapshot_slot, manifest_block_id.uc );
    2693           0 :     FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, block_id_ele, ctx->block_id_arr ) );
    2694             : 
    2695             :     /* We call this after fd_runtime_read_genesis, which sets up the
    2696             :        slot_bank needed in blockstore_init. */
    2697           0 :     init_after_snapshot( ctx, stem );
    2698             : 
    2699           0 :     long now = fd_log_wallclock();
    2700           0 :     FD_LOG_INFO(( "replay ready at slot %lu (%.3f s after snapshot done, %.3f s since boot)",
    2701           0 :                   snapshot_slot, (double)(now-snapshot_done_nanos)/1e9, (double)(now-ctx->boot_timestamp_nanos)/1e9 ));
    2702             : 
    2703           0 :     publish_boot_slot( ctx, stem, bank );
    2704             : 
    2705             : 
    2706           0 :     return;
    2707           0 :   }
    2708             : 
    2709           0 :   switch( msg ) {
    2710           0 :     case FD_SSMSG_MANIFEST_FULL:
    2711           0 :     case FD_SSMSG_MANIFEST_INCREMENTAL: {
    2712             :       /* We may either receive a full snapshot manifest or an
    2713             :          incremental snapshot manifest.  Note that this external message
    2714             :          id is only used temporarily because replay cannot yet receive
    2715             :          the firedancer-internal snapshot manifest message. */
    2716           0 :       if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark ) )
    2717           0 :         FD_LOG_ERR(( "chunk %lu from in %d corrupt, not in range [%lu,%lu]", chunk, ctx->in_kind[ in_idx ], ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
    2718             : 
    2719             :       /* Malformed manifests are rejected recoverably by snapin via
    2720             :          fd_ssload_manifest_validate.  If recover fails here, then the
    2721             :          bank is partially mutated, and we must abort. */
    2722           0 :       if( FD_UNLIKELY( fd_ssload_recover( fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ),
    2723           0 :                                           ctx->banks,
    2724           0 :                                           fd_banks_bank_query( ctx->banks, FD_REPLAY_BOOT_BANK_IDX ),
    2725           0 :                                           ctx->blockhash_seed ) ) ) {
    2726           0 :         FD_LOG_ERR(( "Snapshot manifest recovery failed, aborting." ));
    2727           0 :       }
    2728             : 
    2729           0 :       ctx->has_cluster_type = 1;
    2730           0 :       ctx->cluster_type     = fd_banks_bank_query( ctx->banks, FD_REPLAY_BOOT_BANK_IDX )->f.cluster_type;
    2731             : 
    2732           0 :       fd_snapshot_manifest_t const * manifest = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
    2733             :       /* hard_fork_cnt already validated by fd_ssload_recover. */
    2734           0 :       ctx->hard_fork_cnt = manifest->hard_fork_cnt;
    2735           0 :       for( ulong i=0UL; i<manifest->hard_fork_cnt; i++ ) {
    2736           0 :         ctx->hard_forks[ i ] = manifest->hard_forks[ i ];
    2737           0 :       }
    2738           0 :       ctx->has_expected_genesis_timestamp = 1;
    2739           0 :       ctx->expected_genesis_timestamp     = manifest->creation_time_seconds;
    2740           0 :       ctx->has_manifest_block_id          = manifest->has_block_id;
    2741           0 :       if( manifest->has_block_id ) memcpy( ctx->manifest_block_id.uc, manifest->block_id, 32UL );
    2742           0 :       memcpy( ctx->manifest_leader_id.uc, manifest->leader_id, 32UL );
    2743           0 :       if( FD_UNLIKELY( msg==FD_SSMSG_MANIFEST_FULL ) ) {
    2744           0 :         ctx->snapmk.base_slot = manifest->slot;
    2745           0 :       }
    2746           0 :       break;
    2747           0 :     }
    2748           0 :     default: {
    2749           0 :       FD_LOG_ERR(( "Received unknown snapshot message with msg %lu", msg ));
    2750           0 :       return;
    2751           0 :     }
    2752           0 :   }
    2753             : 
    2754           0 :   return;
    2755           0 : }
    2756             : 
    2757             : static void
    2758             : dispatch_task( fd_replay_tile_t *  ctx,
    2759             :                fd_stem_context_t * stem,
    2760           0 :                fd_sched_task_t *   task ) {
    2761             : 
    2762           0 :   switch( task->task_type ) {
    2763           0 :     case FD_SCHED_TT_TXN_EXEC: {
    2764           0 :       fd_txn_p_t * txn_p = fd_sched_get_txn( ctx->sched, task->txn_exec->txn_idx );
    2765             : 
    2766           0 :       fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->txn_exec->bank_idx );
    2767           0 :       FD_TEST( bank );
    2768             : 
    2769             :       /* Add the transaction to the block dumper if necessary. This
    2770             :          logic doesn't need to be fork-aware since it's only meant to
    2771             :          be used in backtest. */
    2772           0 :       if( FD_UNLIKELY( ctx->dump_proto_ctx && ctx->dump_proto_ctx->dump_block_to_pb ) ) {
    2773           0 :         fd_dump_block_to_protobuf_collect_tx( ctx->block_dump_ctx, txn_p );
    2774           0 :       }
    2775             : 
    2776           0 :       bank->refcnt++;
    2777             : 
    2778           0 :       fd_cost_tracker_store_pin( fd_bank_cost_tracker( bank ), bank->cost_tracker_fork_id );
    2779             : 
    2780           0 :       if( FD_UNLIKELY( !bank->first_transaction_scheduled_nanos ) ) bank->first_transaction_scheduled_nanos = fd_clock_tile_now( ctx->clock );
    2781             : 
    2782           0 :       FD_TEST( task->txn_exec->exec_idx<ctx->exec_cnt );
    2783           0 :       fd_replay_out_link_t * exec_out = &ctx->exec_out[ task->txn_exec->exec_idx ];
    2784           0 :       fd_execrp_txn_exec_msg_t * exec_msg = fd_chunk_to_laddr( exec_out->mem, exec_out->chunk );
    2785           0 :       fd_txn_p_copy( exec_msg->txn, txn_p );
    2786           0 :       exec_msg->bank_idx = task->txn_exec->bank_idx;
    2787           0 :       exec_msg->txn_idx  = task->txn_exec->txn_idx;
    2788           0 :       memcpy( exec_msg->fec_merkle_root, ctx->block_id_arr[ task->txn_exec->bank_idx ].latest_mr.uc, 32UL );
    2789           0 :       fd_sched_txn_info_t const * txn_info = fd_sched_get_txn_info( ctx->sched, task->txn_exec->txn_idx );
    2790           0 :       exec_msg->index_in_slot = txn_info->index_in_slot;
    2791           0 :       exec_msg->want_alts     = !!(txn_info->flags & FD_SCHED_TXN_ALT_UNRESOLVED);
    2792           0 :       if( FD_UNLIKELY( ctx->capture_ctx ) ) {
    2793           0 :         exec_msg->capture_txn_idx = ctx->capture_ctx->current_txn_idx++;
    2794           0 :       }
    2795           0 :       fd_stem_publish( stem, exec_out->idx, (FD_EXECRP_TT_TXN_EXEC<<32) | task->txn_exec->exec_idx, exec_out->chunk, sizeof(*exec_msg), 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
    2796           0 :       exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*exec_msg), exec_out->chunk0, exec_out->wmark );
    2797           0 :       break;
    2798           0 :     }
    2799           0 :     case FD_SCHED_TT_CERT_VERIFY: {
    2800           0 :       fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->cert_verify->bank_idx );
    2801           0 :       FD_TEST( bank );
    2802             :       /* Capture and runtime diff runs verify every cert on this tile at
    2803             :          block end, so the task completes with nothing sent. */
    2804           0 :       if( FD_UNLIKELY( ctx->capture_ctx || ctx->report_runtime_diffs ) ) {
    2805           0 :         FD_TEST( !fd_sched_task_done( ctx->sched, FD_SCHED_TT_CERT_VERIFY, ULONG_MAX, task->cert_verify->exec_idx, NULL ) );
    2806           0 :         break;
    2807           0 :       }
    2808           0 :       fd_block_footer_t const * footer = fd_sched_get_footer( ctx->sched, bank->idx );
    2809           0 :       FD_TEST( footer );
    2810           0 :       fd_block_footer_cert_t const * cert  [ FD_ALPENGLOW_FOOTER_CERT_MAX ];
    2811           0 :       uint                           tag   [ FD_ALPENGLOW_FOOTER_CERT_MAX ];
    2812           0 :       ulong                          quorum[ FD_ALPENGLOW_FOOTER_CERT_MAX ];
    2813           0 :       ulong cnt = fd_alpenglow_footer_certs( footer, cert, tag, quorum );
    2814           0 :       ulong k   = task->cert_verify->cert_idx;
    2815           0 :       FD_TEST( k<cnt && k==ctx->footer_end[ bank->idx ].cert_next );
    2816           0 :       replay_cert_publish( ctx, stem, bank, cert[ k ], tag[ k ], quorum[ k ], task->cert_verify->exec_idx );
    2817           0 :       ctx->footer_end[ bank->idx ].cert_next = k+1UL;
    2818           0 :       break;
    2819           0 :     }
    2820           0 :     case FD_SCHED_TT_TXN_SIGVERIFY: {
    2821           0 :       fd_txn_p_t * txn_p = fd_sched_get_txn( ctx->sched, task->txn_sigverify->txn_idx );
    2822             : 
    2823           0 :       fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->txn_sigverify->bank_idx );
    2824           0 :       FD_TEST( bank );
    2825           0 :       bank->refcnt++;
    2826             : 
    2827           0 :       FD_TEST( task->txn_sigverify->exec_idx<ctx->exec_cnt );
    2828           0 :       fd_replay_out_link_t * exec_out = &ctx->exec_out[ task->txn_sigverify->exec_idx ];
    2829           0 :       fd_execrp_txn_sigverify_msg_t * exec_msg = fd_chunk_to_laddr( exec_out->mem, exec_out->chunk );
    2830           0 :       fd_txn_p_copy( exec_msg->txn, txn_p );
    2831           0 :       exec_msg->bank_idx = task->txn_sigverify->bank_idx;
    2832           0 :       exec_msg->txn_idx  = task->txn_sigverify->txn_idx;
    2833           0 :       fd_stem_publish( stem, exec_out->idx, (FD_EXECRP_TT_TXN_SIGVERIFY<<32) | task->txn_sigverify->exec_idx, exec_out->chunk, sizeof(*exec_msg), 0UL, 0UL, 0UL );
    2834           0 :       exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*exec_msg), exec_out->chunk0, exec_out->wmark );
    2835           0 :       break;
    2836           0 :     };
    2837           0 :     case FD_SCHED_TT_POH_HASH: {
    2838           0 :       fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->poh_hash->bank_idx );
    2839           0 :       FD_TEST( bank );
    2840           0 :       bank->refcnt++;
    2841             : 
    2842           0 :       FD_TEST( task->poh_hash->exec_idx<ctx->exec_cnt );
    2843           0 :       fd_replay_out_link_t * exec_out = &ctx->exec_out[ task->poh_hash->exec_idx ];
    2844           0 :       fd_execrp_poh_hash_msg_t * exec_msg = fd_chunk_to_laddr( exec_out->mem, exec_out->chunk );
    2845           0 :       exec_msg->bank_idx = task->poh_hash->bank_idx;
    2846           0 :       exec_msg->cnt      = task->poh_hash->cnt;
    2847           0 :       exec_msg->hashcnt  = task->poh_hash->hashcnt;
    2848           0 :       FD_STATIC_ASSERT( FD_SCHED_POH_PARA==FD_EXECRP_POH_PARA, poh batch width mismatch );
    2849           0 :       memcpy( exec_msg->hash, task->poh_hash->hash, task->poh_hash->cnt*sizeof(fd_hash_t) );
    2850           0 :       fd_stem_publish( stem, exec_out->idx, (FD_EXECRP_TT_POH_HASH<<32) | task->poh_hash->exec_idx, exec_out->chunk, sizeof(*exec_msg), 0UL, 0UL, 0UL );
    2851           0 :       exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*exec_msg), exec_out->chunk0, exec_out->wmark );
    2852           0 :       break;
    2853           0 :     };
    2854           0 :     case FD_SCHED_TT_LTHASH_SUB:
    2855           0 :     case FD_SCHED_TT_LTHASH_ADD: {
    2856           0 :       fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->lthash->bank_idx );
    2857           0 :       FD_TEST( bank );
    2858           0 :       bank->refcnt++;
    2859             : 
    2860           0 :       FD_TEST( task->lthash->exec_idx<ctx->exec_cnt );
    2861           0 :       FD_TEST( task->lthash->cnt && task->lthash->cnt<=FD_SCHED_LTHASH_BATCH_MAX );
    2862           0 :       fd_replay_out_link_t * exec_out = &ctx->exec_out[ task->lthash->exec_idx ];
    2863           0 :       fd_execrp_lthash_msg_t * exec_msg = fd_chunk_to_laddr( exec_out->mem, exec_out->chunk );
    2864           0 :       exec_msg->bank_idx = task->lthash->bank_idx;
    2865           0 :       exec_msg->cnt      = task->lthash->cnt;
    2866           0 :       fd_memcpy( exec_msg->acct, task->lthash->accts, task->lthash->cnt*sizeof(fd_acct_addr_t) );
    2867           0 :       ulong kind = task->task_type==FD_SCHED_TT_LTHASH_SUB ? FD_EXECRP_TT_LTHASH_SUB : FD_EXECRP_TT_LTHASH_ADD;
    2868           0 :       ulong sz   = fd_execrp_lthash_msg_sz( task->lthash->cnt );
    2869           0 :       fd_stem_publish( stem, exec_out->idx, (kind<<32) | task->lthash->exec_idx, exec_out->chunk, sz, 0UL, 0UL, 0UL );
    2870           0 :       exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sz, exec_out->chunk0, exec_out->wmark );
    2871           0 :       break;
    2872           0 :     }
    2873           0 :     default: {
    2874           0 :       FD_LOG_CRIT(( "unexpected task type %lu", task->task_type ));
    2875           0 :     }
    2876           0 :   }
    2877           0 : }
    2878             : 
    2879             : static void
    2880             : mark_bank_dead( fd_replay_tile_t *        ctx,
    2881             :                 fd_stem_context_t *       stem,
    2882             :                 ulong                     bank_idx,
    2883             :                 int                       dead_reason,
    2884             :                 int                       abandoned_reason,
    2885           0 :                 fd_block_footer_t const * footer ) {
    2886             :   /* UINT_MAX only when the bank knows FLH is off: repair treats the death
    2887             :      as final.  A bank still in INIT was never cloned, so its
    2888             :      features are unknown: count it as a possible prefix.  At worst
    2889             :      rotor finishes repairing a dead slot whose FEC sets replay drops.
    2890             : 
    2891             :      Remove this garbage after FLH is stable. */
    2892           0 :   fd_block_id_ele_t * block_id_ele   = &ctx->block_id_arr[ bank_idx ];
    2893           0 :   fd_bank_t *         dead_bank      = fd_banks_bank_query( ctx->banks, bank_idx );
    2894           0 :   int                 flh_active     = dead_bank->state==FD_BANK_STATE_INIT || FD_FEATURE_ACTIVE_BANK( dead_bank, alpenglow_fast_leader_handover );
    2895           0 :   uint                latest_fec_idx = flh_active ? block_id_ele->latest_fec_idx : UINT_MAX;
    2896             : 
    2897           0 :   ulong * dead_idxs     = ctx->dead_idxs;
    2898           0 :   ulong   dead_idxs_cnt = 0UL;
    2899           0 :   fd_banks_mark_bank_dead( ctx->banks, bank_idx, dead_idxs, &dead_idxs_cnt );
    2900             : 
    2901           0 :   if( block_id_ele->block_id_seen ) publish_slot_dead( ctx, stem, block_id_ele->slot, &block_id_ele->dmr, latest_fec_idx, footer );
    2902           0 :   else if( dead_reason!=FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD && !memcmp( &block_id_ele->block_info.hash, ((fd_hash_t){0}).uc, sizeof(fd_hash_t) ) ) publish_slot_dead( ctx, stem, block_id_ele->slot, &(fd_hash_t){ 0 }, latest_fec_idx, footer ); /* an incomplete turbine copy, keyed {slot,0}: rotor frees its eager blk */
    2903             : 
    2904             :   /* Report each newly dead bank now (dead_idxs excludes already-dead,
    2905             :      already-reported subtrees): the failing bank with its real reason and
    2906             :      descendants as parent_dead, or the whole lineage with the caller's
    2907             :      abandoned flavor.  Exactly one of the two reasons is set.
    2908             :      A previously PRUNABLE (evicted) bank converted by this walk reports
    2909             :      here like any other: eviction emits no row, and the sched-drain
    2910             :      emission is gated on the state still being PRUNABLE.  Blocks still
    2911             :      receiving FECs dedup the slot-completion report via dead_reported. */
    2912           0 :   int abandoned = abandoned_reason!=FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED;
    2913           0 :   for( ulong i=0UL; i<dead_idxs_cnt; i++ ) {
    2914           0 :     fd_block_id_ele_t * ele = &ctx->block_id_arr[ dead_idxs[ i ] ];
    2915           0 :     fd_bank_t *         bank = fd_banks_bank_query( ctx->banks, dead_idxs[ i ] );
    2916           0 :     timing_slot_release( ctx, dead_idxs[ i ] );
    2917           0 :     int dr = abandoned                 ? FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD
    2918           0 :            : dead_idxs[ i ]==bank_idx  ? dead_reason
    2919           0 :            :                             FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_PARENT_DEAD;
    2920           0 :     fd_hash_t * block_id = ele->block_id_seen ? &ele->dmr : &ele->latest_mr;
    2921           0 :     report_block_incomplete( ctx, ele->slot, block_id, bank, dr, abandoned_reason );
    2922           0 :   }
    2923           0 : }
    2924             : 
    2925             : static int
    2926             : try_replay( fd_replay_tile_t *  ctx,
    2927           0 :             fd_stem_context_t * stem ) {
    2928             : 
    2929           0 :   if( FD_UNLIKELY( !ctx->is_booted ) ) return 0;
    2930             : 
    2931             :   /* Hold off executing until the computed shred version is known, so
    2932             :      footer certs verify under it. */
    2933           0 :   if( FD_UNLIKELY( !ctx->shred_version ) ) return 0;
    2934             : 
    2935           0 :   int charge_busy = 0;
    2936           0 :   fd_sched_task_t task[ 1 ];
    2937           0 :   if( FD_UNLIKELY( !fd_sched_task_next_ready( ctx->sched, task ) ) ) {
    2938           0 :     return charge_busy; /* Nothing to execute or do. */
    2939           0 :   }
    2940             : 
    2941           0 :   charge_busy = 1;
    2942             : 
    2943           0 :   switch( task->task_type ) {
    2944           0 :     case FD_SCHED_TT_BLOCK_START: {
    2945           0 :       replay_block_start( ctx, task->block_start->bank_idx, task->block_start->parent_bank_idx, task->block_start->slot, task->block_start->lthash_inband );
    2946           0 :       fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->block_start->bank_idx );
    2947           0 :       fd_replay_footer_end_t * end = ctx->footer_end + bank->idx;
    2948           0 :       end->leader_credits = 0UL;
    2949           0 :       end->cert_next      = 0UL;
    2950           0 :       end->finalized      = 0;
    2951           0 :       int res = fd_sched_task_done( ctx->sched, FD_SCHED_TT_BLOCK_START, ULONG_MAX, ULONG_MAX, NULL );
    2952           0 :       if( FD_UNLIKELY( res ) ) mark_bank_dead( ctx, stem, task->block_start->bank_idx, sched_dead_reason_to_event( res ), FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED, NULL );
    2953           0 :       break;
    2954           0 :     }
    2955           0 :     case FD_SCHED_TT_BLOCK_END: {
    2956           0 :       fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->block_end->bank_idx );
    2957           0 :       int dead = 0;
    2958           0 :       if( FD_LIKELY( bank->state==FD_BANK_STATE_REPLAYABLE ) ) dead = replay_block_finalize( ctx, stem, bank );
    2959           0 :       fd_sched_task_done( ctx->sched, FD_SCHED_TT_BLOCK_END, ULONG_MAX, ULONG_MAX, NULL );
    2960           0 :       if( FD_UNLIKELY( dead ) ) fd_sched_block_abandon( ctx->sched, bank->idx, FD_SCHED_ABANDON_INVALID );
    2961           0 :       break;
    2962           0 :     }
    2963           0 :     case FD_SCHED_TT_TXN_EXEC:
    2964           0 :     case FD_SCHED_TT_TXN_SIGVERIFY:
    2965           0 :     case FD_SCHED_TT_POH_HASH:
    2966           0 :     case FD_SCHED_TT_LTHASH_SUB:
    2967           0 :     case FD_SCHED_TT_LTHASH_ADD:
    2968           0 :     case FD_SCHED_TT_CERT_VERIFY: {
    2969           0 :       dispatch_task( ctx, stem, task );
    2970           0 :       break;
    2971           0 :     }
    2972           0 :     case FD_SCHED_TT_MARK_DEAD: {
    2973           0 :       int dr = sched_block_dead_reason_to_event( ctx, task->mark_dead->bank_idx );
    2974           0 :       int ar = dr==FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD ? FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED
    2975           0 :                                                                  : FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED;
    2976           0 :       mark_bank_dead( ctx, stem, task->mark_dead->bank_idx, dr, ar, NULL );
    2977           0 :       break;
    2978           0 :     }
    2979           0 :     default: {
    2980           0 :       FD_LOG_CRIT(( "unexpected task type %lu", task->task_type ));
    2981           0 :     }
    2982           0 :   }
    2983             : 
    2984           0 :   return charge_busy;
    2985           0 : }
    2986             : 
    2987           0 : #define PROCESS_FEC_WAIT 0  /* Put back on dcache for later retry */
    2988           0 : #define PROCESS_FEC_OK   1  /* Process FEC set */
    2989           0 : #define PROCESS_FEC_DROP 2  /* Drop FEC set and notify rotor to start redelivery */
    2990           0 : #define PROCESS_FEC_SKIP 3  /* Good to skip this FEC set, drop from dcache */
    2991             : 
    2992             : /* live_block_id_ele returns ele if it is backed by a live bank (same
    2993             :    bank_seq, not PRUNABLE), else NULL.  Map entries outlive their banks
    2994             :    through eviction.  TODO if we remove block_id_eles with banks, we
    2995             :    can get rid of this */
    2996             : static fd_block_id_ele_t *
    2997             : live_block_id_ele( fd_replay_tile_t *  ctx,
    2998           0 :                    fd_block_id_ele_t * ele ) {
    2999           0 :   if( FD_UNLIKELY( !ele ) ) return NULL;
    3000           0 :   fd_bank_t * bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, ele ) );
    3001           0 :   if( FD_UNLIKELY( !bank || bank->bank_seq!=ele->bank_seq || bank->state==FD_BANK_STATE_PRUNABLE ) ) return NULL;
    3002           0 :   return ele;
    3003           0 : }
    3004             : 
    3005             : static int
    3006             : can_process_rotor_fec( fd_replay_tile_t       * ctx,
    3007             :                        fd_repair_out_fec_t    * fec,
    3008           0 :                        int *                    evict_banks_out ) {
    3009             :   /* We can process a FEC set if a few conditions are met:
    3010             :      - sched has capacity
    3011             :      - banks has capacity.  Evict if we don't (see below)
    3012             :      Our leader FECs bind to the existing leader bank and never enter
    3013             :      sched, so neither capacity gate applies to them.  One that is not
    3014             :      for the current leader slot has no bank to bind to. */
    3015             : 
    3016           0 :   if( FD_UNLIKELY( fec->is_leader && ( !ctx->leader_bank || ctx->leader_bank->f.slot!=fec->slot ) ) ) return PROCESS_FEC_SKIP;
    3017             : 
    3018             :   /* The FEC sets before an UpdateParent are the abandoned prefix,
    3019             :      built on the leader's optimistic parent.  The block replays from
    3020             :      the UpdateParent FEC set onto the new parent. */
    3021           0 :   if( FD_UNLIKELY( fec->fec_set_idx<fec->update_parent_fec_idx ) ) {
    3022           0 :     FD_LOG_DEBUG(( "skipping FEC set (slot=%lu, fec_set_idx=%u) before the UpdateParent at fec set idx %u", fec->slot, fec->fec_set_idx, fec->update_parent_fec_idx ));
    3023           0 :     return PROCESS_FEC_SKIP;
    3024           0 :   }
    3025             : 
    3026           0 :   if( FD_UNLIKELY( !fec->is_leader && fd_sched_can_ingest_cnt( ctx->sched )==0UL ) ) {
    3027           0 :     ctx->metrics.sched_full++;
    3028           0 :     return PROCESS_FEC_WAIT;
    3029           0 :   }
    3030             : 
    3031             :   /* Recovery redelivery re-publishes the entire ancestry path from the
    3032             :      rotor root, but most of those blocks are already replayed.  A
    3033             :      fully replayed block is always keyed by {slot, block_id_real}; a
    3034             :      turbine block still in flight is keyed by {slot, 0}.  Rotor fills
    3035             :      in block_id on redelivered FECs whenever it knows it, so try the id
    3036             :      key first and fall back to the slot key for a turbine version.
    3037             : 
    3038             :      A turbine FEC only counts a {slot, block_id} hit if that block is
    3039             :      complete.  An incomplete one is the repaired copy of the same
    3040             :      block, a different stream that may be ahead of or behind the
    3041             :      turbine copy: rotor merges the two copies into the turbine one
    3042             :      without changing its is_known, so the turbine copy keeps building
    3043             :      its own {slot, 0} bank, and the repaired one is abandoned once the
    3044             :      turbine bank completes and takes its {slot, block_id} key. */
    3045           0 :   {
    3046           0 :     fd_block_id_ele_t * self = NULL;
    3047           0 :     if( FD_LIKELY( !fd_hash_check_zero( &fec->block_id ) ) ) {
    3048           0 :       fd_block_id_t self_key = fd_block_id( fec->slot, fec->block_id.uc );
    3049           0 :       self = live_block_id_ele( ctx, fd_block_id_map_ele_query( ctx->block_id_map, &self_key, NULL, ctx->block_id_arr ) );
    3050           0 :       if( FD_UNLIKELY( self && !self->block_id_seen && !fec->is_known ) ) self = NULL;
    3051           0 :     }
    3052           0 :     if( FD_LIKELY( !self && !fec->is_known ) ) {
    3053           0 :       fd_block_id_t self_key = { .slot = fec->slot };
    3054           0 :       self = live_block_id_ele( ctx, fd_block_id_map_ele_query( ctx->block_id_map, &self_key, NULL, ctx->block_id_arr ) );
    3055           0 :     }
    3056           0 :     if( FD_UNLIKELY( self && ( self->block_id_seen || self->latest_fec_idx>=fec->fec_set_idx ) ) ) return PROCESS_FEC_SKIP;
    3057           0 :   }
    3058             : 
    3059             :   /* Rooted past: the block is rooted or on a dead fork, so no
    3060             :      redelivery can help.  A DROP would ask for another, and during
    3061             :      catch-up that one also lands behind the backlog, rooted past. */
    3062             : 
    3063           0 :   if( FD_UNLIKELY( ctx->published_root_slot!=ULONG_MAX &&
    3064           0 :                    ( fec->slot<=ctx->published_root_slot ||
    3065           0 :                      fec->parent_slot<ctx->published_root_slot ) ) ) {
    3066           0 :     ctx->metrics.fec_rooted_past++;
    3067           0 :     return PROCESS_FEC_SKIP;
    3068           0 :   }
    3069             : 
    3070           0 :   ulong               parent_bank_idx = UINT_MAX;
    3071           0 :   fd_block_id_ele_t * parent          = NULL;
    3072           0 :   if( FD_UNLIKELY( fec->fec_set_idx==fec->update_parent_fec_idx ) ) {
    3073           0 :     fd_block_id_t parent_key = fd_block_id( fec->parent_slot, fec->parent_block_id.uc );
    3074           0 :     parent = fd_block_id_map_ele_query( ctx->block_id_map, &parent_key, NULL, ctx->block_id_arr );
    3075           0 :     if( FD_UNLIKELY( !parent ) ) {
    3076           0 :       FD_BASE58_ENCODE_32_BYTES( fec->parent_block_id.uc, parent_key_b58 );
    3077           0 :       FD_LOG_INFO(( "parent bank not found for slot %lu fec set idx %u, parent slot %lu parent block_id %s", fec->slot, fec->fec_set_idx, fec->parent_slot, parent_key_b58 ));
    3078             : 
    3079             :       /* We cannot tell here why the parent is missing -- dropped on a
    3080             :          dead lineage, evicted with its tracking slot reused, or rooted
    3081             :          past -- and the latter two are routine and recoverable, so do
    3082             :          not emit events; they would be emitted in bulk during the
    3083             :          redelivery that recovers them. */
    3084           0 :       ctx->metrics.parent_unavailable++;
    3085           0 :       return PROCESS_FEC_DROP;
    3086           0 :     }
    3087           0 :     parent_bank_idx = fd_block_id_ele_get_idx( ctx->block_id_arr, parent );
    3088           0 :   } else {
    3089           0 :     fd_block_id_t key = { .slot = fec->slot };
    3090           0 :     if( FD_LIKELY( fec->is_known ) ) key = fd_block_id( fec->slot, fec->block_id.uc );
    3091             : 
    3092           0 :     parent = fd_block_id_map_ele_query( ctx->block_id_map, &key, NULL, ctx->block_id_arr );
    3093           0 :     if( FD_UNLIKELY( !parent ) ) {
    3094           0 :       FD_BASE58_ENCODE_32_BYTES( fec->block_id.uc, block_id_b58 );
    3095           0 :       FD_LOG_INFO(( "parent bank not found for slot %lu fec set idx %u slot_bid %s. parent slot %lu", fec->slot, fec->fec_set_idx, block_id_b58, fec->parent_slot ));
    3096           0 :       ctx->metrics.parent_unavailable++;
    3097           0 :       return PROCESS_FEC_DROP; // either pruned or bank evicted
    3098           0 :     }
    3099           0 :     parent_bank_idx = fd_block_id_ele_get_idx( ctx->block_id_arr, parent );
    3100           0 :   }
    3101             : 
    3102           0 :   ctx->metrics.reasm_latest_slot    = fec->slot;
    3103           0 :   ctx->metrics.reasm_latest_fec_idx = fec->fec_set_idx;
    3104             : 
    3105             :   /* If the FEC we are building off of is for a prunable bank, we must
    3106             :      wait to process the FEC until the bank has been evicted. */
    3107           0 :   fd_bank_t * parent_fec_bank = parent_bank_idx==UINT_MAX ? NULL : fd_banks_bank_query( ctx->banks, parent_bank_idx );
    3108           0 :   if( FD_UNLIKELY( parent_fec_bank && parent_fec_bank->bank_seq==parent->bank_seq && parent_fec_bank->state==FD_BANK_STATE_PRUNABLE ) ) {
    3109           0 :     FD_LOG_DEBUG(( "waiting to process FEC set (slot=%lu, fec_set_idx=%u) because parent bank is being pruned", fec->slot, fec->fec_set_idx ));
    3110           0 :     return PROCESS_FEC_WAIT;
    3111           0 :   }
    3112             : 
    3113           0 :   if( FD_UNLIKELY( ctx->is_leader &&
    3114           0 :                    fec->fec_set_idx==fec->update_parent_fec_idx &&
    3115           0 :                    parent_bank_idx==ctx->leader_bank->idx &&
    3116           0 :                    parent->bank_seq==ctx->leader_bank->bank_seq ) ) {
    3117             :     /* This guards against a rare race where we receive the FEC set for
    3118             :        the slot right after our leader rotation before we freeze the
    3119             :        bank for the last slot in our leader rotation.  Leader slot
    3120             :        freezing happens only after if we've received the final PoH hash
    3121             :        from the poh tile as well as the final FEC set for the leader
    3122             :        slot.  So the race happens when FEC sets are delivered and
    3123             :        processed sooner than the PoH hash, aka when the
    3124             :        poh=>shred=>replay path for the block id beats the poh=>replay
    3125             :        path for the poh hash.  To mitigate this race, we must block on
    3126             :        ingesting the FEC set for the ensuing slot before the leader
    3127             :        bank freezes, because that would violate ordering invariants in
    3128             :        banks and sched.  Both the bank index and sequence must match
    3129             :        because bank indices are recycled after eviction. */
    3130           0 :     FD_TEST( ctx->block_id_arr[ ctx->leader_bank->idx ].block_id_seen );
    3131           0 :     FD_TEST( !ctx->recv_poh );
    3132           0 :     ctx->metrics.leader_bid_wait++;
    3133           0 :     return PROCESS_FEC_WAIT;
    3134           0 :   }
    3135             : 
    3136             :   /* Should we evict banks if there are no more free banks?  The answer
    3137             :      is it depends.  Eviction should only happen if we can make no
    3138             :      forward replay progress.  This can only happen if:
    3139             :      1. banks are full
    3140             :      2. sched is drained: pending txns could complete a block and
    3141             :         eventually advance the root.
    3142             :      AND
    3143             :      3. next reasm FEC needs a new bank.  A fec that chains off of a
    3144             :         bank that is already allocated can be processed.  A FEC can
    3145             :         require a new bank in three ways:
    3146             :         - fec_set_idx==0: we don't have any free banks to provision a
    3147             :           new bank for this FEC.
    3148             :         - equivocation: a FEC may be in the middle of a block, but if
    3149             :           it's the first equivocating FEC detected, we need to allocate
    3150             :           a new bank for the version of the block.
    3151             :         - backfill: the parent FEC's bank was never created or has been
    3152             :           evicted and must be reconstructed.
    3153             : 
    3154             : 
    3155             :      - Without reasm there is no backfill: if the parent bank was
    3156             :        evicted or its bank idx was reused, this FEC can't be
    3157             :        processed, so drop it and notify rotor to redeliver.
    3158             :      - Rotor equivocating slots are guaranteed to be delivered starting
    3159             :        from FEC 0, so even equivocations mid-slot are delivered from
    3160             :        fec 0 */
    3161             : 
    3162           0 :   int invalid_parent = !parent_fec_bank || parent_fec_bank->bank_seq!=parent->bank_seq;
    3163           0 :   if( FD_UNLIKELY( invalid_parent ) ) {
    3164           0 :     FD_LOG_INFO(( "parent bank evicted for slot %lu fec set idx %u, parent slot %lu", fec->slot, fec->fec_set_idx, fec->parent_slot ));
    3165           0 :     ctx->metrics.parent_unavailable++;
    3166           0 :     return PROCESS_FEC_DROP;
    3167           0 :   } else if( FD_UNLIKELY( fec->fec_set_idx!=fec->update_parent_fec_idx && parent->latest_fec_idx!=fec->fec_set_idx - FD_FEC_SHRED_CNT ) ) {
    3168             :     /* Similar to the very first condition in can_process_rotor_fec,
    3169             :        but this would hit if we were halfway through replaying a slot
    3170             :        and then requested redelivery from rotor. Then we can skip
    3171             :        replaying the first half of the slot. */
    3172           0 :     FD_LOG_INFO(( "fec redelivered for slot %lu fec set idx %u, parent slot %lu. bank_idx %lu, latest_fec_idx %u", fec->slot, fec->fec_set_idx, fec->parent_slot, fd_block_id_ele_get_idx( ctx->block_id_arr, parent ), parent->latest_fec_idx ));
    3173           0 :     return PROCESS_FEC_SKIP; // context for slot exists, but this is an earlier or non-contiguous FEC. Safe to skip.
    3174           0 :   }
    3175             : 
    3176           0 :   if( FD_UNLIKELY( !fd_banks_can_start_bank( ctx->banks ) ) ) {
    3177           0 :     int is_new_block = fec->fec_set_idx==fec->update_parent_fec_idx && !fec->is_leader;
    3178           0 :     if( FD_UNLIKELY( is_new_block ) ) {
    3179           0 :       ctx->metrics.banks_full++;
    3180           0 :       if( FD_UNLIKELY( fd_sched_is_drained( ctx->sched ) && !ctx->snapmk.active ) ) *evict_banks_out = 1; /* see can_process_fec */
    3181           0 :       return PROCESS_FEC_WAIT;
    3182           0 :     }
    3183           0 :   }
    3184             : 
    3185             :   /* A block's first FEC set needs a staging lane.  This comes after the
    3186             :      eviction decision so that a wait for a lane doesn't shadow it.  Our
    3187             :      own leader FECs never enter sched, so they don't need to wait for a
    3188             :      lane. */
    3189           0 :   if( FD_UNLIKELY( fec->fec_set_idx==0U && !fec->is_leader && fd_sched_fec_waits_for_lane( ctx->sched, parent_bank_idx, 1 ) ) ) {
    3190           0 :     ctx->metrics.lane_wait++;
    3191           0 :     return PROCESS_FEC_WAIT;
    3192           0 :   }
    3193             : 
    3194             :   /* Otherwise, banks may not be full, so we can always create a new
    3195             :      bank if needed.  Or, if banks are full, the current fec set's
    3196             :      ancestor (idx 0) already created a bank for this slot. */
    3197           0 :   return PROCESS_FEC_OK;
    3198           0 : }
    3199             : 
    3200             : static int
    3201             : try_notify_consensus_root( fd_replay_tile_t *  ctx,
    3202           0 :                            fd_stem_context_t * stem ) {
    3203             : 
    3204             :   /* Other tiles cannot prune while we are leader - replay may still be
    3205             :      waiting for the remaining FEC sets of our own slots to be
    3206             :      delivered, and if the upstream tiles prune too early, they may fail
    3207             :      to forward our own leader FECs.  Then we would be permanently stuck
    3208             :      in the leader state. */
    3209           0 :   if( FD_UNLIKELY( ctx->is_leader ) ) return 0;
    3210             : 
    3211           0 :   if( FD_LIKELY( ctx->notified_root_slot==ctx->consensus_root_slot &&
    3212           0 :                  fd_hash_eq( &ctx->notified_root, &ctx->consensus_root ) ) ) return 0;
    3213             : 
    3214           0 :   fd_block_id_t root_key = fd_block_id( ctx->consensus_root_slot, ctx->consensus_root.uc );
    3215           0 :   fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &root_key, NULL, ctx->block_id_arr );
    3216           0 :   if( FD_UNLIKELY( !block_id_ele ) ) return 0;
    3217             : 
    3218           0 :   fd_bank_t * bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) );
    3219           0 :   if( FD_UNLIKELY( !bank ||
    3220           0 :                    bank->bank_seq!=block_id_ele->bank_seq ||
    3221           0 :                    !fd_hash_eq( &bank->f.block_id, &ctx->consensus_root ) ||
    3222           0 :                    bank->state==FD_BANK_STATE_PRUNABLE ) ) return 0;
    3223             : 
    3224           0 :   fd_sched_root_notify( ctx->sched, bank->idx );
    3225           0 :   publish_root_advanced( ctx, stem, bank );
    3226             : 
    3227           0 :   ctx->notified_root      = ctx->consensus_root;
    3228           0 :   ctx->notified_root_slot = ctx->consensus_root_slot;
    3229           0 :   ctx->notified_root_bank = bank;
    3230           0 :   return 1;
    3231           0 : }
    3232             : 
    3233             : /* Periodic snapshots use block height, matching Agave.  Manually
    3234             :    scheduled snapshots remain slot based. */
    3235             : 
    3236             : static int
    3237             : snapshot_due_for_root( fd_replay_tile_t * ctx,
    3238             :                        ulong              published_root_block_height,
    3239             :                        ulong              advanceable_root_slot,
    3240             :                        ulong              advanceable_root_block_height,
    3241             :                        ulong              consensus_root_block_height,
    3242           0 :                        int *              out_incremental ) {
    3243           0 :   *out_incremental = 0;
    3244           0 :   if( FD_LIKELY( !ctx->snapmk.supported || ctx->snapmk.active ) ) return 0;
    3245             : 
    3246           0 :   int   caught_up   = ctx->caught_up; /* suspend periodic snaps until caught up */
    3247           0 :   ulong full_target = ULONG_MAX;
    3248           0 :   ulong interval    = ctx->snapmk.full_interval_blocks;
    3249           0 :   if( FD_UNLIKELY( interval && caught_up ) ) {
    3250           0 :     if( FD_UNLIKELY( ctx->snapmk.next_full_block_height==ULONG_MAX ) ) {
    3251           0 :       ctx->snapmk.next_full_block_height = ((published_root_block_height/interval)+1UL)*interval;
    3252           0 :     }
    3253             : 
    3254             :     /* If snapshot production fell behind by more than one interval,
    3255             :        skip ahead to the latest due interval. */
    3256           0 :     full_target = fd_ulong_max( ctx->snapmk.next_full_block_height,
    3257           0 :                                 (consensus_root_block_height/interval)*interval );
    3258           0 :   }
    3259             : 
    3260             :   /* Manual slot scheduling always requests a full snapshot. */
    3261           0 :   if( FD_UNLIKELY( advanceable_root_slot>=ctx->snapmk.scheduled_at_slot ) ) return 1;
    3262             : 
    3263             :   /* An incremental snapshot is only possible once a full snapshot
    3264             :      exists to serve as its base. */
    3265           0 :   ulong incremental_target = ULONG_MAX;
    3266           0 :   ulong incr_interval      = ctx->snapmk.incremental_interval_blocks;
    3267           0 :   if( FD_UNLIKELY( incr_interval && caught_up && ctx->snapmk.base_slot!=ULONG_MAX ) ) {
    3268           0 :     if( FD_UNLIKELY( ctx->snapmk.next_incremental_block_height==ULONG_MAX ) ) {
    3269           0 :       ctx->snapmk.next_incremental_block_height = ((published_root_block_height/incr_interval)+1UL)*incr_interval;
    3270           0 :     }
    3271           0 :     incremental_target = fd_ulong_max( ctx->snapmk.next_incremental_block_height,
    3272           0 :                                        (consensus_root_block_height/incr_interval)*incr_interval );
    3273           0 :   }
    3274             : 
    3275             :   /* A full snapshot due at the same block height supersedes the
    3276             :      incremental snapshot. */
    3277           0 :   ulong target      = full_target;
    3278           0 :   int   incremental = incremental_target<full_target;
    3279           0 :   if( FD_UNLIKELY( incremental ) ) target = incremental_target;
    3280           0 :   if( FD_LIKELY( advanceable_root_block_height<target ) ) return 0;
    3281           0 :   *out_incremental = incremental;
    3282           0 :   return 1;
    3283           0 : }
    3284             : 
    3285             : static void
    3286             : snapmk_start( fd_replay_tile_t *  ctx,
    3287             :               fd_stem_context_t * stem,
    3288             :               int                 incremental );
    3289             : 
    3290             : static int
    3291             : try_advance_published_root( fd_replay_tile_t *  ctx,
    3292           0 :                             fd_stem_context_t * stem ) {
    3293             : 
    3294           0 :   if( FD_LIKELY( ctx->published_root_slot==ctx->consensus_root_slot ) ) return 0;
    3295             : 
    3296             :   /* accdb does not root while producing a snapshot. */
    3297           0 :   if( FD_UNLIKELY( ctx->snapmk.active ) ) return 0;
    3298             : 
    3299             :   /* If the new root is not available because the bank is/has been
    3300             :      evicted, we can't advance the root.  Try again later. */
    3301             : 
    3302           0 :   fd_block_id_t consensus_root_key = fd_block_id( ctx->consensus_root_slot, ctx->consensus_root.uc );
    3303           0 :   fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &consensus_root_key, NULL, ctx->block_id_arr );
    3304           0 :   if( FD_UNLIKELY( !block_id_ele ) ) return 0;
    3305           0 :   fd_bank_t * target_bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) );
    3306           0 :   if( FD_UNLIKELY( !target_bank ||
    3307           0 :                    target_bank->bank_seq!=block_id_ele->bank_seq ||
    3308           0 :                    !fd_hash_eq( &target_bank->f.block_id, &ctx->consensus_root ) ||
    3309           0 :                    target_bank->state==FD_BANK_STATE_PRUNABLE ) ) {
    3310           0 :     return 0;
    3311           0 :   }
    3312             : 
    3313           0 :   fd_bank_t * published_root_bank = fd_banks_bank_query( ctx->banks, ctx->published_root_bank_idx );
    3314           0 :   FD_TEST( published_root_bank );
    3315           0 :   ulong published_root_block_height = published_root_bank->f.block_height;
    3316           0 :   ulong consensus_root_block_height = target_bank->f.block_height;
    3317             : 
    3318             :   /* If the identity vote has been seen on a bank that should be rooted,
    3319             :      then we are now ready to produce blocks. */
    3320           0 :   if( FD_UNLIKELY( !ctx->identity_vote_rooted ) ) {
    3321           0 :     if( target_bank->f.identity_vote_idx==ctx->identity_idx ) ctx->identity_vote_rooted = 1;
    3322           0 :   }
    3323             : 
    3324           0 :   ulong advanceable_root_idx = ULONG_MAX;
    3325           0 :   if( FD_UNLIKELY( !fd_banks_advance_root_prepare( ctx->banks, target_bank->idx, &advanceable_root_idx ) ) ) {
    3326           0 :     ctx->metrics.storage_root_behind++;
    3327           0 :     return 0;
    3328           0 :   }
    3329             : 
    3330           0 :   fd_bank_t * bank = fd_banks_bank_query( ctx->banks, advanceable_root_idx );
    3331           0 :   FD_TEST( bank );
    3332             : 
    3333           0 :   if( FD_UNLIKELY( advanceable_root_idx>=ctx->block_id_len ) ) {
    3334           0 :     FD_LOG_CRIT(( "invariant violation: advanceable root ele out of bounds [0, %lu) index %lu", ctx->block_id_len, advanceable_root_idx ));
    3335           0 :   }
    3336             :   /* Telemetry: banks still mid-replay (started but never completed) on
    3337             :      forks not descending from the new root are about to be cancelled by
    3338             :      fd_banks_advance_root without ever emitting a block_completed row.
    3339             :      Emit them as abandoned with reason `pruned`: the block was not
    3340             :      invalid, it lost the fork race.  Every live bank is linked in the
    3341             :      bank tree under the current root, so one preorder pass that skips
    3342             :      the new root's subtree visits exactly the banks about to go. */
    3343           0 :   if( FD_UNLIKELY( fd_event_tl ) ) {
    3344           0 :     ulong       root_idx = fd_banks_root( ctx->banks )->idx;
    3345           0 :     ulong       s        = root_idx;
    3346           0 :     fd_bank_t * b        = fd_banks_root( ctx->banks );
    3347           0 :     for(;;) {
    3348           0 :       if( FD_LIKELY( s!=advanceable_root_idx ) ) {
    3349           0 :         if( FD_UNLIKELY( b->state==FD_BANK_STATE_INIT || b->state==FD_BANK_STATE_REPLAYABLE ) ) {
    3350           0 :           fd_block_id_ele_t * ele = &ctx->block_id_arr[ s ];
    3351           0 :           report_block_incomplete( ctx, ele->slot, ele->block_id_seen ? &ele->dmr : &ele->latest_mr, b, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED );
    3352           0 :         }
    3353           0 :         if( FD_LIKELY( b->child_idx!=ULONG_MAX ) ) { s = b->child_idx; b = fd_banks_bank_query( ctx->banks, s ); continue; }
    3354           0 :       }
    3355           0 :       while( s!=root_idx && b->sibling_idx==ULONG_MAX ) { s = b->parent_idx; b = fd_banks_bank_query( ctx->banks, s ); }
    3356           0 :       if( s==root_idx ) break;
    3357           0 :       s = b->sibling_idx; b = fd_banks_bank_query( ctx->banks, s );
    3358           0 :     }
    3359           0 :   }
    3360             : 
    3361           0 :   ulong advanceable_root_slot = bank->f.slot;
    3362           0 :   int   root_epoch_changed    = bank->f.epoch!=published_root_bank->f.epoch;
    3363           0 :   uchar              stake_history_data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
    3364           0 :   fd_stake_history_t stake_history_[1];
    3365           0 :   fd_stake_history_t const * stake_history = fd_sysvar_stake_history_read( ctx->accdb, bank->accdb_fork_id, stake_history_data, stake_history_ );
    3366           0 :   fd_txncache_advance_root( ctx->txncache, bank->txncache_fork_id );
    3367           0 :   fd_progcache_advance_root( ctx->progcache, bank->progcache_fork_id );
    3368           0 :   fd_accdb_advance_root( ctx->accdb, bank->accdb_fork_id );
    3369           0 :   fd_sched_advance_root( ctx->sched, advanceable_root_idx );
    3370           0 :   fd_banks_advance_root( ctx->banks, advanceable_root_idx, stake_history );
    3371           0 :   if( FD_UNLIKELY( root_epoch_changed ) ) refresh_vote_account_staked( ctx, bank );
    3372             : 
    3373           0 :   for( ulong b=0UL; b<ctx->max_live_slots; b++ ) {
    3374           0 :     if( FD_UNLIKELY( ctx->timing_slot_of_bank[ b ]!=fd_timing_slot_pool_idx_null( ctx->timing_slot_pool ) && !fd_banks_bank_query( ctx->banks, b ) ) ) timing_slot_release( ctx, b );
    3375           0 :   }
    3376             : 
    3377           0 :   int snap_incremental;
    3378           0 :   int snap_due = snapshot_due_for_root( ctx,
    3379           0 :                                         published_root_block_height,
    3380           0 :                                         advanceable_root_slot,
    3381           0 :                                         bank->f.block_height,
    3382           0 :                                         consensus_root_block_height,
    3383           0 :                                         &snap_incremental );
    3384             : 
    3385           0 :   ctx->published_root_slot     = advanceable_root_slot;
    3386           0 :   ctx->published_root_bank_idx = advanceable_root_idx;
    3387             : 
    3388           0 :   if( FD_UNLIKELY( snap_due ) ) {
    3389           0 :     snapmk_start( ctx, stem, snap_incremental );
    3390           0 :     if( FD_UNLIKELY( !snap_incremental ) ) {
    3391           0 :       if( FD_UNLIKELY( ctx->snapmk.full_interval_blocks &&
    3392           0 :                        bank->f.block_height>=ctx->snapmk.next_full_block_height ) ) {
    3393           0 :         ulong interval = ctx->snapmk.full_interval_blocks;
    3394           0 :         ctx->snapmk.next_full_block_height = ((bank->f.block_height/interval)+1UL)*interval;
    3395           0 :       }
    3396           0 :       if( FD_UNLIKELY( advanceable_root_slot>=ctx->snapmk.scheduled_at_slot ) ) {
    3397           0 :         ctx->snapmk.scheduled_at_slot = ULONG_MAX;
    3398           0 :       }
    3399           0 :     }
    3400           0 :     if( FD_UNLIKELY( ctx->snapmk.incremental_interval_blocks ) ) {
    3401           0 :       ulong interval = ctx->snapmk.incremental_interval_blocks;
    3402           0 :       ctx->snapmk.next_incremental_block_height = ((bank->f.block_height/interval)+1UL)*interval;
    3403           0 :     }
    3404           0 :   }
    3405             : 
    3406           0 :   return 1;
    3407           0 : }
    3408             : 
    3409             : static int
    3410           0 : try_prune_sched( fd_replay_tile_t * ctx ) {
    3411           0 :   ulong bank_idx;
    3412           0 :   int   pruned = 0;
    3413           0 :   while( (bank_idx=fd_sched_pruned_block_next( ctx->sched ) )!=ULONG_MAX ) {
    3414           0 :     fd_bank_t * bank = fd_banks_bank_query( ctx->banks, bank_idx );
    3415           0 :     FD_TEST( bank );
    3416             :     /* An evicted bank drains here with its verdict final: every
    3417             :        in-flight task has resolved, so still PRUNABLE means none ruled
    3418             :        it invalid (that would have converted it to DEAD and reported a
    3419             :        dead row), and this is the block's only row.  A frozen victim
    3420             :        already reported a completed row. */
    3421           0 :     if( FD_UNLIKELY( bank->state==FD_BANK_STATE_PRUNABLE && !bank->block_completed_nanos ) ) {
    3422           0 :       fd_block_id_ele_t * ele = &ctx->block_id_arr[ bank_idx ];
    3423           0 :       report_block_incomplete( ctx, ele->slot, ele->block_id_seen ? &ele->dmr : &ele->latest_mr, bank,
    3424           0 :                                FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD,
    3425           0 :                                FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_EVICTED );
    3426           0 :     }
    3427           0 :     bank->refcnt--;
    3428           0 :     FD_LOG_DEBUG(( "bank (idx=%lu) refcnt decremented to %lu for sched", bank->idx, bank->refcnt ));
    3429           0 :     pruned = 1;
    3430           0 :   }
    3431           0 :   return pruned;
    3432           0 : }
    3433             : 
    3434             : static int
    3435           0 : try_prune_bank( fd_replay_tile_t * ctx ) {
    3436           0 :   fd_banks_prune_cancel_info_t cancel_info[ 1 ];
    3437             : 
    3438           0 :   int pruned = fd_banks_prune_one_bank( ctx->banks, cancel_info );
    3439           0 :   switch( pruned ) {
    3440           0 :     case 2: { /* pruning bank + cancellation is needed */
    3441           0 :       fd_txncache_cancel_fork( ctx->txncache,  cancel_info->txncache_fork_id );
    3442           0 :       fd_progcache_cancel_fork( ctx->progcache, cancel_info->progcache_fork_id );
    3443           0 :       fd_accdb_purge( ctx->accdb, cancel_info->accdb_fork_id );
    3444           0 :       __attribute__((fallthrough));
    3445           0 :     }
    3446           0 :     case 1: { /* pruning bank + no cancellation is needed */
    3447             :       /* A sched block exists, and can be marked dead, for a bank as
    3448             :          soon as its first FEC has been ingested, which can happen
    3449             :          before the bank ever set itself up for actual execution (e.g. a
    3450             :          block that parses as bad on its very first FEC).  So always
    3451             :          instruct sched to prune the block whenever banks prunes the
    3452             :          bank.  The txncache/progcache/accdb forks, on the other hand,
    3453             :          are only created once the bank started actual execution. */
    3454           0 :       fd_sched_cancel( ctx->sched, cancel_info->bank_idx );
    3455           0 :       timing_slot_release( ctx, cancel_info->bank_idx );
    3456           0 :       return 1;
    3457           0 :     }
    3458           0 :     case 0: /* no bank to prune */
    3459           0 :       return 0;
    3460           0 :     default:
    3461           0 :       FD_LOG_ERR(( "unreachable" ));
    3462           0 :   }
    3463           0 : }
    3464             : 
    3465             : static void
    3466             : after_credit( fd_replay_tile_t *  ctx,
    3467             :               fd_stem_context_t * stem,
    3468             :               int *               opt_poll_in,
    3469           0 :               int *               charge_busy ) {
    3470             :   /* Publish slot state intervals before the halt checks. */
    3471           0 :   fd_replay_slot_states_t * states = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
    3472           0 :   states->cnt = fd_sched_slot_states_drain( ctx->sched, states->states );
    3473           0 :   if( FD_UNLIKELY( states->cnt ) ) publish_replay_out( ctx, stem, REPLAY_SIG_SLOT_STATES, sizeof(fd_replay_slot_states_t) );
    3474             : 
    3475           0 :   if( FD_UNLIKELY( ctx->halt_replay && !ctx->is_leader ) ) return;
    3476           0 :   if( FD_UNLIKELY( !ctx->is_booted || !ctx->wfs_complete ) ) return;
    3477             : 
    3478             :   /* The overall priority for the replay tile in order is:
    3479             :      1. Make sure replay has room to progress:
    3480             :         a. evicting pending FECs from the reassembler
    3481             :         b. queueing up evictable banks for pruning if needed
    3482             :         c. clearing any pending bank eviction victims.
    3483             :      2. Drain outstanding bank references from the scheduler.  This
    3484             :         happens after a block gets completed or a fork gets pruned.
    3485             :      3. Notify sched and bank consumers of a new consensus root, then
    3486             :         advance the storage root once old references drain.
    3487             :      4. Replay.  If there is work to do for replay, do it.  This is
    3488             :         more important than ingesting more FEC sets.
    3489             :      5. If replay has nothing to do, ingest more FEC sets.
    3490             :      WARNING: The ordering here is VERY load bearing and it should not
    3491             :      be changed without extreme caution. */
    3492             : 
    3493           0 :   if( FD_UNLIKELY( try_prune_sched( ctx ) ) ) {
    3494           0 :     *charge_busy = 1;
    3495           0 :     *opt_poll_in = 0;
    3496           0 :     return;
    3497           0 :   }
    3498             : 
    3499           0 :   if( FD_UNLIKELY( try_notify_consensus_root( ctx, stem ) ) ) {
    3500           0 :     *charge_busy = 1;
    3501           0 :     *opt_poll_in = 0;
    3502           0 :     return;
    3503           0 :   }
    3504             : 
    3505           0 :   if( FD_UNLIKELY( try_prune_bank( ctx ) ) ) {
    3506           0 :     *charge_busy = 1;
    3507           0 :     *opt_poll_in = 0;
    3508           0 :     return;
    3509           0 :   }
    3510             : 
    3511           0 :   if( FD_UNLIKELY( try_become_leader( ctx, stem, FD_METRICS_SAMPLE_REASON_LEADER_START, 0UL ) ) ) {
    3512           0 :     *charge_busy = 1;
    3513           0 :     *opt_poll_in = 0;
    3514           0 :     return;
    3515           0 :   }
    3516             : 
    3517           0 :   if( FD_UNLIKELY( try_fini_leader( ctx, stem ) ) ) {
    3518           0 :     *charge_busy = 1;
    3519           0 :     *opt_poll_in = 0;
    3520           0 :     return;
    3521           0 :   }
    3522             : 
    3523           0 :   if( FD_UNLIKELY( try_advance_published_root( ctx, stem ) ) ) {
    3524           0 :     *charge_busy = 1;
    3525           0 :     *opt_poll_in = 0;
    3526           0 :     return;
    3527           0 :   }
    3528             : 
    3529           0 :   if( FD_LIKELY( try_replay( ctx, stem ) ) ) {
    3530           0 :     *charge_busy = 1;
    3531           0 :     *opt_poll_in = 0;
    3532           0 :     return;
    3533           0 :   }
    3534             : 
    3535           0 :   ctx->execrp_idle_cnt++;
    3536           0 : }
    3537             : 
    3538             : static int
    3539             : before_frag( fd_replay_tile_t * ctx,
    3540             :              ulong              in_idx,
    3541             :              ulong              seq FD_PARAM_UNUSED,
    3542           0 :              ulong              sig ) {
    3543             : 
    3544           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP_OUT && sig!=FD_GOSSIP_UPDATE_TAG_WFS_DONE ) ) return 1;
    3545           0 :   return 0;
    3546           0 : }
    3547             : 
    3548             : static void
    3549             : process_exec_task_done( fd_replay_tile_t *          ctx,
    3550             :                         fd_stem_context_t *         stem,
    3551             :                         fd_execrp_task_done_msg_t * msg,
    3552             :                         ulong                       sz,
    3553           0 :                         ulong                       sig ) {
    3554             : 
    3555           0 :   ulong exec_tile_idx = sig&0xFFFFFFFFUL;
    3556           0 :   FD_TEST( sz==sizeof(fd_execrp_task_done_msg_t) );
    3557             : 
    3558           0 :   fd_bank_t * bank = fd_banks_bank_query( ctx->banks, msg->bank_idx );
    3559           0 :   FD_TEST( bank );
    3560           0 :   bank->refcnt--;
    3561             : 
    3562           0 :   switch( sig>>32 ) {
    3563           0 :     case FD_EXECRP_TT_TXN_EXEC: {
    3564           0 :       ulong txn_idx           = msg->txn_exec->txn_idx;
    3565           0 :       ulong max_compute_units = fd_cost_tracker_store_peek( fd_bank_cost_tracker( bank ), bank->cost_tracker_fork_id )->block_cost_limit;
    3566           0 :       fd_cost_tracker_store_unpin( fd_bank_cost_tracker( bank ), bank->cost_tracker_fork_id );
    3567             : 
    3568           0 :       if( FD_UNLIKELY( !ctx->identity_vote_rooted ) ) {
    3569             :         /* Query the txn signature against our recently generated vote
    3570             :            txn signatures.  If the query is successful, then we have
    3571             :            seen our own vote transaction land and this should be marked
    3572             :            in the bank.  We go through this exercise until we've seen
    3573             :            our vote rooted. */
    3574           0 :         fd_txn_p_t * txn_p = fd_sched_get_txn( ctx->sched, txn_idx );
    3575             : 
    3576           0 :         fd_pubkey_t * identity_pubkey_out = NULL;
    3577           0 :         if( fd_vote_tracker_query_sig( ctx->vote_tracker, fd_type_pun_const( txn_p->payload+TXN( txn_p )->signature_off ), &identity_pubkey_out ) && fd_pubkey_eq( identity_pubkey_out, ctx->identity_pubkey ) ) {
    3578           0 :           bank->f.identity_vote_idx = ctx->identity_idx;
    3579           0 :         }
    3580           0 :       }
    3581           0 :       if( FD_UNLIKELY( !msg->txn_exec->is_committable && bank->state!=FD_BANK_STATE_DEAD) ) {
    3582             :         /* Every transaction in a valid block has to execute.
    3583             :            Otherwise, we should mark the block as dead.  Non-committable
    3584             :            means the txn failed before account loading or failed the cost
    3585             :            tracker; txn_err distinguishes the cases for telemetry. */
    3586           0 :         int dead_reason;
    3587           0 :         switch( msg->txn_exec->txn_err ) {
    3588           0 :           case FD_RUNTIME_TXN_ERR_WOULD_EXCEED_MAX_BLOCK_COST_LIMIT:
    3589           0 :             dead_reason = FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_BLOCK_COST_LIMIT;
    3590           0 :             break;
    3591           0 :           case FD_RUNTIME_TXN_ERR_WOULD_EXCEED_MAX_ACCOUNT_COST_LIMIT:
    3592           0 :             dead_reason = FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ACCOUNT_COST_LIMIT;
    3593           0 :             break;
    3594           0 :           case FD_RUNTIME_TXN_ERR_WOULD_EXCEED_MAX_VOTE_COST_LIMIT:
    3595           0 :             dead_reason = FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_VOTE_COST_LIMIT;
    3596           0 :             break;
    3597           0 :           case FD_RUNTIME_TXN_ERR_WOULD_EXCEED_ACCOUNT_DATA_BLOCK_LIMIT:
    3598           0 :           case FD_RUNTIME_TXN_ERR_WOULD_EXCEED_ACCOUNT_DATA_TOTAL_LIMIT:
    3599           0 :             dead_reason = FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ACCOUNT_DATA_LIMIT;
    3600           0 :             break;
    3601           0 :           case FD_RUNTIME_TXN_ERR_ACCOUNT_LOADED_TWICE: /* exec-path duplicate, same taxonomy as the chkdup ruling */
    3602           0 :             dead_reason = FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_DUPLICATE_ACCOUNT;
    3603           0 :             break;
    3604           0 :           default:
    3605           0 :             dead_reason = FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TXN_FAILED_TO_LOAD;
    3606           0 :         }
    3607           0 :         mark_bank_dead( ctx, stem, bank->idx, dead_reason, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED, NULL );
    3608           0 :         fd_sched_block_abandon( ctx->sched, bank->idx, FD_SCHED_ABANDON_INVALID );
    3609           0 :       }
    3610           0 :       fd_sched_txn_alts_t alts[ 1 ] = { { .cnt = msg->txn_exec->alt_writable_cnt, .addrs = fd_type_pun_const( ctx->execrp_result[ exec_tile_idx ].alt_writable ) } };
    3611           0 :       int res = fd_sched_task_done( ctx->sched, FD_SCHED_TT_TXN_EXEC, txn_idx, exec_tile_idx, alts->cnt ? alts : NULL );
    3612           0 :       if( FD_UNLIKELY( res && bank->state!=FD_BANK_STATE_DEAD ) ) {
    3613           0 :         mark_bank_dead( ctx, stem, bank->idx, sched_dead_reason_to_event( res ), FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED, NULL );
    3614           0 :       }
    3615           0 :       fd_sched_txn_info_t * txn_info = fd_sched_get_txn_info( ctx->sched, txn_idx );
    3616           0 :       txn_info->flags |= FD_SCHED_TXN_EXEC_DONE;
    3617           0 :       txn_info->bank_seq = msg->txn_exec->bank_seq;
    3618             : 
    3619           0 :       txn_info->tick_load_start        = msg->txn_exec->tick_load_start;
    3620           0 :       txn_info->tick_check_start       = msg->txn_exec->tick_check_start;
    3621           0 :       txn_info->tick_exec_start        = msg->txn_exec->tick_exec_start;
    3622           0 :       txn_info->tick_commit_start      = msg->txn_exec->tick_commit_start;
    3623           0 :       txn_info->tick_commit_end        = msg->txn_exec->tick_commit_end;
    3624             : 
    3625           0 :       txn_info->compute_units_consumed = msg->txn_exec->compute_units_consumed;
    3626           0 :       txn_info->max_compute_units      = max_compute_units;
    3627           0 :       txn_info->transaction_fee        = msg->txn_exec->transaction_fee;
    3628           0 :       txn_info->priority_fee           = msg->txn_exec->priority_fee;
    3629           0 :       txn_info->tips                   = msg->txn_exec->tips;
    3630           0 :       if( FD_LIKELY( !(txn_info->flags&FD_SCHED_TXN_SIGVERIFY_DONE)||!txn_info->txn_err ) ) { /* Set execution status if sigverify hasn't happened yet or if sigverify was a success. */
    3631           0 :         txn_info->txn_err = msg->txn_exec->txn_err;
    3632           0 :         txn_info->flags  |= fd_ulong_if( msg->txn_exec->is_committable, FD_SCHED_TXN_IS_COMMITTABLE, 0UL );
    3633           0 :         txn_info->flags  |= fd_ulong_if( msg->txn_exec->is_fees_only,   FD_SCHED_TXN_IS_FEES_ONLY,   0UL );
    3634           0 :         txn_info->flags  |= fd_ulong_if( msg->txn_exec->is_noop,        FD_SCHED_TXN_IS_NOOP,        0UL );
    3635           0 :       }
    3636           0 :       if( FD_UNLIKELY( (txn_info->flags&FD_SCHED_TXN_REPLAY_DONE)==FD_SCHED_TXN_REPLAY_DONE ) ) { /* UNLIKELY because generally exec happens before sigverify. */
    3637           0 :         publish_txn_executed( ctx, stem, bank->idx, txn_idx );
    3638           0 :       }
    3639           0 :       break;
    3640           0 :     }
    3641           0 :     case FD_EXECRP_TT_TXN_SIGVERIFY: {
    3642           0 :       ulong txn_idx = msg->txn_sigverify->txn_idx;
    3643           0 :       fd_sched_txn_info_t * txn_info = fd_sched_get_txn_info( ctx->sched, txn_idx );
    3644           0 :       txn_info->flags |= FD_SCHED_TXN_SIGVERIFY_DONE;
    3645           0 :       if( FD_UNLIKELY( msg->txn_sigverify->err ) ) {
    3646           0 :         txn_info->txn_err = FD_RUNTIME_TXN_ERR_SIGNATURE_FAILURE;
    3647           0 :         txn_info->flags  &= ~FD_SCHED_TXN_IS_COMMITTABLE;
    3648           0 :         txn_info->flags  &= ~FD_SCHED_TXN_IS_FEES_ONLY;
    3649           0 :         txn_info->flags  &= ~FD_SCHED_TXN_IS_NOOP;
    3650           0 :       }
    3651           0 :       if( FD_UNLIKELY( msg->txn_sigverify->err && bank->state!=FD_BANK_STATE_DEAD ) ) {
    3652             :         /* Every transaction in a valid block has to sigverify.
    3653             :            Otherwise, we should mark the block as dead.  Also freeze the
    3654             :            bank if possible. */
    3655           0 :         mark_bank_dead( ctx, stem, bank->idx, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_SIGVERIFY_FAILED, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED, NULL );
    3656           0 :         fd_sched_block_abandon( ctx->sched, bank->idx, FD_SCHED_ABANDON_INVALID );
    3657           0 :       }
    3658           0 :       int res = fd_sched_task_done( ctx->sched, FD_SCHED_TT_TXN_SIGVERIFY, txn_idx, exec_tile_idx, NULL );
    3659           0 :       FD_TEST( res==0 );
    3660           0 :       if( FD_LIKELY( (txn_info->flags&FD_SCHED_TXN_REPLAY_DONE)==FD_SCHED_TXN_REPLAY_DONE ) ) {
    3661           0 :         publish_txn_executed( ctx, stem, bank->idx, txn_idx );
    3662           0 :       }
    3663           0 :       break;
    3664           0 :     }
    3665           0 :     case FD_EXECRP_TT_POH_HASH: {
    3666           0 :       int res = fd_sched_task_done( ctx->sched, FD_SCHED_TT_POH_HASH, ULONG_MAX, exec_tile_idx, msg->poh_hash );
    3667           0 :       if( FD_UNLIKELY( res && bank->state!=FD_BANK_STATE_DEAD ) ) {
    3668           0 :         mark_bank_dead( ctx, stem, bank->idx, sched_dead_reason_to_event( res ), FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED, NULL );
    3669           0 :       }
    3670           0 :       break;
    3671           0 :     }
    3672           0 :     case FD_EXECRP_TT_CERT_VERIFY: {
    3673           0 :       FD_TEST( bank->footer_task_cnt );
    3674           0 :       bank->footer_task_cnt--;
    3675           0 :       if( msg->cert_verify->has_hm ) fd_footer_cache_hash_insert( ctx->footer_cache, ctx->cert_payload[ exec_tile_idx ], ctx->cert_payload_sz[ exec_tile_idx ], &msg->cert_verify->hm );
    3676           0 :       if( FD_UNLIKELY( msg->cert_verify->err && bank->state!=FD_BANK_STATE_DEAD ) ) {
    3677           0 :         mark_bank_dead( ctx, stem, bank->idx, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_BAD_FOOTER, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED, NULL );
    3678           0 :         fd_sched_block_abandon( ctx->sched, bank->idx, FD_SCHED_ABANDON_INVALID );
    3679           0 :       }
    3680             :       /* The last footer task back finishes and publishes the block,
    3681             :          before sched can start its child. */
    3682           0 :       if( !bank->footer_task_cnt && ctx->footer_end[ bank->idx ].finalized && bank->state==FD_BANK_STATE_REPLAYABLE && !fd_sched_block_is_discarded( ctx->sched, bank->idx ) ) {
    3683           0 :         if( FD_UNLIKELY( replay_block_end( ctx, stem, bank, fd_sched_get_footer( ctx->sched, bank->idx ), bank->execution_fees_pre_settle, bank->priority_fees_pre_settle ) ) ) {
    3684           0 :           fd_sched_block_abandon( ctx->sched, bank->idx, FD_SCHED_ABANDON_INVALID );
    3685           0 :         }
    3686           0 :       }
    3687           0 :       FD_TEST( !fd_sched_task_done( ctx->sched, FD_SCHED_TT_CERT_VERIFY, ULONG_MAX, exec_tile_idx, NULL ) );
    3688           0 :       break;
    3689           0 :     }
    3690           0 :     case FD_EXECRP_TT_REWARDS: {
    3691           0 :       FD_TEST( bank->footer_task_cnt && ctx->rewards_in_flight_cnt );
    3692           0 :       bank->footer_task_cnt--;
    3693           0 :       ctx->rewards_in_flight_cnt--;
    3694           0 :       if( FD_UNLIKELY( msg->rewards->err ) ) {
    3695           0 :         if( bank->state!=FD_BANK_STATE_DEAD ) {
    3696           0 :           mark_bank_dead( ctx, stem, bank->idx, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_BAD_FOOTER, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED, NULL );
    3697           0 :           fd_sched_block_abandon( ctx->sched, bank->idx, FD_SCHED_ABANDON_INVALID );
    3698           0 :         }
    3699           0 :       } else {
    3700           0 :         fd_replay_footer_end_t * end = ctx->footer_end + bank->idx;
    3701           0 :         end->leader_credits = fd_ulong_sat_add( end->leader_credits, msg->rewards->leader_credits );
    3702           0 :         fd_lthash_add( fd_bank_lthash_locking_modify( bank ), &ctx->execrp_result[ exec_tile_idx ].lthash );
    3703           0 :         fd_bank_lthash_end_locking_modify( bank );
    3704           0 :       }
    3705             :       /* The last footer task back finishes and publishes the block,
    3706             :          before sched can start its child. */
    3707           0 :       if( !bank->footer_task_cnt && ctx->footer_end[ bank->idx ].finalized && bank->state==FD_BANK_STATE_REPLAYABLE && !fd_sched_block_is_discarded( ctx->sched, bank->idx ) ) {
    3708           0 :         if( FD_UNLIKELY( replay_block_end( ctx, stem, bank, fd_sched_get_footer( ctx->sched, bank->idx ), bank->execution_fees_pre_settle, bank->priority_fees_pre_settle ) ) ) {
    3709           0 :           fd_sched_block_abandon( ctx->sched, bank->idx, FD_SCHED_ABANDON_INVALID );
    3710           0 :         }
    3711           0 :       }
    3712           0 :       FD_TEST( !fd_sched_task_done( ctx->sched, FD_SCHED_TT_REWARDS, ULONG_MAX, exec_tile_idx, NULL ) );
    3713           0 :       break;
    3714           0 :     }
    3715           0 :     case FD_EXECRP_TT_LTHASH_SUB:
    3716           0 :     case FD_EXECRP_TT_LTHASH_ADD: {
    3717           0 :       ulong task_type = (sig>>32)==FD_EXECRP_TT_LTHASH_SUB ? FD_SCHED_TT_LTHASH_SUB : FD_SCHED_TT_LTHASH_ADD;
    3718           0 :       int res = fd_sched_task_done( ctx->sched, task_type, ULONG_MAX, exec_tile_idx, &ctx->execrp_result[ exec_tile_idx ].lthash );
    3719           0 :       FD_TEST( res==0 );
    3720           0 :       break;
    3721           0 :     }
    3722           0 :     default: FD_LOG_CRIT(( "unexpected sig 0x%lx", sig ));
    3723           0 :   }
    3724             : 
    3725             :   /* Reference counter just decreased, and an exec tile just got freed
    3726             :      up.  If there's a need to be more aggressively pruning, we could
    3727             :      check here if more slots just became publishable and publish.  Not
    3728             :      publishing here shouldn't bloat the fork tree too much though.  We
    3729             :      mark minority forks dead as soon as we can, and execution dispatch
    3730             :      stops on dead blocks.  So shortly afterwards, dead blocks should be
    3731             :      eligible for pruning as in-flight transactions retire from the
    3732             :      execution pipeline. */
    3733             : 
    3734           0 : }
    3735             : 
    3736             : /* Essentially an inlined version of try_process_fec that reads off the
    3737             :    dcache directly, instead of the reasm out_queue.
    3738             :    All FECs processed by this function must be safe to be forever
    3739             :    removed from the dcache. */
    3740             : static void
    3741             : process_rotor_fec( fd_replay_tile_t       * ctx,
    3742             :                    fd_stem_context_t      * stem,
    3743           0 :                    fd_repair_out_fec_t    * fec ) {
    3744           0 :   if( FD_LIKELY( fec->highest_fec_complete_slot && ( ctx->catch_up_max_fec_slot==ULONG_MAX || fec->highest_fec_complete_slot>ctx->catch_up_max_fec_slot ) ) ) {
    3745           0 :     ctx->catch_up_max_fec_slot = fec->highest_fec_complete_slot;
    3746           0 :     ctx->catch_up_tip_advance_cnt++;
    3747           0 :   }
    3748             : 
    3749           0 :   ulong parent_bank_idx = ULONG_MAX;
    3750           0 :   if( FD_UNLIKELY( fec->fec_set_idx==fec->update_parent_fec_idx ) ) {
    3751           0 :     fd_block_id_t parent_key = fd_block_id( fec->parent_slot, fec->parent_block_id.uc );
    3752           0 :     fd_block_id_ele_t * parent = fd_block_id_map_ele_query( ctx->block_id_map, &parent_key, NULL, ctx->block_id_arr );
    3753           0 :     FD_TEST( parent );
    3754           0 :     parent_bank_idx = fd_block_id_ele_get_idx( ctx->block_id_arr, parent );
    3755           0 :   } else {
    3756             :     /* mid-slot FEC: rotor tile promises to populate block_id
    3757             :        correctly for blocks with known block id.  If we are processing
    3758             :        the last FEC set of a slot received through turbine though, the
    3759             :        block_id_map will still be keyed with {0}, but the block_id on
    3760             :        the FEC will have the computed DMR.  We first try to replay
    3761             :        this FEC on a bank keyed with the computed DMR - if it doesn't
    3762             :        exist, it must belong to the original turbine version. */
    3763           0 :     fd_block_id_t key = { .slot = fec->slot };
    3764           0 :     if( FD_LIKELY( fec->is_known ) ) {
    3765           0 :       key = fd_block_id( fec->slot, fec->block_id.uc );
    3766           0 :     }
    3767             : 
    3768           0 :     fd_block_id_ele_t * parent = fd_block_id_map_ele_query( ctx->block_id_map, &key, NULL, ctx->block_id_arr );
    3769           0 :     FD_TEST( parent );
    3770           0 :     parent_bank_idx = fd_block_id_ele_get_idx( ctx->block_id_arr, parent );
    3771           0 :   }
    3772             : 
    3773           0 :   fd_bank_t * parent_bank = fd_banks_bank_query( ctx->banks, parent_bank_idx );
    3774           0 :   FD_TEST( parent_bank );
    3775             : 
    3776           0 :   if( FD_UNLIKELY( parent_bank->state==FD_BANK_STATE_DEAD ) ) {
    3777           0 :     FD_LOG_WARNING(( "parent bank is dead for slot %lu, fec set idx %u, parent slot %lu, dropping", fec->slot, fec->fec_set_idx, fec->parent_slot ));
    3778           0 :     return;
    3779           0 :   }
    3780             : 
    3781           0 :   long work = fd_tickcount();
    3782             : 
    3783           0 :   fd_store_fec_data_view_t data_view[1];
    3784           0 :   fd_rwlock_read( &ctx->store->fec_lock );
    3785           0 :   fd_store_fec_t * store_fec = fd_store_query( ctx->map_join, &fec->mr );
    3786           0 :   ctx->metrics.store_query_cnt++;
    3787           0 :   int view_err = store_fec ? fd_store_fec_data_view( ctx->store, ctx->store_disk_fd, store_fec, data_view ) : -1;
    3788           0 :   fd_rwlock_unread( &ctx->store->fec_lock );
    3789             : 
    3790             :   /* A missing store entry is expected: replay removes FEC sets when
    3791             :      publishing the root, so a FEC delivered for a slice that has
    3792             :      since been pruned/rooted is no longer in the store.  The lookup and
    3793             :      payload pin are one lifetime transaction under fec_lock: remove may
    3794             :      unlink the key only after the view has pinned the pool element. */
    3795           0 :   if( FD_UNLIKELY( !store_fec || view_err ) ) {
    3796           0 :     ctx->metrics.store_query_missing_cnt++;
    3797           0 :     FD_BASE58_ENCODE_32_BYTES( fec->mr.key, key_b58 );
    3798           0 :     FD_LOG_INFO(( "store fec for slot: %lu unavailable (pruned by publish); abandoning slice. root: %lu. merkle: %s", fec->slot, ctx->consensus_root_slot, key_b58 ));
    3799           0 :     return;
    3800           0 :   }
    3801             : 
    3802           0 :   long now = fd_log_wallclock();
    3803           0 :   fd_block_id_ele_t * block_id_ele;
    3804           0 :   fd_bank_t *         bank;
    3805           0 :   if( FD_UNLIKELY( fec->fec_set_idx==fec->update_parent_fec_idx ) ) {
    3806             :     /* Give up on the prefix bank if it exists: the block is not at
    3807             :        fault, so its sched block is discarded, not invalid, and
    3808             :        SLOT_DEAD is not published. */
    3809           0 :     fd_block_id_t       prefix_key = fec->is_known ? fd_block_id( fec->slot, fec->block_id.uc ) : (fd_block_id_t){ .slot = fec->slot };
    3810           0 :     fd_block_id_ele_t * prefix     = fec->update_parent_fec_idx && !fec->is_leader ? live_block_id_ele( ctx, fd_block_id_map_ele_query( ctx->block_id_map, &prefix_key, NULL, ctx->block_id_arr ) ) : NULL;
    3811           0 :     if( FD_UNLIKELY( prefix ) ) {
    3812           0 :       ulong opt_idx = fd_block_id_ele_get_idx( ctx->block_id_arr, prefix );
    3813           0 :       FD_TEST( fd_block_id_map_ele_remove( ctx->block_id_map, &prefix->block_info, NULL, ctx->block_id_arr )==prefix );
    3814           0 :       fd_sched_block_abandon( ctx->sched, opt_idx, FD_SCHED_ABANDON_DISCARDED );
    3815           0 :       ulong dead_idxs_cnt = 0UL;
    3816           0 :       fd_banks_mark_bank_dead( ctx->banks, opt_idx, ctx->dead_idxs, &dead_idxs_cnt );
    3817           0 :       for( ulong i=0UL; i<dead_idxs_cnt; i++ ) {
    3818           0 :         fd_block_id_ele_t * dead = &ctx->block_id_arr[ ctx->dead_idxs[ i ] ];
    3819           0 :         timing_slot_release( ctx, ctx->dead_idxs[ i ] );
    3820           0 :         report_block_incomplete( ctx, dead->slot, dead->block_id_seen ? &dead->dmr : &dead->latest_mr, fd_banks_bank_query( ctx->banks, ctx->dead_idxs[ i ] ),
    3821           0 :                                  FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_REPARENTED );
    3822           0 :       }
    3823           0 :       FD_LOG_INFO(( "abandoned optimistic bank (idx=%lu, slot=%lu), replaying the slot from its UpdateParent", opt_idx, fec->slot ));
    3824           0 :       try_prune_sched( ctx );
    3825           0 :     }
    3826           0 :     bank = fec->is_leader ? ctx->leader_bank : fd_banks_new_bank( ctx->banks, parent_bank_idx, now, 0 );
    3827           0 :     if( FD_UNLIKELY( fec->is_leader && ctx->leader_stats.slot==fec->slot ) ) {
    3828           0 :       ctx->leader_stats.first_fec_returned_nanos = now;
    3829           0 :     }
    3830             : 
    3831           0 :     block_id_ele = &ctx->block_id_arr[ bank->idx ];
    3832             :     /* Clear the previous occupant of this reused bank idx  */
    3833           0 :     if( FD_LIKELY( fd_block_id_map_ele_query( ctx->block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr )==block_id_ele ) ) {
    3834           0 :       FD_TEST( fd_block_id_map_ele_remove( ctx->block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr ) );
    3835           0 :     }
    3836             : 
    3837           0 :     block_id_ele->block_id_seen  = 0;
    3838           0 :     block_id_ele->bank_seq       = bank->bank_seq;
    3839           0 :     block_id_ele->slot           = fec->slot;
    3840           0 :     block_id_ele->latest_fec_idx = fec->fec_set_idx;
    3841           0 :     block_id_ele->fec_cnt        = 0U;
    3842           0 :     block_id_ele->finalized      = 0;
    3843           0 :     block_id_ele->block_info     = fec->is_known ? fd_block_id( fec->slot, fec->block_id.uc ) : (fd_block_id_t){ .slot = fec->slot };
    3844             : 
    3845             :     /* If this fec 0 is rebuilding an evicted block (recovery redelivery),
    3846             :        the evicted bank's stale map entry may still hold this key at a
    3847             :        DIFFERENT idx. */
    3848           0 :     fd_block_id_map_ele_remove( ctx->block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr ); // evicted / stale entry
    3849           0 :     FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, block_id_ele, ctx->block_id_arr ) );
    3850           0 :   } else { /* FEC for the middle or end of a block */
    3851             :     /* Assign bank idx + seqno to the FEC.  Update block id pool ele.
    3852             :        The block stays keyed by {slot, 0} in the compound map. */
    3853           0 :     block_id_ele = &ctx->block_id_arr[ parent_bank_idx ];
    3854           0 :     block_id_ele->latest_fec_idx = fec->fec_set_idx;
    3855           0 :     bank = fd_banks_bank_query( ctx->banks, parent_bank_idx );
    3856           0 :   }
    3857             : 
    3858           0 :   block_id_ele->latest_mr = fec->mr;
    3859             : 
    3860           0 :   if( FD_UNLIKELY( ctx->report_runtime_diffs && block_id_ele->fec_cnt<FD_FEC_BLK_MAX ) ) {
    3861           0 :     ctx->fec_chain[ bank->idx*FD_FEC_BLK_MAX + block_id_ele->fec_cnt ] = fec->mr;
    3862           0 :   }
    3863           0 :   block_id_ele->fec_cnt++;
    3864             : 
    3865           0 :   fd_reception_stats_t * stats = &ctx->reception_stats[ bank->idx ];
    3866           0 :   stats->bank_seq      = bank->bank_seq;
    3867           0 :   stats->fec_set_idx   = fec->fec_set_idx;
    3868           0 :   stats->votor_repaired = fec->votor_repaired;
    3869             : 
    3870           0 :   if( FD_UNLIKELY( fec->slot_complete ) ) {
    3871           0 :     FD_BASE58_ENCODE_32_BYTES( fec->block_id.uc, block_id_b58 );
    3872           0 :     FD_LOG_INFO(( "slot %lu fec set idx %u slot_bid %s is complete, inserted to map. finalized: %d", fec->slot, fec->fec_set_idx, block_id_b58, fec->is_finalized ));
    3873           0 :     block_id_ele->block_id_seen = 1;
    3874           0 :     block_id_ele->dmr           = fec->block_id;
    3875           0 :     block_id_ele->finalized     = !!fec->is_finalized;
    3876             : 
    3877             :     /* Block is complete and DMR block id is now known, so re-key. */
    3878           0 :     FD_TEST( fd_block_id_map_ele_remove( ctx->block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr )==block_id_ele );
    3879           0 :     fd_block_id_t key = fd_block_id( fec->slot, fec->block_id.uc );
    3880           0 :     block_id_ele->block_info = key;
    3881           0 :     fd_block_id_map_ele_remove( ctx->block_id_map, &key, NULL, ctx->block_id_arr ); // evicted / stale entry
    3882           0 :     FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, block_id_ele, ctx->block_id_arr ) );
    3883           0 :   }
    3884             : 
    3885             :   /* For leader FECs, don't insert the FEC into the scheduler. */
    3886           0 :   if( FD_UNLIKELY( fec->is_leader ) ) {
    3887           0 :     fd_store_fec_data_view_release( ctx->store, data_view );
    3888           0 :     return;
    3889           0 :   }
    3890             : 
    3891             :   /* Forks form a partial ordering over FEC sets. The Rotor tile
    3892             :      delivers FEC sets in-order per fork, but FEC set ordering across
    3893             :      forks is arbitrary */
    3894           0 :   fd_sched_fec_t sched_fec[ 1 ];
    3895           0 :   sched_fec->shred_cnt         = FD_FEC_SHRED_CNT;
    3896           0 :   sched_fec->is_last_in_batch  = !!fec->data_complete;
    3897           0 :   sched_fec->is_last_in_block  = !!fec->slot_complete;
    3898           0 :   sched_fec->bank_idx          = bank->idx;
    3899           0 :   sched_fec->parent_bank_idx   = bank->parent_idx;
    3900           0 :   sched_fec->slot              = fec->slot;
    3901           0 :   sched_fec->parent_slot       = fec->parent_slot;
    3902           0 :   sched_fec->is_first_in_block       = fec->fec_set_idx==fec->update_parent_fec_idx;
    3903           0 :   sched_fec->starts_at_update_parent = sched_fec->is_first_in_block && fec->update_parent_fec_idx!=0U; /* the feature is checked at BLOCK_START, after the bank is cloned */
    3904           0 :   sched_fec->fec               = store_fec;
    3905           0 :   sched_fec->data              = data_view->data;
    3906           0 :   sched_fec->alut_ctx->fork_id = fd_banks_bank_query( ctx->banks, ctx->published_root_bank_idx )->accdb_fork_id;
    3907           0 :   sched_fec->alut_ctx->accdb   = ctx->accdb;
    3908           0 :   sched_fec->alut_ctx->els     = ctx->published_root_slot;
    3909           0 :   sched_fec->completed_ns      = fec->fec_completed_ts_nanos;
    3910             : 
    3911           0 :   if( sched_fec->is_first_in_block ) {
    3912           0 :     bank->refcnt++;
    3913           0 :     FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for sched", bank->idx, sched_fec->slot, bank->refcnt ));
    3914           0 :   }
    3915             : 
    3916           0 :   int ingested = fd_sched_fec_ingest( ctx->sched, sched_fec );
    3917           0 :   fd_store_fec_data_view_release( ctx->store, data_view );
    3918             : 
    3919           0 :   if( FD_UNLIKELY( !ingested ) ) {
    3920           0 :     int dr = sched_block_dead_reason_to_event( ctx, sched_fec->bank_idx );
    3921           0 :     int ar = dr==FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD ? FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED
    3922           0 :                                                                : FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED;
    3923           0 :     mark_bank_dead( ctx, stem, sched_fec->bank_idx, dr, ar, NULL );
    3924           0 :     return;
    3925           0 :   }
    3926             : 
    3927           0 :   fd_histf_sample( ctx->metrics.store_query_work, (ulong)( fd_tickcount() - work ) );
    3928           0 :   ctx->execrp_idle_cnt = 0UL;
    3929           0 :   return;
    3930           0 : }
    3931             : 
    3932             : static void
    3933           0 : process_resolv_slot_completed( fd_replay_tile_t * ctx, ulong bank_idx ) {
    3934           0 :   fd_bank_t * bank = fd_banks_bank_query( ctx->banks, bank_idx );
    3935           0 :   FD_TEST( bank );
    3936           0 :   bank->refcnt--;
    3937           0 :   FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt decremented to %lu for resolv", bank->idx, bank->f.slot, bank->refcnt ));
    3938           0 : }
    3939             : 
    3940             : static void
    3941             : process_vote_txn_sent( fd_replay_tile_t *  ctx,
    3942           0 :                        fd_txn_m_t *        txnm ) {
    3943             :   /* The send tile has signed and sent a vote.  Add this vote to the
    3944             :      vote tracker.  We go through this exercise until the client has
    3945             :      seen a vote corresponding to the current identity rooted. */
    3946           0 :   if( FD_UNLIKELY( !ctx->identity_vote_rooted ) ) {
    3947           0 :     uchar *    payload = (uchar *)txnm + sizeof(fd_txn_m_t);
    3948           0 :     uchar      txn_mem[ FD_TXN_MAX_SZ ] __attribute__((aligned(alignof(fd_txn_t))));
    3949           0 :     fd_txn_t * txn = (fd_txn_t *)txn_mem;
    3950           0 :     if( FD_UNLIKELY( !fd_txn_parse( payload, txnm->payload_sz, txn_mem, NULL ) ) ) {
    3951           0 :       FD_LOG_CRIT(( "Could not parse txn from send tile" ));
    3952           0 :     }
    3953             :     /* The identity of the validator that the signed the vote will
    3954             :        always be the first signer in the vote transaction. */
    3955           0 :     fd_pubkey_t * vote_identity = fd_type_pun( payload+txn->acct_addr_off );
    3956           0 :     fd_vote_tracker_insert( ctx->vote_tracker, vote_identity, fd_type_pun_const( payload+txn->signature_off ) );
    3957           0 :   }
    3958           0 : }
    3959             : 
    3960             : static inline void
    3961           0 : maybe_verify_shred_version( fd_replay_tile_t * ctx ) {
    3962           0 :   if( FD_LIKELY( ctx->has_genesis_hash && ctx->hard_fork_cnt!=ULONG_MAX ) ) {
    3963           0 :     ctx->shred_version = compute_shred_version( ctx->genesis_hash->uc, ctx->hard_forks, ctx->hard_fork_cnt );
    3964           0 :   }
    3965             : 
    3966           0 :   if( FD_LIKELY( ctx->expected_shred_version && ctx->ipecho_shred_version ) ) {
    3967           0 :     if( FD_UNLIKELY( ctx->expected_shred_version!=ctx->ipecho_shred_version ) ) {
    3968           0 :       FD_LOG_ERR(( "shred version mismatch: expected %u but got %u from ipecho", ctx->expected_shred_version, ctx->ipecho_shred_version ) );
    3969           0 :     }
    3970           0 :   }
    3971             : 
    3972             :   /* The configured shred version is the post-restart one.  Compare it
    3973             :      only on the boot bank: before boot the hard forks are the full
    3974             :      snapshot's, predating the restart. */
    3975           0 :   if( FD_UNLIKELY( ctx->wfs_enabled && !ctx->is_booted && ctx->expected_shred_version ) ) return;
    3976             : 
    3977           0 :   if( FD_LIKELY( ctx->shred_version && (ctx->expected_shred_version || ctx->ipecho_shred_version) ) ) {
    3978           0 :     ushort expected_shred_version = ctx->expected_shred_version ? ctx->expected_shred_version : ctx->ipecho_shred_version;
    3979           0 :     ushort actual_shred_version   = ctx->shred_version;
    3980             : 
    3981           0 :     if( FD_UNLIKELY( expected_shred_version!=actual_shred_version ) ) {
    3982           0 :       char const * expected_source = ctx->expected_shred_version ?
    3983           0 :                                      "[consensus.expected_shred_version]" : "the boot entrypoint";
    3984           0 :       FD_LOG_ERR(( "genesis.bin at `%s` plus the loaded snapshot's hard_forks give shred version %hu, "
    3985           0 :                    "but %s reports %hu. Delete genesis.bin to re-download it, or the snapshot if it "
    3986           0 :                    "predates the latest hard fork, and restart.",
    3987           0 :                    ctx->genesis_path, actual_shred_version, expected_source, expected_shred_version ));
    3988           0 :     }
    3989           0 :   }
    3990           0 : }
    3991             : 
    3992             : static inline void
    3993           0 : maybe_verify_genesis_timestamp( fd_replay_tile_t * ctx ) {
    3994           0 :   if( FD_LIKELY( !ctx->has_expected_genesis_timestamp || !ctx->has_genesis_timestamp ) ) return;
    3995           0 :   if( FD_LIKELY( ctx->genesis_timestamp==ctx->expected_genesis_timestamp ) ) return;
    3996             : 
    3997           0 :   FD_LOG_ERR(( "Your genesis.bin file at `%s` has a genesis timestamp of %lu but the snapshot you loaded has a genesis "
    3998           0 :                "timestamp of %lu. This either means that the genesis.bin file you have is for a different cluster than "
    3999           0 :                "the one you are trying to connect to, or you have loaded a snapshot for the wrong cluster. In either "
    4000           0 :                "case, you can delete the problematic file and restart the node to download the correct one automatically.",
    4001           0 :                ctx->genesis_path, ctx->genesis_timestamp, ctx->expected_genesis_timestamp ));
    4002           0 : }
    4003             : 
    4004             : /* snapmk_start instructs the snapmk tile to start producing a snapshot. */
    4005             : 
    4006             : static void
    4007             : snapmk_start( fd_replay_tile_t *  ctx,
    4008             :               fd_stem_context_t * stem,
    4009           0 :               int                 incremental ) {
    4010             : 
    4011           0 :   FD_CHECK_CRIT( !ctx->snapmk.active, "snapshot creation already in progress" );
    4012             : 
    4013             :   /* pin current produced bank */
    4014           0 :   fd_bank_t * bank = fd_banks_bank_query( ctx->banks, ctx->published_root_bank_idx );
    4015           0 :   FD_CHECK_CRIT( bank, "invalid published_root_bank_idx" );
    4016             : 
    4017           0 :   if( FD_UNLIKELY( incremental ) ) {
    4018           0 :     FD_CHECK_CRIT( ctx->snapmk.base_slot!=ULONG_MAX, "incremental snapshot without a base full snapshot" );
    4019           0 :     FD_CHECK_CRIT( bank->f.slot>ctx->snapmk.base_slot, "incremental snapshot at or below its base slot" );
    4020           0 :   }
    4021             : 
    4022           0 :   bank->refcnt++;
    4023           0 :   ctx->snapmk.bank_idx    = bank->idx;
    4024           0 :   ctx->snapmk.incremental = !!incremental;
    4025             : 
    4026             :   /* Snapmk reads the bank's epoch credits in place, so they stay
    4027             :      pinned in memory until snapmk_done. */
    4028           0 :   FD_TEST( fd_epoch_credits_view_init( ctx->snapmk.epoch_credits_view, fd_bank_epoch_credits( bank ), bank->epoch_credits_fork_id ) );
    4029             : 
    4030             :   /* Send SNAP_START message to snapmk. */
    4031           0 :   fd_replay_snap_start_t * msg = fd_chunk_to_laddr( ctx->snapmk_out->mem, ctx->snapmk_out->chunk );
    4032           0 :   *msg = (fd_replay_snap_start_t) {
    4033           0 :     .bank_idx  = ctx->published_root_bank_idx,
    4034           0 :     .base_slot = incremental ? ctx->snapmk.base_slot : bank->f.slot,
    4035           0 :     .slot      = bank->f.slot,
    4036           0 :     .leader    = bank->leader
    4037           0 :   };
    4038           0 :   ulong out_idx = ctx->snapmk_out->idx;
    4039           0 :   ulong sig     = REPLAY_SIG_SNAP_START;
    4040           0 :   ulong chunk   = ctx->snapmk_out->chunk;
    4041           0 :   ulong tspub   = fd_frag_meta_ts_comp( fd_tickcount() );
    4042           0 :   ulong sz      = sizeof(fd_replay_snap_start_t);
    4043           0 :   ulong seq     = fd_stem_publish( stem, out_idx, sig, chunk, sz, 0UL, 0UL, tspub );
    4044           0 :   ctx->snapmk_out->chunk = fd_dcache_compact_next( ctx->snapmk_out->chunk, sz, ctx->snapmk_out->chunk0, ctx->snapmk_out->wmark );
    4045             : 
    4046             :   /* wake up the snapmk tile */
    4047           0 :   fd_frag_meta_t * replay_snapmk = stem->mcaches[ out_idx ];
    4048           0 :   ulong *          snap_sync     = fd_mcache_seq_laddr( replay_snapmk );
    4049           0 :   fd_mcache_seq_update( snap_sync, fd_seq_inc( seq, 1UL ) );
    4050           0 :   long ret = syscall( SYS_futex, snap_sync, FUTEX_WAKE, 1 );
    4051           0 :   if( FD_UNLIKELY( ret<0 ) ) {
    4052           0 :     FD_LOG_ERR(( "FUTEX_WAKE(snap_sync,seq=%u) failed (%i-%s)", (uint)seq, errno, fd_io_strerror( errno ) ));
    4053           0 :   }
    4054             : 
    4055             :   /* update internal state */
    4056           0 :   ctx->snapmk.active = 1;
    4057           0 : }
    4058             : 
    4059             : /* snapmk_done reacts to the snapmk tile reporting completion. */
    4060             : 
    4061             : static void
    4062             : snapmk_done( fd_replay_tile_t *  ctx,
    4063             :              fd_stem_context_t * stem,
    4064           0 :              int                 success ) {
    4065           0 :   (void)stem;
    4066             : 
    4067           0 :   FD_CHECK_CRIT( ctx->snapmk.active, "spurious snap complete msg (not creating snapshot)" );
    4068             : 
    4069             :   /* release bank */
    4070           0 :   fd_bank_t * bank = fd_banks_bank_query( ctx->banks, ctx->snapmk.bank_idx );
    4071           0 :   FD_CHECK_CRIT( bank, "invalid snapmk.bank_idx" );
    4072           0 :   FD_CHECK_CRIT( bank->refcnt > 0UL, "invalid snapmk.bank_idx refcnt" );
    4073             : 
    4074             :   /* A completed full snapshot becomes the base of later incrementals. */
    4075           0 :   if( FD_LIKELY( success && !ctx->snapmk.incremental ) ) {
    4076           0 :     ctx->snapmk.base_slot = bank->f.slot;
    4077           0 :   }
    4078             : 
    4079           0 :   if( FD_LIKELY( success ) ) {
    4080           0 :     if( FD_UNLIKELY( ctx->snapmk.incremental ) ) {
    4081           0 :       ctx->snapmk.snap_finished_incr = bank->f.slot;
    4082           0 :       ctx->snapmk.snap_produced_incr_cnt++;
    4083           0 :     } else {
    4084           0 :       ctx->snapmk.snap_finished_full = bank->f.slot;
    4085           0 :     }
    4086           0 :   }
    4087             : 
    4088           0 :   fd_epoch_credits_view_fini( ctx->snapmk.epoch_credits_view );
    4089           0 :   bank->refcnt--;
    4090           0 :   ctx->snapmk.active = 0;
    4091           0 : }
    4092             : 
    4093             : static void
    4094             : msg_snapmk( fd_replay_tile_t *  ctx,
    4095             :             fd_stem_context_t * stem,
    4096             :             ulong               msg_type,
    4097             :             ulong               in_idx,
    4098           0 :             ulong               chunk ) {
    4099           0 :   switch( msg_type ) {
    4100           0 :   case FD_SNAPMK_MSG_FOUND: {
    4101           0 :     fd_snapmk_msg_found_t const * found = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
    4102           0 :     if( FD_UNLIKELY( found->base_slot==ULONG_MAX ) ) {
    4103           0 :       if( found->slot > ctx->snapmk.snap_finished_full ) ctx->snapmk.snap_finished_full = found->slot;
    4104           0 :     } else {
    4105           0 :       if( found->slot > ctx->snapmk.snap_finished_incr ) ctx->snapmk.snap_finished_incr = found->slot;
    4106           0 :     }
    4107           0 :     break;
    4108           0 :   }
    4109           0 :   case FD_SNAPMK_MSG_DELETED: {
    4110             :     /* Zeroing loses older surviving snapshots, but keeping a stale
    4111             :        watermark would claim one that no longer exists. */
    4112           0 :     fd_snapmk_msg_deleted_t const * deleted = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
    4113           0 :     if( FD_UNLIKELY( deleted->base_slot==ULONG_MAX ) ) {
    4114           0 :       if( deleted->slot==ctx->snapmk.snap_finished_full ) ctx->snapmk.snap_finished_full = 0UL;
    4115           0 :     } else {
    4116           0 :       if( deleted->slot==ctx->snapmk.snap_finished_incr ) ctx->snapmk.snap_finished_incr = 0UL;
    4117           0 :     }
    4118           0 :     break;
    4119           0 :   }
    4120           0 :   case FD_SNAPMK_MSG_CREATED:
    4121           0 :     snapmk_done( ctx, stem, 1 );
    4122           0 :     break;
    4123           0 :   case FD_SNAPMK_MSG_FAILED:
    4124           0 :     snapmk_done( ctx, stem, 0 );
    4125           0 :     break;
    4126           0 :   default:
    4127           0 :     break;
    4128           0 :   }
    4129           0 : }
    4130             : 
    4131             : /* admin command handlers
    4132             :    every admin command must trigger one response frag */
    4133             : 
    4134             : static void
    4135             : admin_respond( fd_replay_tile_t *  ctx,
    4136             :                fd_stem_context_t * stem,
    4137             :                ulong               orig,
    4138           0 :                ulong               err ) {
    4139           0 :   ulong ctl   = fd_frag_meta_ctl( orig, 0, 0, !!err );
    4140           0 :   ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
    4141           0 :   fd_stem_publish( stem, ctx->admin_out_idx, err, 0UL, 0UL, ctl, 0UL, tspub );
    4142           0 : }
    4143             : 
    4144             : static void
    4145             : admin_snap_create( fd_replay_tile_t *  ctx,
    4146             :                    fd_stem_context_t * stem,
    4147           0 :                    ulong               sig ) {
    4148           0 :   ulong target_slot = sig;
    4149             : 
    4150           0 :   if( FD_UNLIKELY( !ctx->snapmk.supported ) ) {
    4151           0 :     FD_LOG_WARNING(( "admin requested snapshot creation, but current config cannot create snapshots. increase [layout.snapzp_tile_count]?" ));
    4152           0 :     admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_ADMINCTL_RESULT_UNSUPPORTED );
    4153           0 :     return;
    4154           0 :   }
    4155             : 
    4156           0 :   if( FD_UNLIKELY( !ctx->is_booted ) ) {
    4157           0 :     FD_LOG_WARNING(( "admin requested snapshot creation, but client has not yet started" ));
    4158           0 :     admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_SNAPSHOT_CREATE_RESULT_NOT_READY );
    4159           0 :     return;
    4160           0 :   }
    4161             : 
    4162           0 :   if( FD_UNLIKELY( target_slot ) ) {
    4163           0 :     if( FD_UNLIKELY( target_slot<=ctx->published_root_slot ) ) {
    4164           0 :       FD_LOG_WARNING(( "admin requested snapshot creation at slot %lu, but rooting is already past it (published root slot %lu)", target_slot, ctx->published_root_slot ));
    4165           0 :       admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_SNAPSHOT_CREATE_RESULT_SLOT_IN_PAST );
    4166           0 :       return;
    4167           0 :     }
    4168             : 
    4169             : 
    4170           0 :     if( FD_UNLIKELY( ctx->snapmk.scheduled_at_slot!=ULONG_MAX &&
    4171           0 :                      ctx->snapmk.scheduled_at_slot!=target_slot ) ) {
    4172           0 :       FD_LOG_WARNING(( "admin requested snapshot creation at slot %lu, but a snapshot is already scheduled at slot %lu. ignoring ...", target_slot, ctx->snapmk.scheduled_at_slot ));
    4173           0 :       admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_SNAPSHOT_CREATE_RESULT_BUSY );
    4174           0 :       return;
    4175           0 :     }
    4176             : 
    4177           0 :     ctx->snapmk.scheduled_at_slot = target_slot;
    4178           0 :     FD_LOG_NOTICE(( "snapshot creation scheduled at slot %lu", target_slot ));
    4179           0 :     admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_ADMINCTL_RESULT_SUCCESS );
    4180           0 :     return;
    4181           0 :   }
    4182             : 
    4183           0 :   if( FD_UNLIKELY( ctx->snapmk.active ) ) {
    4184           0 :     FD_LOG_WARNING(( "admin requested snapshot creation, but currently busy creating another snapshot. ignoring ..." ));
    4185           0 :     admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_SNAPSHOT_CREATE_RESULT_BUSY );
    4186           0 :     return;
    4187           0 :   }
    4188             : 
    4189           0 :   snapmk_start( ctx, stem, 0 );
    4190           0 :   admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_ADMINCTL_RESULT_SUCCESS );
    4191           0 : }
    4192             : 
    4193             : static void
    4194             : msg_admin( fd_replay_tile_t *  ctx,
    4195             :            fd_stem_context_t * stem,
    4196             :            ulong               orig,
    4197           0 :            ulong               sig ) {
    4198           0 :   switch( orig ) {
    4199           0 :   case FD_ADMINCTL_CMD_SNAP_CREATE:
    4200           0 :     admin_snap_create( ctx, stem, sig );
    4201           0 :     break;
    4202           0 :   default:
    4203           0 :     FD_LOG_CRIT(( "unknown admin cmd (orig=%lu, sig=%lu)", orig, sig ));
    4204           0 :   }
    4205           0 : }
    4206             : 
    4207             : static inline int
    4208             : returnable_frag( fd_replay_tile_t *  ctx,
    4209             :                  ulong               in_idx,
    4210             :                  ulong               seq,
    4211             :                  ulong               sig,
    4212             :                  ulong               chunk,
    4213             :                  ulong               sz,
    4214             :                  ulong               ctl,
    4215             :                  ulong               tsorig,
    4216             :                  ulong               tspub,
    4217           0 :                  fd_stem_context_t * stem ) {
    4218           0 :   (void)seq;
    4219           0 :   (void)ctl;
    4220           0 :   (void)tsorig;
    4221           0 :   (void)tspub;
    4222             : 
    4223           0 :   if( FD_UNLIKELY( sz!=0UL && (chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) ) )
    4224           0 :     FD_LOG_CRIT(( "chunk %lu %lu from in %d corrupt, not in range [%lu,%lu]", chunk, sz, ctx->in_kind[ in_idx ], ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
    4225             : 
    4226           0 :   switch( ctx->in_kind[in_idx] ) {
    4227           0 :     case IN_KIND_GENESIS: {
    4228           0 :       fd_genesis_meta_t const * meta = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
    4229           0 :       ctx->has_genesis_hash = 1;
    4230           0 :       ctx->has_genesis_timestamp = 1;
    4231           0 :       ctx->genesis_timestamp = meta->creation_time_seconds;
    4232           0 :       *ctx->genesis_hash = meta->genesis_hash;
    4233           0 :       fd_node_info_write_begin( ctx->node_info );
    4234           0 :       ctx->node_info->info.genesis_hash = *ctx->genesis_hash;
    4235           0 :       fd_node_info_write_end( ctx->node_info );
    4236           0 :       if( FD_LIKELY( meta->bootstrap ) ) {
    4237           0 :         boot_genesis( ctx, stem, meta );
    4238           0 :       } else {
    4239           0 :         uchar const * genesis_blob = (uchar const *)( meta+1 );
    4240           0 :         FD_TEST( fd_genesis_parse( ctx->genesis, genesis_blob, meta->blob_sz ) );
    4241           0 :       }
    4242           0 :       ctx->has_genesis_timestamp = 1;
    4243           0 :       ctx->genesis_timestamp     = ctx->genesis->creation_time;
    4244             : 
    4245           0 :       maybe_verify_cluster_type( ctx );
    4246           0 :       maybe_verify_shred_version( ctx );
    4247           0 :       maybe_verify_genesis_timestamp( ctx );
    4248           0 :       break;
    4249           0 :     }
    4250           0 :     case IN_KIND_IPECHO: {
    4251           0 :       FD_TEST( sig && sig<=USHORT_MAX );
    4252           0 :       ctx->ipecho_shred_version = (ushort)sig;
    4253           0 :       maybe_verify_shred_version( ctx );
    4254           0 :       break;
    4255           0 :     }
    4256           0 :     case IN_KIND_SNAP: {
    4257             :       /* Booting from the snapshot needs the parsed genesis (see
    4258             :          restore_default_slot_params).  genesi_out and snapin_manif are
    4259             :          independent links, so hold DONE until genesis has arrived. */
    4260           0 :       if( FD_UNLIKELY( fd_ssmsg_sig_message( sig )==FD_SSMSG_DONE && !ctx->has_genesis_hash ) ) return 1;
    4261           0 :       on_snapshot_message( ctx, stem, in_idx, chunk, sig );
    4262           0 :       maybe_verify_cluster_type( ctx );
    4263           0 :       maybe_verify_shred_version( ctx );
    4264           0 :       maybe_verify_genesis_timestamp( ctx );
    4265           0 :       break;
    4266           0 :     }
    4267           0 :     case IN_KIND_EXECRP: {
    4268           0 :       process_exec_task_done( ctx, stem, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ), sz, sig );
    4269           0 :       ctx->execrp_idle_cnt = 0UL;
    4270           0 :       break;
    4271           0 :     }
    4272           0 :     case IN_KIND_MOTOR: {
    4273           0 :       process_motor_done( ctx, stem, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
    4274           0 :       break;
    4275           0 :     }
    4276           0 :     case IN_KIND_RESOLV: {
    4277           0 :       fd_resolv_slot_exchanged_t * exchanged_slot = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
    4278           0 :       process_resolv_slot_completed( ctx, exchanged_slot->bank_idx );
    4279           0 :       break;
    4280           0 :     }
    4281           0 :     case IN_KIND_VOTOR: {
    4282           0 :       if( FD_UNLIKELY( sig==FD_VOTOR_SIG_LEADER ) ) {
    4283           0 :         fd_votor_leader_t const * leader = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
    4284           0 :         *ctx->votor_leader    = *leader;
    4285           0 :         ctx->next_leader_slot = leader->slot;
    4286           0 :         try_become_leader( ctx, stem, FD_METRICS_SAMPLE_REASON_LEADER_START, 0UL );
    4287           0 :       } else if( FD_LIKELY( sig==FD_VOTOR_SIG_CERTED ) ) {
    4288           0 :         fd_votor_certed_t const * certed = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
    4289           0 :         fd_votor_certed_t *       fin    = ctx->votor_final;
    4290           0 :         switch( certed->kind ) {
    4291           0 :         case FD_CERT_KIND_FINAL:
    4292           0 :           if( fin->slot==ULONG_MAX || certed->slot>fin->slot ) *fin = *certed;
    4293           0 :           try_advance_root( ctx, fd_block_id( certed->slot, certed->block_id.uc ) );
    4294           0 :           break;
    4295           0 :         case FD_CERT_KIND_FAST_FINAL: /* fast beats slow at the same slot */
    4296           0 :           if( fin->slot==ULONG_MAX || certed->slot>fin->slot || ( certed->slot==fin->slot && fin->kind==FD_CERT_KIND_FINAL ) ) *fin = *certed;
    4297           0 :           try_advance_root( ctx, fd_block_id( certed->slot, certed->block_id.uc ) );
    4298           0 :           break;
    4299           0 :         default: break;
    4300           0 :         }
    4301           0 :       } else if( FD_UNLIKELY( sig==FD_VOTOR_SIG_PARENT_READY ) ) {
    4302           0 :         fd_votor_parent_ready_t const * pr = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
    4303           0 :         ctx->votor_parent_ready_slot = fd_ulong_max( ctx->votor_parent_ready_slot, pr->slot );
    4304           0 :       } else if( FD_UNLIKELY( sig==FD_VOTOR_SIG_QUORUM ) ) {
    4305           0 :         fd_votor_quorum_t const * quorum = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
    4306           0 :         if( FD_UNLIKELY( quorum->kind==FD_VOTOR_QUORUM_KIND_IMPLICITLY_FINALIZED ) ) {
    4307           0 :           fd_block_id_t block_id = fd_block_id( quorum->slot, quorum->block_id.uc );
    4308           0 :           if( FD_UNLIKELY( fd_bhm_map_ele_query( ctx->bhm_map, &block_id, NULL, ctx->bhm_pool ) ) ) {
    4309           0 :             FD_BASE58_ENCODE_32_BYTES( block_id.hash.uc, block_id_b58 );
    4310           0 :             FD_LOG_ERR(( "HARD FORK DETECTED for slot %lu block ID `%s`", block_id.slot, block_id_b58 ));
    4311           0 :           }
    4312           0 :         }
    4313           0 :       } else if( FD_UNLIKELY( sig==FD_VOTOR_SIG_REWARD ) ) {
    4314           0 :         fd_votor_reward_t const * reward = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
    4315           0 :         fd_votor_reward_t *       ring   = &ctx->votor_reward[ reward->slot%(FD_NUM_SLOTS_FOR_REWARD+FD_VOTOR_SLOTS_PER_WINDOW+1UL) ];
    4316           0 :         if( ring->slot==ULONG_MAX || reward->slot>=ring->slot ) *ring = *reward;
    4317           0 :       }
    4318           0 :       break;
    4319           0 :     }
    4320           0 :     case IN_KIND_REPAIR: {
    4321           0 :       if( FD_UNLIKELY( sig!=REPAIR_SIG_FEC ) ) break;
    4322             : 
    4323             :       /* process rotor incoming FECs. 1 to keep frag for retry, returning 0 consumes it. */
    4324             : 
    4325           0 :       fd_repair_out_fec_t * fec = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
    4326           0 :       if( FD_UNLIKELY( !( ctx->execrp_idle_cnt>=2UL*ctx->in_cnt || ctx->is_leader ) ) ) return 1; /* pace behind exec notifications, retry */
    4327             : 
    4328             :       /* Rare case where we replayed a slot completely, but on re-delivery
    4329             :          we discover the FEC is finalized. */
    4330           0 :       if( FD_UNLIKELY( fec->is_finalized && fec->slot_complete ) ) {
    4331           0 :         fd_block_id_t key = fd_block_id( fec->slot, fec->block_id.uc );
    4332           0 :         if( FD_UNLIKELY( fd_bhm_map_ele_query( ctx->bhm_map, &key, NULL, ctx->bhm_pool ) ) ) {
    4333           0 :           FD_BASE58_ENCODE_32_BYTES( key.hash.uc, block_id_b58 );
    4334           0 :           FD_LOG_ERR(( "HARD FORK DETECTED for slot %lu block ID `%s`", key.slot, block_id_b58 ));
    4335           0 :         }
    4336           0 :         fd_block_id_ele_t * ele = live_block_id_ele( ctx, fd_block_id_map_ele_query( ctx->block_id_map, &key, NULL, ctx->block_id_arr ) );
    4337           0 :         if( FD_LIKELY( ele && ele->block_id_seen ) ) ele->finalized = 1;
    4338           0 :       }
    4339             : 
    4340           0 :       int evict_banks = 0;
    4341           0 :       int res = can_process_rotor_fec( ctx, fec, &evict_banks );
    4342             : 
    4343             :       /* drain_rotor_fecs: a bank eviction broke the replayable chain of
    4344             :          FECs delivered from rotor (we lost a parent we needed to
    4345             :          replay off of).  While draining, ignore DROP/SKIP FECs until
    4346             :          one is replayable again (PROCESS_FEC_OK, i.e. its parent
    4347             :          context is present), then resume normal processing.  WAIT must
    4348             :          fall through to the retry/eviction logic below: WAIT's contract
    4349             :          is keep-and-retry, and the redelivered FEC that rebuilds the
    4350             :          evicted block can itself return WAIT+evict_banks (banks full) --
    4351             :          swallowing it would consume the frag, never queue the eviction,
    4352             :          and wedge recovery. */
    4353           0 :       if( FD_UNLIKELY( ctx->drain_rotor_fecs ) ) {
    4354           0 :         if( FD_LIKELY( res==PROCESS_FEC_OK ) ) {
    4355           0 :           ctx->drain_rotor_fecs = 0; /* chain re-established, resume */
    4356           0 :         }
    4357           0 :         else if( res!=PROCESS_FEC_WAIT ) return 0; /* swallow DROPs/SKIPs, do evict_banks if needed */
    4358           0 :       }
    4359             : 
    4360           0 :       switch( res ) {
    4361           0 :         case PROCESS_FEC_OK: {
    4362           0 :           process_rotor_fec( ctx, stem, fec );
    4363           0 :           return 0;
    4364           0 :         }
    4365           0 :         case PROCESS_FEC_DROP: {
    4366             :           /* enter drain state */
    4367           0 :           ctx->drain_rotor_fecs = 1;
    4368           0 :           publish_replay_out( ctx, stem, REPLAY_SIG_MISSING_FEC, 0 );
    4369           0 :           return 0;
    4370           0 :         }
    4371           0 :         case PROCESS_FEC_SKIP: { return 0; }
    4372           0 :         case PROCESS_FEC_WAIT: {
    4373             :           /* queue an eviction, then retry the frag. */
    4374           0 :           if( FD_UNLIKELY( evict_banks ) ) {
    4375           0 :             ulong evictable_bank_idx = fd_banks_get_evictable_bank( ctx->banks, ctx->notified_root_bank );
    4376           0 :             if( FD_UNLIKELY( evictable_bank_idx==ULONG_MAX ) ) {
    4377           0 :               FD_LOG_DEBUG(( "replay has no banks to mark as prunable, it's possible that there is one bank already marked as prunable" ));
    4378           0 :               return 1;
    4379           0 :             }
    4380             : 
    4381           0 :             FD_LOG_WARNING(( "banks full, evicting bank (idx=%lu)", evictable_bank_idx ));
    4382             : 
    4383           0 :             timing_slot_release( ctx, evictable_bank_idx );
    4384             : 
    4385           0 :             if( FD_UNLIKELY( fd_sched_block_is_discarded( ctx->sched, evictable_bank_idx ) ) ) {
    4386           0 :               fd_block_id_ele_t * ele = &ctx->block_id_arr[ evictable_bank_idx ];
    4387           0 :               report_block_incomplete( ctx, ele->slot, ele->block_id_seen ? &ele->dmr : &ele->latest_mr, fd_banks_bank_query( ctx->banks, evictable_bank_idx ),
    4388           0 :                                        FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED );
    4389           0 :             }
    4390             : 
    4391             :             /* Send a notification to other tiles to drop a reference to the
    4392             :                evictable bank.  The RPC tile is the only tile which holds onto
    4393             :                non-rooted banks, non-transiently. */
    4394           0 :             fd_replay_drop_bank_ref_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
    4395           0 :             fd_sched_block_abandon( ctx->sched, evictable_bank_idx, FD_SCHED_ABANDON_DISCARDED );
    4396           0 :             msg->bank_idx = evictable_bank_idx;
    4397           0 :             publish_replay_out( ctx, stem, REPLAY_SIG_DROP_BANK_REF, sizeof(fd_replay_drop_bank_ref_t) );
    4398           0 :           }
    4399           0 :           return 1;
    4400           0 :         }
    4401           0 :         default:
    4402           0 :           FD_LOG_CRIT(( "unhandled process_rotor_fec result: %d", res ));
    4403           0 :       }
    4404           0 :       break;
    4405           0 :     }
    4406           0 :     case IN_KIND_TXSEND: {
    4407           0 :       process_vote_txn_sent( ctx, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
    4408           0 :       break;
    4409           0 :     }
    4410           0 :     case IN_KIND_GOSSIP_OUT: {
    4411           0 :       FD_TEST( sig==FD_GOSSIP_UPDATE_TAG_WFS_DONE );
    4412           0 :       if( FD_UNLIKELY( ctx->wfs_complete ) ) break;
    4413           0 :       ctx->wfs_complete = 1;
    4414           0 :       maybe_verify_shred_version( ctx );
    4415             : 
    4416           0 :       FD_LOG_NOTICE(( "Done waiting for supermajority. More than 80 percent of cluster stake has joined." ));
    4417             : 
    4418           0 :       if( FD_LIKELY( ctx->replay_out->idx!=ULONG_MAX ) ) {
    4419           0 :         publish_replay_out( ctx, stem, REPLAY_SIG_WFS_DONE, 0UL );
    4420           0 :       }
    4421           0 :       break;
    4422           0 :     }
    4423           0 :     case IN_KIND_RPC: {
    4424           0 :       fd_bank_t * bank = fd_banks_bank_query( ctx->banks, sig );
    4425           0 :       FD_TEST( bank );
    4426           0 :       bank->refcnt--;
    4427           0 :       FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt decremented to %lu for %s", bank->idx, bank->f.slot, bank->refcnt, ctx->in_kind[ in_idx ]==IN_KIND_RPC ? "rpc" : "gui" ));
    4428           0 :       break;
    4429           0 :     }
    4430           0 :     case IN_KIND_SNAPMK:
    4431           0 :       msg_snapmk( ctx, stem, sig, in_idx, chunk );
    4432           0 :       break;
    4433           0 :     case IN_KIND_ADMIN:
    4434           0 :       msg_admin( ctx, stem, fd_frag_meta_ctl_orig( ctl ), sig );
    4435           0 :       break;
    4436           0 :     default:
    4437           0 :       FD_LOG_ERR(( "unhandled kind %d", ctx->in_kind[ in_idx ] ));
    4438           0 :   }
    4439             : 
    4440           0 :   return 0;
    4441           0 : }
    4442             : 
    4443             : #undef PROCESS_FEC_DROP
    4444             : #undef PROCESS_FEC_WAIT
    4445             : #undef PROCESS_FEC_SKIP
    4446             : #undef PROCESS_FEC_OK
    4447             : 
    4448             : static inline fd_replay_out_link_t
    4449             : out1( fd_topo_t const *      topo,
    4450             :       fd_topo_tile_t const * tile,
    4451           0 :       char const *           name ) {
    4452           0 :   ulong idx = ULONG_MAX;
    4453             : 
    4454           0 :   for( ulong i=0UL; i<tile->out_cnt; i++ ) {
    4455           0 :     fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ];
    4456           0 :     if( !strcmp( link->name, name ) ) {
    4457           0 :       if( FD_UNLIKELY( idx!=ULONG_MAX ) ) FD_LOG_ERR(( "tile %s:%lu had multiple output links named %s but expected one", tile->name, tile->kind_id, name ));
    4458           0 :       idx = i;
    4459           0 :     }
    4460           0 :   }
    4461             : 
    4462           0 :   if( FD_UNLIKELY( idx==ULONG_MAX ) ) return (fd_replay_out_link_t){ .idx = ULONG_MAX, .mem = NULL, .chunk0 = 0, .wmark = 0, .chunk = 0 };
    4463             : 
    4464           0 :   void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp;
    4465           0 :   ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache );
    4466           0 :   ulong wmark  = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, topo->links[ tile->out_link_id[ idx ] ].mtu );
    4467             : 
    4468           0 :   return (fd_replay_out_link_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0 };
    4469           0 : }
    4470             : 
    4471             : static void
    4472             : privileged_init( fd_topo_t const *      topo,
    4473           0 :                  fd_topo_tile_t const * tile ) {
    4474           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    4475             : 
    4476           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    4477           0 :   fd_replay_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_tile_t), sizeof(fd_replay_tile_t) );
    4478             : 
    4479           0 :   if( FD_UNLIKELY( !strcmp( tile->replay.identity_key_path, "" ) ) ) FD_LOG_ERR(( "identity_key_path not set" ));
    4480             : 
    4481           0 :   ctx->identity_pubkey[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->replay.identity_key_path, /* pubkey only: */ 1 ) );
    4482           0 :   ctx->identity_idx         = 0UL;
    4483           0 :   ctx->voter_rank_cached    = 0;
    4484           0 :   ctx->identity_dirty       = 0;
    4485             : 
    4486           0 :   ctx->vote_account_staked       = 0;
    4487           0 :   ctx->vote_account_inadmissible = 0;
    4488             : 
    4489           0 :   ctx->metrics.voted_slot = ULONG_MAX;
    4490             : 
    4491           0 :   ctx->has_vote_account = !!tile->replay.vote_account_path[ 0 ];
    4492           0 :   if( FD_LIKELY( ctx->has_vote_account ) ) {
    4493           0 :     if( FD_UNLIKELY( !fd_base58_decode_32( tile->replay.vote_account_path, ctx->vote_account->uc ) ) ) {
    4494           0 :       uchar const * vote_key = fd_keyload_load( tile->replay.vote_account_path, /* pubkey only: */ 1 );
    4495           0 :       fd_memcpy( ctx->vote_account->uc, vote_key, sizeof(fd_pubkey_t) );
    4496           0 :     }
    4497           0 :   }
    4498             : 
    4499           0 :   ctx->bundle.enabled = tile->replay.bundle.enabled;
    4500           0 :   if( FD_UNLIKELY( !tile->replay.bundle.vote_account_path[0] ) ) {
    4501           0 :     ctx->bundle.enabled = 0;
    4502           0 :   }
    4503             : 
    4504           0 :   if( FD_UNLIKELY( ctx->bundle.enabled ) ) {
    4505           0 :     if( FD_UNLIKELY( !fd_base58_decode_32( tile->replay.bundle.vote_account_path, ctx->bundle.vote_account.uc ) ) ) {
    4506           0 :       const uchar * vote_key = fd_keyload_load( tile->replay.bundle.vote_account_path, /* pubkey only: */ 1 );
    4507           0 :       fd_memcpy( ctx->bundle.vote_account.uc, vote_key, 32UL );
    4508           0 :     }
    4509           0 :   }
    4510             : 
    4511           0 :   FD_TEST( fd_rng_secure( &ctx->rng_seed,             sizeof(ctx->rng_seed) ) );
    4512           0 :   FD_TEST( fd_rng_secure( &ctx->blockhash_seed,       sizeof(ulong) )         );
    4513           0 :   FD_TEST( fd_rng_secure( &ctx->vote_tracker_seed,    sizeof(ulong) )         );
    4514           0 :   FD_TEST( fd_rng_secure( &ctx->block_id_map_seed,    sizeof(ulong) )         );
    4515           0 :   FD_TEST( fd_rng_secure( &ctx->bhm_map_seed,         sizeof(ulong) )         );
    4516           0 :   FD_TEST( fd_rng_secure( &ctx->initial_block_id,     sizeof(fd_hash_t) )     );
    4517           0 :   FD_TEST( fd_rng_secure( &ctx->runtime_stack_seed,   sizeof(ulong) )         );
    4518             : 
    4519           0 :   ctx->store_disk_fd = -1;
    4520           0 :   ulong store_obj_id = fd_pod_query_ulong( topo->props, "store", ULONG_MAX );
    4521           0 :   if( FD_LIKELY( store_obj_id!=ULONG_MAX ) ) {
    4522           0 :     fd_store_t * store = fd_store_join( fd_topo_obj_laddr( topo, store_obj_id ) );
    4523           0 :     FD_TEST( store && store->magic==FD_STORE_MAGIC );
    4524           0 :     ctx->store_disk_fd = FD_STORE_FD_RO;
    4525           0 :     if( FD_UNLIKELY( fcntl( ctx->store_disk_fd, F_GETFD )<0 ) )
    4526           0 :       FD_LOG_ERR(( "store file descriptor was not inherited (%i-%s)", errno, fd_io_strerror( errno ) ));
    4527           0 :   }
    4528           0 : }
    4529             : 
    4530             : static FD_TL fd_event_reporter_t replay_diff_reporter[1];
    4531             : 
    4532             : static void
    4533             : unprivileged_init( fd_topo_t const *      topo,
    4534           0 :                    fd_topo_tile_t const * tile ) {
    4535           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    4536           0 :   void * block_id_map_mem = NULL;
    4537           0 :   ulong chain_cnt  = fd_block_id_map_chain_cnt_est( tile->replay.max_live_slots );
    4538             : 
    4539           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    4540           0 :   fd_replay_tile_t * ctx    = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_tile_t),   sizeof(fd_replay_tile_t) );
    4541           0 :   void * genesis_mem        = FD_SCRATCH_ALLOC_APPEND( l, fd_genesis_align(),          fd_genesis_footprint( fd_genesis_account_max( tile->replay.genesis_max_message_size ) ) );
    4542           0 :   void * runtime_stack_mem  = FD_SCRATCH_ALLOC_APPEND( l, fd_runtime_stack_align(),    fd_runtime_stack_footprint( FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS, FD_RUNTIME_MAX_STAKED_VOTE_ACCOUNTS, FD_RUNTIME_MAX_STAKE_ACCOUNTS ) );
    4543           0 :   void * block_id_arr_mem   = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_block_id_ele_t),  sizeof(fd_block_id_ele_t) * tile->replay.max_live_slots );
    4544           0 :   void * dead_idxs_mem      = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong),              sizeof(ulong) * tile->replay.max_live_slots );
    4545           0 :   void * fec_chain_mem      = tile->replay.report_runtime_diffs ?
    4546           0 :                               FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_hash_t),          sizeof(fd_hash_t) * FD_FEC_BLK_MAX * tile->replay.max_live_slots ) : NULL;
    4547           0 :   void * slot_diffs_mem     = tile->replay.report_runtime_diffs ?
    4548           0 :                               FD_SCRATCH_ALLOC_APPEND( l, 8UL,                         FD_EVENT_RUNTIME_SLOT_DIFFS_FOOTPRINT * tile->replay.max_live_slots ) : NULL;
    4549           0 :     block_id_map_mem        = FD_SCRATCH_ALLOC_APPEND( l, fd_block_id_map_align(),     fd_block_id_map_footprint( chain_cnt ) );
    4550           0 :   void * bhm_map_mem        = FD_SCRATCH_ALLOC_APPEND( l, fd_bhm_map_align(),          fd_bhm_map_footprint( chain_cnt ) );
    4551           0 :   void * bhm_pool_mem       = FD_SCRATCH_ALLOC_APPEND( l, fd_bhm_pool_align(),         fd_bhm_pool_footprint( tile->replay.max_live_slots ) );
    4552           0 :   void * bhm_dlist_mem      = FD_SCRATCH_ALLOC_APPEND( l, fd_bhm_dlist_align(),        fd_bhm_dlist_footprint() );
    4553             : 
    4554           0 :   void * _txncache          = FD_SCRATCH_ALLOC_APPEND( l, fd_txncache_align(),                 fd_txncache_footprint( tile->replay.max_live_slots ) );
    4555           0 :   void * _accdb             = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(),                    fd_accdb_footprint( tile->replay.max_live_slots, 0 ) );
    4556           0 :   void * recp_stats_mem     = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_reception_stats_t),       sizeof(fd_reception_stats_t)*tile->replay.max_live_slots );
    4557           0 :   void * sched_mem          = FD_SCRATCH_ALLOC_APPEND( l, fd_sched_align(),                    fd_sched_footprint( tile->replay.sched_depth, tile->replay.max_live_slots, tile->replay.max_shreds_per_block, tile->replay.max_txn_per_slot ) );
    4558           0 :   void * lthash_rec_keys    = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_pubkey_t),                FD_SCHED_LTHASH_START_WRITES_MAX*sizeof(fd_pubkey_t) );
    4559           0 :   void * vote_tracker_mem   = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_tracker_align(),             fd_vote_tracker_footprint() );
    4560           0 :   void * _capture_ctx       = FD_SCRATCH_ALLOC_APPEND( l, fd_capture_ctx_align(),              fd_capture_ctx_footprint() );
    4561           0 :   void * dump_proto_ctx_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_dump_proto_ctx_t),        sizeof(fd_dump_proto_ctx_t) );
    4562           0 :   void * block_completed_ev = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_event_block_completed_t), sizeof(fd_event_block_completed_t) );
    4563           0 :   void * timing_pool_mem    = FD_SCRATCH_ALLOC_APPEND( l, fd_timing_slot_pool_align(),         fd_timing_slot_pool_footprint( FD_REPLAY_TXN_TIMING_SLOTS ) );
    4564           0 :   void * timing_rec_mem     = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_txn_timing_t),     FD_REPLAY_TXN_TIMING_SLOTS*tile->replay.max_txn_per_slot*sizeof(fd_replay_txn_timing_t) );
    4565           0 :   void * timing_of_bank_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong),                      tile->replay.max_live_slots*sizeof(ulong) );
    4566           0 :   void * footer_end_mem     = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_footer_end_t),     tile->replay.max_live_slots*sizeof(fd_replay_footer_end_t) );
    4567           0 :   void * block_dump_ctx     = NULL;
    4568           0 :   if( FD_UNLIKELY( tile->replay.dump_block_to_pb ) ) {
    4569           0 :     block_dump_ctx = FD_SCRATCH_ALLOC_APPEND( l, fd_block_dump_context_align(), fd_block_dump_context_footprint() );
    4570           0 :   }
    4571             : 
    4572           0 :   ctx->genesis = fd_genesis_new( genesis_mem, fd_genesis_account_max( tile->replay.genesis_max_message_size ) );
    4573           0 :   FD_TEST( ctx->genesis );
    4574             : 
    4575           0 :   ctx->runtime_stack = fd_runtime_stack_join( fd_runtime_stack_new( runtime_stack_mem, FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS, FD_RUNTIME_MAX_STAKED_VOTE_ACCOUNTS, FD_RUNTIME_MAX_STAKE_ACCOUNTS, ctx->runtime_stack_seed ) );
    4576           0 :   FD_TEST( ctx->runtime_stack );
    4577             : 
    4578           0 :   ctx->wksp = topo->workspaces[ topo->objs[ tile->tile_obj_id ].wksp_id ].wksp;
    4579             : 
    4580           0 :   ulong store_obj_id = fd_pod_query_ulong( topo->props, "store", ULONG_MAX );
    4581           0 :   FD_TEST( store_obj_id!=ULONG_MAX );
    4582           0 :   ctx->store = fd_store_join( fd_topo_obj_laddr( topo, store_obj_id ) );
    4583           0 :   FD_TEST( ctx->store );
    4584           0 :   FD_TEST( fd_store_map_ljoin( ctx->store, ctx->map_join ) );
    4585             : 
    4586           0 :   ulong banks_obj_id = fd_pod_query_ulong( topo->props, "banks", ULONG_MAX );
    4587           0 :   FD_TEST( banks_obj_id!=ULONG_MAX );
    4588             : 
    4589           0 :   ctx->banks = fd_banks_join( fd_topo_obj_laddr( topo, banks_obj_id ) );
    4590           0 :   FD_TEST( ctx->banks );
    4591             : 
    4592           0 :   ulong footer_cache_obj_id = fd_pod_query_ulong( topo->props, "footer_cache", ULONG_MAX );
    4593           0 :   ctx->footer_cache = NULL;
    4594           0 :   if( footer_cache_obj_id!=ULONG_MAX ) {
    4595           0 :     ctx->footer_cache = fd_footer_cache_join( fd_topo_obj_laddr( topo, footer_cache_obj_id ) );
    4596           0 :     FD_TEST( ctx->footer_cache );
    4597           0 :   }
    4598             : 
    4599           0 :   ctx->leader_txn_timing = NULL;
    4600           0 :   ulong ldr_tt_obj_id = fd_pod_query_ulong( topo->props, "ldr_tt", ULONG_MAX );
    4601           0 :   if( FD_LIKELY( ldr_tt_obj_id!=ULONG_MAX ) ) ctx->leader_txn_timing = fd_topo_obj_laddr( topo, ldr_tt_obj_id );
    4602             : 
    4603           0 :   ulong node_info_obj_id = fd_pod_query_ulong( topo->props, "node_info", ULONG_MAX );
    4604           0 :   FD_TEST( node_info_obj_id!=ULONG_MAX );
    4605           0 :   ctx->node_info = fd_node_info_box_join( fd_topo_obj_laddr( topo, node_info_obj_id ) );
    4606           0 :   FD_TEST( ctx->node_info );
    4607           0 :   fd_node_info_write_begin( ctx->node_info );
    4608           0 :   ctx->node_info->info.identity = *ctx->identity_pubkey;
    4609           0 :   if( FD_LIKELY( ctx->has_vote_account ) ) ctx->node_info->info.vote_account = *ctx->vote_account;
    4610           0 :   fd_node_info_write_end( ctx->node_info );
    4611             : 
    4612           0 :   ulong wait_info_obj_id = fd_pod_query_ulong( topo->props, "wait_info", ULONG_MAX );
    4613           0 :   if( FD_LIKELY( wait_info_obj_id!=ULONG_MAX ) ) {
    4614           0 :     ctx->wait_info = fd_wait_info_box_join( fd_topo_obj_laddr( topo, wait_info_obj_id ) );
    4615           0 :     FD_TEST( ctx->wait_info );
    4616           0 :   } else {
    4617           0 :     ctx->wait_info = NULL;
    4618           0 :   }
    4619             : 
    4620           0 :   FD_MGAUGE_SET( REPLAY, BANK_CAPACITY, fd_banks_pool_max_cnt( ctx->banks ) );
    4621             : 
    4622           0 :   ctx->delinquent_known              = 0;
    4623           0 :   ctx->delinquent_sample_slot        = 0UL;
    4624           0 :   ctx->delinquent_stake_lamports     = 0UL;
    4625           0 :   ctx->cluster_active_stake_lamports = 0UL;
    4626           0 :   ctx->ag_last_voted_epoch = ULONG_MAX;
    4627           0 :   ctx->ag_last_settled     = 0UL;
    4628           0 :   ctx->ag_stamp_since      = ULONG_MAX;
    4629           0 :   ctx->consensus_root_slot = ULONG_MAX;
    4630           0 :   ctx->consensus_root      = ctx->initial_block_id;
    4631           0 :   ctx->finalized_block_id_lo = fd_block_id( ULONG_MAX, ctx->initial_block_id.uc );
    4632           0 :   ctx->finalized_block_id_hi = ctx->finalized_block_id_lo;
    4633           0 :   ctx->notified_root_slot  = ULONG_MAX;
    4634           0 :   ctx->notified_root       = ctx->initial_block_id;
    4635           0 :   ctx->notified_root_bank = NULL;
    4636           0 :   ctx->published_root_slot = ULONG_MAX;
    4637             : 
    4638           0 :   ctx->expected_shred_version = tile->replay.expected_shred_version;
    4639           0 :   ctx->ipecho_shred_version = 0;
    4640           0 :   ctx->shred_version        = 0;
    4641           0 :   fd_memcpy( ctx->genesis_path, tile->replay.genesis_path, sizeof(ctx->genesis_path) );
    4642           0 :   ctx->has_genesis_hash = 0;
    4643           0 :   ctx->has_cluster_type = 0;
    4644           0 :   ctx->has_genesis_timestamp          = 0;
    4645           0 :   ctx->has_expected_genesis_timestamp = 0;
    4646           0 :   ctx->cluster_type = FD_CLUSTER_UNKNOWN;
    4647           0 :   ctx->hard_fork_cnt = ULONG_MAX;
    4648           0 :   ctx->has_manifest_block_id = 0;
    4649             : 
    4650           0 :   if( FD_UNLIKELY( ctx->bundle.enabled ) ) {
    4651           0 :     if( FD_UNLIKELY( !fd_bundle_crank_gen_init( ctx->bundle.gen,
    4652           0 :              (fd_acct_addr_t const *)tile->replay.bundle.tip_distribution_program_addr,
    4653           0 :              (fd_acct_addr_t const *)tile->replay.bundle.tip_payment_program_addr,
    4654           0 :              (fd_acct_addr_t const *)ctx->bundle.vote_account.uc,
    4655           0 :              (fd_acct_addr_t const *)ctx->bundle.vote_account.uc, "NAN", 0UL ) ) ) {
    4656           0 :       FD_LOG_ERR(( "failed to initialize bundle crank gen" ));
    4657           0 :     }
    4658           0 :   }
    4659             : 
    4660           0 :   FD_TEST( tile->replay.enable_features_cnt<=sizeof(ctx->enable_features)/sizeof(ctx->enable_features[0]) );
    4661           0 :   ctx->enable_features_cnt = tile->replay.enable_features_cnt;
    4662           0 :   for( ulong i=0UL; i<tile->replay.enable_features_cnt; i++ ) {
    4663           0 :     fd_memcpy( ctx->enable_features[ i ], tile->replay.enable_features[ i ], FD_BASE58_ENCODED_32_SZ );
    4664           0 :   }
    4665             : 
    4666           0 :   ulong progcache_obj_id; FD_TEST( (progcache_obj_id = fd_pod_query_ulong( topo->props, "progcache", ULONG_MAX ) )!=ULONG_MAX );
    4667           0 :   FD_TEST( fd_progcache_shmem_join( ctx->progcache, fd_topo_obj_laddr( topo, progcache_obj_id       ) ) );
    4668             : 
    4669           0 :   void * _txncache_shmem = fd_topo_obj_laddr( topo, tile->replay.txncache_obj_id );
    4670           0 :   fd_txncache_shmem_t * txncache_shmem = fd_txncache_shmem_join( _txncache_shmem );
    4671           0 :   FD_TEST( txncache_shmem );
    4672           0 :   ctx->txncache = fd_txncache_join( fd_txncache_new( _txncache, txncache_shmem, FD_TXNCACHE_FD ) );
    4673           0 :   FD_TEST( ctx->txncache );
    4674             : 
    4675           0 :   void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->replay.accdb_obj_id );
    4676           0 :   fd_accdb_shmem_t * accdb_shmem = fd_accdb_shmem_join( _accdb_shmem );
    4677           0 :   FD_TEST( accdb_shmem );
    4678           0 :   fd_sleep_t * accdb_sleep = topo->sleep_obj_id!=ULONG_MAX ? fd_sleep_join( fd_topo_obj_laddr( topo, topo->sleep_obj_id ) ) : NULL;
    4679           0 :   ctx->accdb = fd_accdb_join( fd_accdb_new( _accdb, accdb_shmem, FD_ACCDB_FD_RW, 0UL, NULL, accdb_sleep, fd_topo_find_tile( topo, "accdb", 0UL ), 0 ) );
    4680           0 :   FD_TEST( ctx->accdb );
    4681             : 
    4682           0 :   ctx->capture_ctx = NULL;
    4683           0 :   if( FD_UNLIKELY( strcmp( "", tile->replay.solcap_capture ) ) ) {
    4684           0 :     ctx->capture_ctx = fd_capture_ctx_join( fd_capture_ctx_new( _capture_ctx ) );
    4685           0 :     ctx->capture_ctx->solcap_start_slot = tile->replay.capture_start_slot;
    4686           0 :     ctx->capture_ctx->capture_solcap = 1;
    4687           0 :   }
    4688             : 
    4689           0 :   ctx->block_completed_event = block_completed_ev;
    4690             : 
    4691           0 :   ctx->timing_slot_pool = fd_timing_slot_pool_join( fd_timing_slot_pool_new( timing_pool_mem, FD_REPLAY_TXN_TIMING_SLOTS ) );
    4692           0 :   FD_TEST( ctx->timing_slot_pool );
    4693           0 :   ctx->timing_rec          = timing_rec_mem;
    4694           0 :   ctx->timing_slot_of_bank = timing_of_bank_mem;
    4695           0 :   for( ulong i=0UL; i<tile->replay.max_live_slots; i++ ) ctx->timing_slot_of_bank[ i ] = fd_timing_slot_pool_idx_null( ctx->timing_slot_pool );
    4696             : 
    4697           0 :   ctx->dump_proto_ctx = NULL;
    4698           0 :   if( FD_UNLIKELY( strcmp( "", tile->replay.dump_proto_dir ) ) ) {
    4699           0 :     ctx->dump_proto_ctx                        = dump_proto_ctx_mem;
    4700           0 :     ctx->dump_proto_ctx->dump_proto_output_dir = tile->replay.dump_proto_dir;
    4701           0 :     if( FD_LIKELY( tile->replay.dump_block_to_pb ) ) {
    4702           0 :       ctx->dump_proto_ctx->dump_block_to_pb = !!tile->replay.dump_block_to_pb;
    4703           0 :     }
    4704           0 :   }
    4705             : 
    4706           0 :   if( FD_UNLIKELY( tile->replay.dump_block_to_pb ) ) {
    4707           0 :     ctx->block_dump_ctx = fd_block_dump_context_join( fd_block_dump_context_new( block_dump_ctx ) );
    4708           0 :   } else {
    4709           0 :     ctx->block_dump_ctx = NULL;
    4710           0 :   }
    4711             : 
    4712           0 :   ctx->is_booted = 0;
    4713             : 
    4714           0 :   fd_clock_tile_init( ctx->clock );
    4715             : 
    4716           0 :   ctx->max_txn_per_slot     = tile->replay.max_txn_per_slot;
    4717           0 :   ctx->max_shreds_per_block = tile->replay.max_shreds_per_block;
    4718             : 
    4719           0 :   FD_TEST( fd_rng_new( ctx->rng, ctx->rng_seed, 0UL ) );
    4720             : 
    4721           0 :   ctx->reception_stats     = recp_stats_mem;
    4722           0 :   ctx->reception_stats_cnt = tile->replay.max_live_slots;
    4723           0 :   FD_TEST( ctx->reception_stats_cnt );
    4724           0 :   memset( ctx->reception_stats, 0, sizeof(fd_reception_stats_t)*ctx->reception_stats_cnt );
    4725             : 
    4726           0 :   ctx->leader_stats.slot = ULONG_MAX;
    4727           0 :   ctx->sched = fd_sched_join( fd_sched_new( sched_mem, ctx->rng, tile->replay.sched_depth, tile->replay.max_live_slots, ctx->max_shreds_per_block, ctx->max_txn_per_slot, fd_topo_tile_name_cnt( topo, "execrp" ) ) );
    4728           0 :   FD_TEST( ctx->sched );
    4729             : 
    4730           0 :   ctx->lthash_rec->cnt  = 0UL;
    4731           0 :   ctx->lthash_rec->max  = FD_SCHED_LTHASH_START_WRITES_MAX;
    4732           0 :   ctx->lthash_rec->keys = lthash_rec_keys;
    4733           0 :   ulong execrp_result_obj_id = fd_pod_query_ulong( topo->props, "execrp_result", ULONG_MAX );
    4734           0 :   FD_TEST( execrp_result_obj_id!=ULONG_MAX );
    4735           0 :   ctx->execrp_result = fd_topo_obj_laddr( topo, execrp_result_obj_id );
    4736             : 
    4737           0 :   ctx->in_cnt          = tile->in_cnt;
    4738           0 :   ctx->execrp_idle_cnt = 0UL;
    4739             : 
    4740             : 
    4741           0 :   ctx->footer_end            = footer_end_mem;
    4742           0 :   ctx->rewards_in_flight_cnt = 0UL;
    4743             : 
    4744           0 :   ctx->vote_tracker = fd_vote_tracker_join( fd_vote_tracker_new( vote_tracker_mem, ctx->vote_tracker_seed ) );
    4745           0 :   FD_TEST( ctx->vote_tracker );
    4746             : 
    4747           0 :   ctx->identity_vote_rooted = 0;
    4748             : 
    4749           0 :   ctx->wait_for_vote_to_start_leader = tile->replay.wait_for_vote_to_start_leader;
    4750             : 
    4751           0 :   ctx->wfs_hash_is_zero = !memcmp( tile->replay.wait_for_supermajority_with_bank_hash.uc, ((fd_hash_t){0}).uc, sizeof(fd_hash_t) );
    4752           0 :   ctx->wfs_enabled = fd_wfs_configured( tile->replay.wait_for_supermajority_at_slot,
    4753           0 :                                         ctx->wfs_hash_is_zero,
    4754           0 :                                         (ulong)tile->replay.expected_shred_version );
    4755           0 :   ctx->wait_for_supermajority_at_slot = tile->replay.wait_for_supermajority_at_slot;
    4756           0 :   ctx->expected_bank_hash = tile->replay.wait_for_supermajority_with_bank_hash;
    4757           0 :   ctx->wfs_complete = !ctx->wfs_enabled;
    4758             : 
    4759             :   /* With WFS, auto is resolved at boot instead (on_snapshot_message). */
    4760           0 :   if( FD_UNLIKELY( !ctx->wfs_enabled ) ) {
    4761           0 :     ctx->wait_for_vote_to_start_leader = wfs_resolve_leader_gate_auto( ctx->wait_for_vote_to_start_leader );
    4762           0 :   }
    4763             : 
    4764           0 :   ctx->mleaders = fd_multi_epoch_leaders_join( fd_multi_epoch_leaders_new( ctx->mleaders_mem ) );
    4765           0 :   FD_TEST( ctx->mleaders );
    4766           0 :   ctx->next_leader_query_start = ULONG_MAX;
    4767             : 
    4768           0 :   ctx->is_leader             = 0;
    4769           0 :   ctx->drain_rotor_fecs      = 0;
    4770           0 :   ctx->supports_leader       = fd_topo_find_tile( topo, "pack", 0UL )!=ULONG_MAX;
    4771           0 :   ctx->snapmk.active                        = 0;
    4772           0 :   ctx->snapmk.supported                     = fd_topo_find_tile( topo, "snapmk", 0UL )!=ULONG_MAX;
    4773           0 :   ctx->snapmk.scheduled_at_slot             = ULONG_MAX;
    4774           0 :   ctx->snapmk.full_interval_blocks          = tile->replay.full_snapshot_interval_blocks;
    4775           0 :   ctx->snapmk.next_full_block_height        = ULONG_MAX;
    4776           0 :   ctx->snapmk.incremental_interval_blocks   = tile->replay.incremental_snapshot_interval_blocks;
    4777           0 :   ctx->snapmk.next_incremental_block_height = ULONG_MAX;
    4778           0 :   ctx->snapmk.base_slot                     = ULONG_MAX;
    4779           0 :   ctx->snapmk.snap_finished_full            = 0UL;
    4780           0 :   ctx->snapmk.snap_finished_incr            = 0UL;
    4781           0 :   ctx->snapmk.snap_produced_incr_cnt        = 0UL;
    4782           0 :   if( FD_UNLIKELY( !ctx->snapmk.supported ) ) {
    4783           0 :     ctx->snapmk.full_interval_blocks        = 0UL;
    4784           0 :     ctx->snapmk.incremental_interval_blocks = 0UL;
    4785           0 :   }
    4786           0 :   ctx->reset_slot            = 0UL;
    4787           0 :   ctx->epoch_end_slot        = 0UL;
    4788           0 :   ctx->slots_per_epoch       = 0UL;
    4789           0 :   ctx->ns_per_slot           = 0UL;
    4790           0 :   ctx->next_leader_slot      = ULONG_MAX;
    4791           0 :   ctx->highwater_leader_slot = ULONG_MAX;
    4792             : 
    4793           0 :   ctx->votor_final->slot       = ULONG_MAX;
    4794           0 :   ctx->votor_parent_ready_slot = 0UL;
    4795           0 :   for( ulong i=0UL; i<FD_NUM_SLOTS_FOR_REWARD+FD_VOTOR_SLOTS_PER_WINDOW+1UL; i++ ) ctx->votor_reward[ i ].slot = ULONG_MAX;
    4796             : 
    4797           0 :   ctx->caught_up                = 0;
    4798           0 :   ctx->catch_up_max_fec_slot    = ULONG_MAX;
    4799           0 :   ctx->catch_up_tip_advance_cnt = 0UL;
    4800           0 :   ctx->boot_timestamp_nanos     = tile->replay.boot_timestamp_nanos;
    4801           0 :   ctx->leader_bank = NULL;
    4802             : 
    4803           0 :   ctx->block_id_len   = tile->replay.max_live_slots;
    4804           0 :   ctx->max_live_slots = tile->replay.max_live_slots;
    4805           0 :   ctx->block_id_arr = (fd_block_id_ele_t *)block_id_arr_mem;
    4806           0 :   ctx->dead_idxs    = (ulong *)dead_idxs_mem;
    4807             : 
    4808           0 :   ctx->fec_chain    = (fd_hash_t *)fec_chain_mem;
    4809           0 :   if( FD_UNLIKELY( slot_diffs_mem ) ) fd_event_runtime_slot_diffs_init( slot_diffs_mem, tile->replay.max_live_slots );
    4810             : 
    4811           0 :   ctx->block_id_map = fd_block_id_map_join( fd_block_id_map_new( block_id_map_mem, chain_cnt, ctx->block_id_map_seed ) );
    4812           0 :   FD_TEST( ctx->block_id_map );
    4813           0 :   ctx->bhm_map  = fd_bhm_map_join( fd_bhm_map_new( bhm_map_mem, chain_cnt, ctx->bhm_map_seed ) );
    4814           0 :   FD_TEST( ctx->bhm_map );
    4815           0 :   ctx->bhm_pool = fd_bhm_pool_join( fd_bhm_pool_new( bhm_pool_mem, tile->replay.max_live_slots ) );
    4816           0 :   FD_TEST( ctx->bhm_pool );
    4817           0 :   ctx->bhm_dlist = fd_bhm_dlist_join( fd_bhm_dlist_new( bhm_dlist_mem ) );
    4818           0 :   FD_TEST( ctx->bhm_dlist );
    4819             : 
    4820             : 
    4821           0 :   for( ulong i=0UL; i<tile->replay.max_live_slots; i++ ) {
    4822           0 :     ctx->block_id_arr[ i ].block_id_seen = 0;
    4823           0 :     ctx->block_id_arr[ i ].fec_cnt       = 0U;
    4824           0 :     memset( &ctx->block_id_arr[ i ].block_info, 0, sizeof(fd_block_id_t) );
    4825           0 :   }
    4826             : 
    4827           0 :   ctx->resolv_tile_cnt = fd_topo_tile_name_cnt( topo, "resolv" );
    4828             : 
    4829           0 :   ctx->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
    4830           0 :   FD_TEST( ctx->keyswitch );
    4831           0 :   ctx->halt_replay = 0;
    4832             : 
    4833           0 :   FD_TEST( tile->in_cnt<=sizeof(ctx->in)/sizeof(ctx->in[0]) );
    4834           0 :   for( ulong i=0UL; i<tile->in_cnt; i++ ) {
    4835           0 :     fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
    4836           0 :     fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
    4837             : 
    4838           0 :     if( FD_LIKELY( link->dcache ) ) {
    4839           0 :       ctx->in[ i ].mem    = link_wksp->wksp;
    4840           0 :       ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
    4841           0 :       ctx->in[ i ].wmark  = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
    4842           0 :       ctx->in[ i ].mtu    = link->mtu;
    4843           0 :     }
    4844             : 
    4845           0 :     if(      !strcmp( link->name, "genesi_out"    ) ) ctx->in_kind[ i ] = IN_KIND_GENESIS;
    4846           0 :     else if( !strcmp( link->name, "ipecho_out"    ) ) ctx->in_kind[ i ] = IN_KIND_IPECHO;
    4847           0 :     else if( !strcmp( link->name, "snapin_manif"  ) ) ctx->in_kind[ i ] = IN_KIND_SNAP;
    4848           0 :     else if( !strcmp( link->name, "execrp_replay" ) ) ctx->in_kind[ i ] = IN_KIND_EXECRP;
    4849           0 :     else if( !strcmp( link->name, "poh_replay"    ) ) ctx->in_kind[ i ] = IN_KIND_MOTOR;
    4850           0 :     else if( !strcmp( link->name, "resolv_replay" ) ) ctx->in_kind[ i ] = IN_KIND_RESOLV;
    4851           0 :     else if( !strcmp( link->name, "shred_out"     ) ) ctx->in_kind[ i ] = IN_KIND_REPAIR;
    4852           0 :     else if( !strcmp( link->name, "repair_out"    ) ) ctx->in_kind[ i ] = IN_KIND_REPAIR;
    4853           0 :     else if( !strcmp( link->name, "txsend_out"    ) ) ctx->in_kind[ i ] = IN_KIND_TXSEND;
    4854           0 :     else if( !strcmp( link->name, "rpc_replay"    ) ) ctx->in_kind[ i ] = IN_KIND_RPC;
    4855           0 :     else if( !strcmp( link->name, "gossip_misc"   ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP_OUT;
    4856           0 :     else if( !strcmp( link->name, "snapmk_out"    ) ) ctx->in_kind[ i ] = IN_KIND_SNAPMK;
    4857           0 :     else if( !strcmp( link->name, "admin_replay"  ) ) ctx->in_kind[ i ] = IN_KIND_ADMIN;
    4858           0 :     else if( !strcmp( link->name, "votor_out"     ) ) ctx->in_kind[ i ] = IN_KIND_VOTOR;
    4859           0 :     else FD_LOG_ERR(( "unexpected input link name %s", link->name ));
    4860             : 
    4861           0 :     if( ctx->in_kind[ i ]==IN_KIND_ADMIN ) {
    4862           0 :       FD_TEST( ( ctx->admin_out_idx = fd_topo_find_tile_out_link( topo, tile, "replay_admin", 0UL ) )!=ULONG_MAX );
    4863           0 :     }
    4864           0 :   }
    4865             : 
    4866           0 :   *ctx->epoch_out  = out1( topo, tile, "replay_epoch" ); FD_TEST( ctx->epoch_out->idx!=ULONG_MAX );
    4867           0 :   *ctx->replay_out = out1( topo, tile, "replay_out"   ); FD_TEST( ctx->replay_out->idx!=ULONG_MAX );
    4868           0 :   *ctx->slot_out   = out1( topo, tile, "replay_slot"  ); FD_TEST( ctx->slot_out->idx!=ULONG_MAX );
    4869           0 :   *ctx->rotor_out  = out1( topo, tile, "replay_repair" );
    4870           0 :   *ctx->snapmk_out = out1( topo, tile, "replay_snapmk" ); FD_TEST( ctx->snapmk.supported == (ctx->snapmk_out->idx!=ULONG_MAX) );
    4871             : 
    4872           0 :   ctx->exec_cnt = 0UL;
    4873           0 :   for( ulong i=0UL; i<FD_SCHED_MAX_EXEC_TILE_CNT; i++ ) ctx->exec_out[ i ].idx = ULONG_MAX;
    4874           0 :   for( ulong i=0UL; i<tile->out_cnt; i++ ) {
    4875           0 :     fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ];
    4876           0 :     if( strcmp( link->name, "replay_execrp" ) ) continue;
    4877           0 :     FD_TEST( link->kind_id<FD_SCHED_MAX_EXEC_TILE_CNT && ctx->exec_out[ link->kind_id ].idx==ULONG_MAX );
    4878           0 :     void * mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
    4879           0 :     ulong chunk0 = fd_dcache_compact_chunk0( mem, link->dcache );
    4880           0 :     ctx->exec_out[ link->kind_id ] = (fd_replay_out_link_t){
    4881           0 :       .idx    = i,
    4882           0 :       .mem    = mem,
    4883           0 :       .chunk0 = chunk0,
    4884           0 :       .wmark  = fd_dcache_compact_wmark( mem, link->dcache, link->mtu ),
    4885           0 :       .chunk  = chunk0,
    4886           0 :     };
    4887           0 :     ctx->exec_cnt++;
    4888           0 :   }
    4889           0 :   FD_TEST( ctx->exec_cnt && ctx->exec_cnt==fd_topo_tile_name_cnt( topo, "execrp" ) );
    4890           0 :   for( ulong i=0UL; i<ctx->exec_cnt; i++ ) FD_TEST( ctx->exec_out[ i ].idx!=ULONG_MAX );
    4891             : 
    4892           0 :   ctx->slot_out_seq   = fd_mcache_seq_laddr_const( topo->links[ tile->out_link_id[ ctx->slot_out->idx   ] ].mcache );
    4893             : 
    4894           0 :   ctx->rpc_enabled = fd_topo_find_tile( topo, "rpc", 0UL )!=ULONG_MAX;
    4895             : 
    4896           0 :   if( FD_UNLIKELY( strcmp( "", tile->replay.solcap_capture ) ) ) {
    4897           0 :     ulong idx = fd_topo_find_tile_out_link( topo, tile, "cap_repl", 0UL );
    4898           0 :     FD_TEST( idx!=ULONG_MAX );
    4899           0 :     fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ idx ] ];
    4900             : 
    4901             : 
    4902           0 :     fd_capture_link_buf_t * cap_repl_out = ctx->cap_repl_out;
    4903           0 :     cap_repl_out->base.vt = &fd_capture_link_buf_vt;
    4904           0 :     cap_repl_out->idx     = idx;
    4905           0 :     cap_repl_out->mem     = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
    4906           0 :     cap_repl_out->chunk0  = fd_dcache_compact_chunk0( cap_repl_out->mem, link->dcache );
    4907           0 :     cap_repl_out->wmark   = fd_dcache_compact_wmark( cap_repl_out->mem, link->dcache, link->mtu );
    4908           0 :     cap_repl_out->chunk   = cap_repl_out->chunk0;
    4909           0 :     cap_repl_out->mcache  = link->mcache;
    4910           0 :     cap_repl_out->depth   = fd_mcache_depth( link->mcache );
    4911           0 :     cap_repl_out->seq     = 0UL;
    4912             : 
    4913           0 :     ctx->capture_ctx->capctx_type.buf  = cap_repl_out;
    4914           0 :     ctx->capture_ctx->capture_link    = &cap_repl_out->base;
    4915           0 :     ctx->capture_ctx->current_txn_idx = 0UL;
    4916             : 
    4917             : 
    4918           0 :     ulong consumer_tile_idx = fd_topo_find_tile( topo, "solcap", 0UL );
    4919           0 :     fd_topo_tile_t const * consumer_tile = &topo->tiles[ consumer_tile_idx ];
    4920           0 :     cap_repl_out->fseq = NULL;
    4921           0 :     for( ulong j = 0UL; j < consumer_tile->in_cnt; j++ ) {
    4922           0 :       if( FD_UNLIKELY( consumer_tile->in_link_id[ j ]  == link->id ) ) {
    4923           0 :         cap_repl_out->fseq = fd_fseq_join( fd_topo_obj_laddr( topo, consumer_tile->in_link_fseq_obj_id[ j ] ) );
    4924           0 :         FD_TEST( cap_repl_out->fseq );
    4925           0 :         break;
    4926           0 :       }
    4927           0 :     }
    4928           0 :   }
    4929             : 
    4930           0 :   fd_memset( &ctx->metrics, 0, sizeof(ctx->metrics) );
    4931             : 
    4932           0 :   fd_histf_join( fd_metrics_histf_new_ns( ctx->metrics.store_query_work,   FD_MHIST_EDGES_NS( REPLAY, STORE_QUERY_DURATION_NANOS ) ) );
    4933             : 
    4934             :   /* Ensure precompiles are available, crash fast otherwise */
    4935           0 :   fd_precompiles();
    4936             : 
    4937           0 :   ctx->report_runtime_diffs = tile->replay.report_runtime_diffs;
    4938           0 :   ctx->banks->report_runtime_diffs = tile->replay.report_runtime_diffs;
    4939             : 
    4940           0 :   ulong diff_link_id = fd_topo_find_link( topo, "replay_diff", 0UL );
    4941           0 :   if( FD_UNLIKELY( diff_link_id!=ULONG_MAX ) ) fd_event_runtime_diff_tl = fd_event_reporter_join( replay_diff_reporter, topo, diff_link_id );
    4942             : 
    4943           0 :   ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
    4944           0 :   if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
    4945           0 :     FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
    4946           0 : }
    4947             : 
    4948             : static ulong
    4949             : populate_allowed_seccomp( fd_topo_t const *      topo,
    4950             :                           fd_topo_tile_t const * tile,
    4951             :                           ulong                  out_cnt,
    4952           0 :                           struct sock_filter *   out ) {
    4953           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    4954           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    4955           0 :   fd_replay_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_tile_t), sizeof(fd_replay_tile_t) );
    4956           0 :   populate_sock_filter_policy_fd_replay_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), FD_ACCDB_FD_RW, (uint)ctx->store_disk_fd, FD_STAKE_DELEGATIONS_FD, FD_EPOCH_CREDITS_FD, FD_COST_TRACKER_FD, FD_VOTE_STAKES_FD, (uint)FD_TXNCACHE_FD );
    4957           0 :   return sock_filter_policy_fd_replay_tile_instr_cnt;
    4958           0 : }
    4959             : 
    4960             : static ulong
    4961             : populate_allowed_fds( fd_topo_t const *      topo,
    4962             :                       fd_topo_tile_t const * tile,
    4963             :                       ulong                  out_fds_cnt,
    4964           0 :                       int *                  out_fds ) {
    4965           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    4966           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    4967           0 :   fd_replay_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_tile_t), sizeof(fd_replay_tile_t) );
    4968           0 :   if( FD_UNLIKELY( out_fds_cnt<9UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
    4969             : 
    4970           0 :   ulong out_cnt = 0UL;
    4971           0 :   out_fds[ out_cnt++ ] = 2; /* stderr */
    4972           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
    4973           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
    4974           0 :   out_fds[ out_cnt++ ] = FD_ACCDB_FD_RW; /* accounts db */
    4975           0 :   out_fds[ out_cnt++ ] = FD_STAKE_DELEGATIONS_FD; /* stake delegation disk spill */
    4976           0 :   out_fds[ out_cnt++ ] = FD_EPOCH_CREDITS_FD; /* epoch credits disk spill */
    4977           0 :   out_fds[ out_cnt++ ] = FD_COST_TRACKER_FD; /* cost tracker disk spill */
    4978           0 :   out_fds[ out_cnt++ ] = FD_VOTE_STAKES_FD; /* vote stakes disk spill */
    4979           0 :   out_fds[ out_cnt++ ] = FD_TXNCACHE_FD; /* transaction cache spill */
    4980           0 :   if( FD_LIKELY( ctx->store_disk_fd>=0 ) )
    4981           0 :     out_fds[ out_cnt++ ] = ctx->store_disk_fd;
    4982             : 
    4983           0 :   return out_cnt;
    4984           0 : }
    4985             : 
    4986             : static inline void
    4987           0 : during_housekeeping( fd_replay_tile_t * ctx ) {
    4988           0 :   wait_info_publish( ctx );
    4989             : 
    4990           0 :   if( FD_UNLIKELY( fd_clock_tile_recal_due( ctx->clock ) ) ) fd_clock_tile_recal( ctx->clock );
    4991             : 
    4992           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
    4993           0 :     FD_CHECK_CRIT( ctx->halt_replay, "state machine corruption" );
    4994           0 :     FD_LOG_DEBUG(( "keyswitch: unhalting replay" ));
    4995           0 :     ctx->halt_replay = 0;
    4996           0 :     fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
    4997           0 :   }
    4998             : 
    4999           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
    5000           0 :     if( FD_LIKELY( !ctx->halt_replay ) ) FD_LOG_DEBUG(( "keyswitch: halting replay" ));
    5001           0 :     ctx->halt_replay = 1;
    5002           0 :     if( !ctx->is_leader && ctx->is_booted ) maybe_switch_identity( ctx );
    5003           0 :   }
    5004           0 : }
    5005             : 
    5006             : #undef DEBUG_LOGGING
    5007             : 
    5008             : /* Credits are checked against the out link with the fewest, so the
    5009             :    burst is the most frags one stem iteration publishes to any one out
    5010             :    link: 3, from boot (slot completed, next leader, root advanced) or
    5011             :    from try_fini_leader chaining into the next leader slot (slot
    5012             :    completed, next leader, became leader).  after_credit and
    5013             :    returnable_frag never both publish in one iteration: every
    5014             :    after_credit path that publishes returns nonzero, which skips the
    5015             :    input poll.  The exception is REPLAY_SIG_SLOT_STATES, which doesn't
    5016             :    skip the input poll. */
    5017           0 : #define STEM_BURST (5UL)
    5018             : 
    5019             : /* Housekeeping interval in ticks, tuned independently of output-link
    5020             :    depths. */
    5021           0 : #define STEM_LAZY ((long)36865)
    5022             : 
    5023           0 : #define STEM_CALLBACK_CONTEXT_TYPE  fd_replay_tile_t
    5024           0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_replay_tile_t)
    5025             : 
    5026           0 : #define STEM_CALLBACK_METRICS_WRITE       metrics_write
    5027           0 : #define STEM_CALLBACK_AFTER_CREDIT        after_credit
    5028           0 : #define STEM_CALLBACK_BEFORE_FRAG         before_frag
    5029           0 : #define STEM_CALLBACK_RETURNABLE_FRAG     returnable_frag
    5030           0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
    5031             : 
    5032             : #include "../../disco/stem/fd_stem.c"
    5033             : 
    5034             : static ulong
    5035           0 : max_event_sz( fd_topo_tile_t const * tile ) {
    5036             :   /* replay always emits block_completed; runtime_block (the largest
    5037             :      runtime event) is added when runtime diffs are on. */
    5038           0 :   ulong sz = fd_ulong_max( sizeof(fd_event_block_completed_t), sizeof(fd_event_metrics_trigger_t) );
    5039           0 :   if( tile->replay.report_runtime_diffs && sizeof(fd_event_runtime_block_t)>sz ) sz = sizeof(fd_event_runtime_block_t);
    5040           0 :   return sz;
    5041           0 : }
    5042             : 
    5043             : fd_topo_run_tile_t fd_tile_replay = {
    5044             :   .name                     = "replay",
    5045             :   .max_event_sz             = max_event_sz,
    5046             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    5047             :   .populate_allowed_fds     = populate_allowed_fds,
    5048             :   .scratch_align            = scratch_align,
    5049             :   .scratch_footprint        = scratch_footprint,
    5050             :   .privileged_init          = privileged_init,
    5051             :   .unprivileged_init        = unprivileged_init,
    5052             :   .run                      = stem_run,
    5053             : };

Generated by: LCOV version 1.14