LCOV - code coverage report
Current view: top level - discof/tower - fd_tower_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 28 814 3.4 %
Date: 2026-03-31 06:22:16 Functions: 1 57 1.8 %

          Line data    Source code
       1             : #include "fd_tower_tile.h"
       2             : #include "generated/fd_tower_tile_seccomp.h"
       3             : 
       4             : #include "../../choreo/eqvoc/fd_eqvoc.h"
       5             : #include "../../choreo/ghost/fd_ghost.h"
       6             : #include "../../choreo/hfork/fd_hfork.h"
       7             : #include "../../choreo/votes/fd_votes.h"
       8             : #include "../../choreo/tower/fd_tower.h"
       9             : #include "../../choreo/tower/fd_tower_voters.h"
      10             : #include "../../choreo/tower/fd_tower_blocks.h"
      11             : #include "../../choreo/tower/fd_tower_serdes.h"
      12             : #include "../../choreo/tower/fd_tower_stakes.h"
      13             : #include "../../disco/fd_txn_p.h"
      14             : #include "../../disco/keyguard/fd_keyload.h"
      15             : #include "../../disco/keyguard/fd_keyswitch.h"
      16             : #include "../../disco/metrics/fd_metrics.h"
      17             : #include "../../disco/topo/fd_topo.h"
      18             : #include "../../disco/fd_txn_m.h"
      19             : #include "../../discof/fd_accdb_topo.h"
      20             : #include "../../discof/replay/fd_replay_tile.h"
      21             : #include "../../flamenco/accdb/fd_accdb_sync.h"
      22             : #include "../../flamenco/accdb/fd_accdb_pipe.h"
      23             : #include "../../flamenco/leaders/fd_multi_epoch_leaders.h"
      24             : #include "../../flamenco/runtime/fd_bank.h"
      25             : #include "../../flamenco/runtime/program/vote/fd_vote_state_versioned.h"
      26             : #include "../../util/pod/fd_pod.h"
      27             : 
      28             : #include <errno.h>
      29             : #include <fcntl.h>
      30             : #include <unistd.h>
      31             : 
      32             : /* The Tower tile broadly processes three classes of frags, leading to
      33             :    three distinct kinds of frag processing:
      34             : 
      35             :    1. Processing vote _accounts_ (after replaying a block)
      36             : 
      37             :       When Replay finishes executing a block, Tower reads back the vote
      38             :       account state for every staked validator.  This is deterministic:
      39             :       the vote account state is the result of executing all vote txns in
      40             :       the block through the vote program, so it is guaranteed to
      41             :       converge with Agave's view of the same accounts.  Tower uses these
      42             :       accounts to run the fork choice rule (fd_ghost) and TowerBFT
      43             :       (fd_tower).
      44             : 
      45             :    2. Processing vote _transactions_ (at arbitrary points in time)
      46             : 
      47             :       Tower also receives vote txns from Gossip and TPU.  These arrive
      48             :       at arbitrary, nondeterministic times because Gossip and TPU are
      49             :       both unreliable mediums: there's no guarantee we observe all the
      50             :       same vote txns as Agave (nor another Firedancer, for that matter).
      51             : 
      52             :       Tower is stricter than Agave when validating these vote txns (e.g.
      53             :       we use is_simple_vote which requires at most two signers, whereas
      54             :       Agave's Gossip vote parser does not).  Being stricter is
      55             :       acceptable given vote txns from Gossip and TPU are inherently
      56             :       unreliable, so dropping a small number of votes that Agave allows
      57             :       but Firedancer does not is not significant to convergence.
      58             : 
      59             :       However, these same vote txns are (redundantly) transmitted as
      60             :       part of a block as well ie. through Replay.  The validation of
      61             :       these Replay-sourced vote txns _is_ one-to-one with Agave (namely
      62             :       the Vote Program), and critical for convergence.  Specifically, we
      63             :       only process Replay vote txns that have been successfully executed
      64             :       when counting them towards confirmations.
      65             : 
      66             :       This provides an "eventually-consistent" model, specifically that
      67             :       even though individual Gossip or TPU vote txns may be lost, we are
      68             :       guaranteed to "eventually" confirm a block and converge with Agave
      69             :       as long as we replay the block and its descendants, because our
      70             :       vote programs match 1-1.  Gossip / TPU provide an optimization for
      71             :       earlier confirmations as well as a source of security via
      72             :       redundancy, in case we are not receiving block data from the rest
      73             :       of the network.
      74             : 
      75             :       The processing of vote txns is important to (as already alluded)
      76             :       confirmations (fd_notar) and hard fork detection (fd_hfork).
      77             : 
      78             :   3. Processing "other" frags.  Vote account and vote transaction
      79             :      processing (1 and 2 above) is the meat and potatoes, but Tower also
      80             :      processes several auxiliary frag types:
      81             : 
      82             :       a. Shreds (from the shred tile): Tower checks incoming shreds for
      83             :          equivocation via fd_eqvoc.  If two conflicting shreds are
      84             :          detected for the same FEC set, Tower constructs a duplicate
      85             :          proof and publishes it (FD_TOWER_SIG_SLOT_DUPLICATE).
      86             : 
      87             :       b. Duplicate shred gossip messages (from the gossip tile): Tower
      88             :          receives duplicate shred proofs from other validators via
      89             :          gossip.  These proofs arrive in chunks (fd_eqvoc_chunk_insert)
      90             :          and are reassembled and cryptographically verified before being
      91             :          accepted.
      92             : 
      93             :       c. Epoch stake updates (from the replay tile): Tower receives
      94             :          epoch stake information to maintain the leader schedule via
      95             :          fd_stake_ci, which is needed by eqvoc for signature
      96             :          verification of shred proofs.
      97             : 
      98             :       d. Shred version (from the ipecho tile): Tower receives the shred
      99             :          version from ipecho to configure eqvoc's shred version
     100             :          filtering for proof verification.
     101             : 
     102             :       e. Slot dead (from the replay tile): Tower records a NULL bank
     103             :          hash for dead slots in the hard fork detector (fd_hfork).
     104             : 
     105             :    Tower signals to other tiles about events that occur as a result of
     106             :    those three modes, such as what block to vote on, what block to reset
     107             :    onto as leader, what block got rooted, what blocks are duplicates,
     108             :    and what blocks are confirmed.
     109             : 
     110             :    In general, Tower uses "block_id" as the identifier for a block.  The
     111             :    block_id is the merkle root of the last FEC set for a block.  Unlike
     112             :    slot numbers, this is guaranteed to be unique for a given block and
     113             :    is therefore a canonical identifier because slot numbers can identify
     114             :    multiple blocks, if a leader equivocates (produces multiple blocks
     115             :    for the same slot), whereas it is not feasible for a leader to
     116             :    produce block_id collisions.
     117             : 
     118             :    However, the block_id was only introduced into the Solana protocol
     119             :    recently, and TowerBFT still uses the "legacy" identifier of slot
     120             :    numbers for blocks.  So the tile (and relevant modules) will use
     121             :    block_id when possible to interface with the protocol but otherwise
     122             :    fallback to slot number when block_id is unsupported due to limits of
     123             :    the protocol. */
     124             : 
     125             : #define LOGGING 0
     126             : 
     127           0 : #define IN_KIND_DEDUP  (0)
     128           0 : #define IN_KIND_EPOCH  (1)
     129           0 : #define IN_KIND_REPLAY (2)
     130           0 : #define IN_KIND_GOSSIP (3)
     131           0 : #define IN_KIND_IPECHO (4)
     132           0 : #define IN_KIND_SHRED  (5)
     133             : 
     134           0 : #define OUT_IDX 0 /* only a single out link tower_out */
     135           0 : #define AUTH_VTR_LG_MAX (5) /* The Solana Vote Interface supports up to 32 authorized voters. */
     136             : FD_STATIC_ASSERT( 1<<AUTH_VTR_LG_MAX==32, AUTH_VTR_LG_MAX );
     137             : 
     138             : /* Tower processes at most 2 equivocating blocks for a given slot: the
     139             :    first block is the first one we observe for a slot, and the second
     140             :    block is the one that gets duplicate confirmed.  Most of the time,
     141             :    they are the same (ie. the block we first saw is the block that gets
     142             :    duplicate confirmed), but we size for the worst case which is every
     143             :    block in slot_max equivocates and we always see 2 blocks for every
     144             :    slot. */
     145             : 
     146           0 : #define EQVOC_MAX (2)
     147             : 
     148             : /* The Alpenglow VAT caps the voting set of validators to 2000.  Only
     149             :    the top 2000 voters by stake will be counted towards consensus rules.
     150             :    Firedancer uses the same bound for TowerBFT.
     151             : 
     152             :    https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0357-alpenglow_validator_admission_ticket.md */
     153             : 
     154           0 : #define VTR_MAX (2000) /* the maximum # of unique voters ie. node pubkeys. */
     155             : 
     156             : /* PER_VTR_MAX controls how many "entries" a validator is allowed to
     157             :    occupy in various vote-tracking structures.  This is set somewhat
     158             :    arbitrarily based on expected worst-case usage by an honest validator
     159             :    and is set to guard against a malicious spamming validator attempting
     160             :    to fill up Firedancer structures. */
     161             : 
     162           0 : #define PER_VTR_MAX (512) /* the maximum amount of slot history the sysvar retains */
     163             : 
     164             : struct publish {
     165             :   ulong          sig;
     166             :   fd_tower_msg_t msg;
     167             : };
     168             : typedef struct publish publish_t;
     169             : 
     170             : #define DEQUE_NAME publishes
     171           0 : #define DEQUE_T    publish_t
     172             : #include "../../util/tmpl/fd_deque_dynamic.c"
     173             : 
     174             : struct auth_vtr {
     175             :   fd_pubkey_t addr;      /* map key, vote account address */
     176             :   uint        hash;      /* reserved for use by fd_map */
     177             :   ulong       paths_idx; /* index in authorized voter paths */
     178             : };
     179             : typedef struct auth_vtr auth_vtr_t;
     180             : 
     181             : #define MAP_NAME               auth_vtr
     182           0 : #define MAP_T                  auth_vtr_t
     183           0 : #define MAP_LG_SLOT_CNT        AUTH_VTR_LG_MAX
     184           0 : #define MAP_KEY                addr
     185           0 : #define MAP_KEY_T              fd_pubkey_t
     186           0 : #define MAP_KEY_NULL           (fd_pubkey_t){0}
     187           0 : #define MAP_KEY_EQUAL(k0,k1)   (!(memcmp((k0).key,(k1).key,sizeof(fd_pubkey_t))))
     188           0 : #define MAP_KEY_INVAL(k)       (MAP_KEY_EQUAL((k),MAP_KEY_NULL))
     189             : #define MAP_KEY_EQUAL_IS_SLOW  1
     190           0 : #define MAP_KEY_HASH(k)        ((uint)fd_ulong_hash( fd_ulong_load_8( (k).uc ) ))
     191             : #include "../../util/tmpl/fd_map.c"
     192             : 
     193             : #define AUTH_VOTERS_MAX (16UL)
     194             : 
     195             : typedef struct {
     196             :   int         mcache_only;
     197             :   fd_wksp_t * mem;
     198             :   ulong       chunk0;
     199             :   ulong       wmark;
     200             :   ulong       mtu;
     201             : } in_ctx_t;
     202             : 
     203             : struct fd_tower_tile {
     204             :   ulong            seed; /* map seed */
     205             :   int              checkpt_fd;
     206             :   int              restore_fd;
     207             :   fd_pubkey_t      identity_key[1];
     208             :   fd_pubkey_t      vote_account[1];
     209             :   ulong            auth_vtr_path_cnt;  /* number of authorized voter paths passed to tile */
     210             :   uchar            our_vote_acct[FD_VOTE_STATE_DATA_MAX]; /* buffer for reading back our own vote acct data */
     211             :   ulong            our_vote_acct_sz;
     212             : 
     213             :   /* owned joins */
     214             : 
     215             :   fd_wksp_t *      wksp; /* workspace */
     216             :   fd_keyswitch_t * identity_keyswitch;
     217             :   auth_vtr_t *     auth_vtr;
     218             :   fd_keyswitch_t * auth_vtr_keyswitch; /* authorized voter keyswitch */
     219             : 
     220             :   fd_eqvoc_t * eqvoc;
     221             :   fd_ghost_t * ghost;
     222             :   fd_hfork_t * hfork;
     223             :   fd_votes_t * votes;
     224             :   fd_tower_t * tower;
     225             : 
     226             :   fd_tower_t *        scratch_tower;  /* spare tower used during processing */
     227             :   fd_tower_blocks_t * tower_blocks;
     228             :   fd_tower_lockos_t * tower_lockos;
     229             :   fd_tower_stakes_t * tower_stakes;  /* tracks the stakes for each voter in the epoch per fork */
     230             :   fd_tower_voters_t * tower_voters;
     231             :   uchar *             voter_towers;  /* pre-allocated array of vtr_max towers (each FD_TOWER_FOOTPRINT bytes) */
     232             : 
     233             :   publish_t *                publishes; /* deque of slot_confirmed msgs queued for publishing */
     234             :   fd_multi_epoch_leaders_t * mleaders; /* multi-epoch leaders */
     235             : 
     236             :   /* borrowed joins */
     237             : 
     238             :   fd_banks_t *    banks;
     239             :   fd_accdb_user_t accdb[1];
     240             : 
     241             :   /* static structures */
     242             : 
     243             :   fd_gossip_duplicate_shred_t   chunks[FD_EQVOC_CHUNK_CNT];
     244             :   fd_compact_tower_sync_serde_t compact_tower_sync_serde;
     245             :   uchar                         vote_txn[FD_TPU_PARSED_MTU];
     246             : 
     247             :   uchar __attribute__((aligned(FD_MULTI_EPOCH_LEADERS_ALIGN))) mleaders_mem[ FD_MULTI_EPOCH_LEADERS_FOOTPRINT ];
     248             : 
     249             :   /* metadata */
     250             : 
     251             :   int    halt_signing;
     252             :   int    hard_fork_fatal;
     253             :   ushort shred_version;
     254             :   ulong  init_slot; /* initial slot (either genesis or snapshot slot) */
     255             :   ulong  root_slot; /* monotonically increasing contiguous root slot */
     256             : 
     257             :   /* in/out link setup */
     258             : 
     259             :   int      in_kind[ 64UL ];
     260             :   in_ctx_t in     [ 64UL ];
     261             : 
     262             :   fd_wksp_t * out_mem;
     263             :   ulong       out_chunk0;
     264             :   ulong       out_wmark;
     265             :   ulong       out_chunk;
     266             :   ulong       out_seq;
     267             : 
     268             :   /* metrics */
     269             : 
     270             :   struct {
     271             : 
     272             :     ulong ignored_cnt;
     273             :     ulong ignored_slot;
     274             :     ulong eqvoc_cnt;
     275             :     ulong eqvoc_slot;
     276             : 
     277             :     ulong replay_slot;
     278             :     ulong last_vote_slot;
     279             :     ulong reset_slot;
     280             :     ulong root_slot;
     281             :     ulong init_slot;
     282             : 
     283             :     ulong ancestor_rollback;
     284             :     ulong sibling_confirmed;
     285             :     ulong same_fork;
     286             :     ulong switch_pass;
     287             :     ulong switch_fail;
     288             :     ulong lockout_fail;
     289             :     ulong threshold_fail;
     290             :     ulong propagated_fail;
     291             : 
     292             :     ulong txn_bad_deser;
     293             :     ulong txn_bad_tower;
     294             :     ulong txn_not_tower_sync;
     295             :     ulong txn_empty_tower;
     296             :     ulong txn_not_ready;
     297             : 
     298             :     ulong votes_too_old;
     299             :     ulong votes_too_new;
     300             :     ulong votes_unknown_vtr;
     301             :     ulong votes_already_voted;
     302             :     ulong votes_unknown_slot;
     303             :     ulong votes_unknown_block_id;
     304             : 
     305             :     ulong eqvoc_success_merkle;
     306             :     ulong eqvoc_success_meta;
     307             :     ulong eqvoc_success_last;
     308             :     ulong eqvoc_success_overlap;
     309             :     ulong eqvoc_success_chained;
     310             : 
     311             :     ulong eqvoc_err_serde;
     312             :     ulong eqvoc_err_slot;
     313             :     ulong eqvoc_err_version;
     314             :     ulong eqvoc_err_type;
     315             :     ulong eqvoc_err_merkle;
     316             :     ulong eqvoc_err_signature;
     317             : 
     318             :     ulong eqvoc_err_chunk_cnt;
     319             :     ulong eqvoc_err_chunk_idx;
     320             :     ulong eqvoc_err_chunk_len;
     321             : 
     322             :     ulong eqvoc_err_ignored_from;
     323             :     ulong eqvoc_err_ignored_slot;
     324             : 
     325             :     ulong eqvoc_proof_constructed;
     326             :     ulong eqvoc_proof_verified;
     327             : 
     328             :     ulong ghost_not_voted;
     329             :     ulong ghost_too_old;
     330             :     ulong ghost_already_voted;
     331             : 
     332             :     ulong hfork_unknown_vtr;
     333             :     ulong hfork_already_voted;
     334             :     ulong hfork_too_old;
     335             : 
     336             :     ulong hfork_matched_slot;
     337             :     ulong hfork_mismatched_slot;
     338             :   } metrics;
     339             : };
     340             : typedef struct fd_tower_tile fd_tower_tile_t;
     341             : 
     342             : static void
     343             : update_metrics_eqvoc( fd_tower_tile_t * ctx,
     344           0 :                       int               err ) {
     345           0 :   switch( err ) {
     346             : 
     347           0 :   case FD_EQVOC_SUCCESS: break;
     348             : 
     349           0 :   case FD_EQVOC_SUCCESS_MERKLE:  ctx->metrics.eqvoc_success_merkle++;  break;
     350           0 :   case FD_EQVOC_SUCCESS_META:    ctx->metrics.eqvoc_success_meta++;    break;
     351           0 :   case FD_EQVOC_SUCCESS_LAST:    ctx->metrics.eqvoc_success_last++;    break;
     352           0 :   case FD_EQVOC_SUCCESS_OVERLAP: ctx->metrics.eqvoc_success_overlap++; break;
     353           0 :   case FD_EQVOC_SUCCESS_CHAINED: ctx->metrics.eqvoc_success_chained++; break;
     354             : 
     355           0 :   case FD_EQVOC_ERR_SERDE:   ctx->metrics.eqvoc_err_serde++;     break;
     356           0 :   case FD_EQVOC_ERR_SLOT:    ctx->metrics.eqvoc_err_slot++;      break;
     357           0 :   case FD_EQVOC_ERR_VERSION: ctx->metrics.eqvoc_err_version++;   break;
     358           0 :   case FD_EQVOC_ERR_TYPE:    ctx->metrics.eqvoc_err_type++;      break;
     359           0 :   case FD_EQVOC_ERR_MERKLE:  ctx->metrics.eqvoc_err_merkle++;    break;
     360           0 :   case FD_EQVOC_ERR_SIG:     ctx->metrics.eqvoc_err_signature++; break;
     361             : 
     362           0 :   case FD_EQVOC_ERR_CHUNK_CNT: ctx->metrics.eqvoc_err_chunk_cnt++; break;
     363           0 :   case FD_EQVOC_ERR_CHUNK_IDX: ctx->metrics.eqvoc_err_chunk_idx++; break;
     364           0 :   case FD_EQVOC_ERR_CHUNK_LEN: ctx->metrics.eqvoc_err_chunk_len++; break;
     365             : 
     366           0 :   case FD_EQVOC_ERR_IGNORED_FROM: ctx->metrics.eqvoc_err_ignored_from++; break;
     367           0 :   case FD_EQVOC_ERR_IGNORED_SLOT: ctx->metrics.eqvoc_err_ignored_slot++; break;
     368             : 
     369           0 :   default: FD_LOG_ERR(( "unhandled eqvoc err %d", err ));
     370           0 :   }
     371           0 : }
     372             : 
     373             : static void
     374             : update_metrics_hfork( fd_tower_tile_t * ctx,
     375             :                       int               hfork_err,
     376             :                       ulong             slot,
     377           0 :                       fd_hash_t const * block_id ) {
     378           0 :   switch( hfork_err ) {
     379           0 :   case FD_HFORK_SUCCESS_MATCHED:   ctx->metrics.hfork_matched_slot = fd_ulong_max( ctx->metrics.hfork_matched_slot, slot ); break;
     380           0 :   case FD_HFORK_SUCCESS:           break;
     381           0 :   case FD_HFORK_ERR_MISMATCHED: {
     382           0 :     ctx->metrics.hfork_mismatched_slot = fd_ulong_max( ctx->metrics.hfork_mismatched_slot, slot );
     383           0 :     FD_BASE58_ENCODE_32_BYTES( block_id->uc, _block_id );
     384           0 :     if( FD_UNLIKELY( ctx->hard_fork_fatal ) ) FD_LOG_ERR    (( "HARD FORK DETECTED for slot %lu block ID `%s`", slot, _block_id ));
     385           0 :     else                                      FD_LOG_WARNING(( "HARD FORK DETECTED for slot %lu block ID `%s`", slot, _block_id ));
     386           0 :     break;
     387           0 :   }
     388           0 :   case FD_HFORK_ERR_UNKNOWN_VTR:   ctx->metrics.hfork_unknown_vtr++;   break;
     389           0 :   case FD_HFORK_ERR_ALREADY_VOTED: ctx->metrics.hfork_already_voted++; break;
     390           0 :   case FD_HFORK_ERR_VOTE_TOO_OLD:  ctx->metrics.hfork_too_old++;       break;
     391           0 :   default: FD_LOG_ERR(( "unhandled hfork_err %d", hfork_err ));
     392           0 :   }
     393           0 : }
     394             : 
     395             : static void
     396             : update_metrics_votes( fd_tower_tile_t * ctx,
     397           0 :                       int               err ) {
     398           0 :   ctx->metrics.votes_too_new       += (ulong)(err==FD_VOTES_ERR_VOTE_TOO_NEW);
     399           0 :   ctx->metrics.votes_unknown_vtr   += (ulong)(err==FD_VOTES_ERR_UNKNOWN_VTR);
     400           0 :   ctx->metrics.votes_already_voted += (ulong)(err==FD_VOTES_ERR_ALREADY_VOTED);
     401           0 : }
     402             : 
     403             : static void
     404             : update_metrics_ghost( fd_tower_tile_t * ctx,
     405           0 :                       int               err ) {
     406           0 :   ctx->metrics.ghost_not_voted     += (ulong)(err==FD_GHOST_ERR_NOT_VOTED);
     407           0 :   ctx->metrics.ghost_too_old       += (ulong)(err==FD_GHOST_ERR_VOTE_TOO_OLD);
     408           0 :   ctx->metrics.ghost_already_voted += (ulong)(err==FD_GHOST_ERR_ALREADY_VOTED);
     409           0 : }
     410             : 
     411             : static void
     412             : confirm_block( fd_tower_tile_t * ctx,
     413             :                ulong             slot,
     414             :                fd_hash_t const * block_id,
     415           0 :                ulong             total_stake ) {
     416             : 
     417           0 :   fd_tower_blk_t * tower_blk = fd_tower_blocks_query( ctx->tower_blocks, slot );
     418           0 :   fd_ghost_blk_t * ghost_blk = fd_ghost_query( ctx->ghost, block_id );
     419           0 :   fd_votes_blk_t * votes_blk = fd_votes_query( ctx->votes, slot, block_id );
     420           0 :   if( FD_UNLIKELY( !votes_blk ) ) return;
     421             : 
     422           0 :   static double const ratios[FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT] = FD_TOWER_SLOT_CONFIRMED_RATIOS;
     423           0 :   int const           levels[FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT] = FD_TOWER_SLOT_CONFIRMED_LEVELS;
     424           0 :   for( int i = 0; i < FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT; i++ ) {
     425           0 :     if( FD_LIKELY( fd_uchar_extract_bit( votes_blk->flags, i ) ) ) continue; /* already contiguously confirmed */
     426           0 :     double ratio = (double)votes_blk->stake / (double)total_stake;
     427           0 :     if( FD_LIKELY( ratio < ratios[i] ) ) break; /* threshold not met */
     428             : 
     429             :     /* If ghost is missing, then we know this is a forward
     430             :        confirmation (ie. we haven't replayed the block yet). */
     431             : 
     432           0 :     if( FD_UNLIKELY( !ghost_blk ) ) {
     433           0 :       if( fd_uchar_extract_bit( votes_blk->flags, i+4 ) ) continue; /* already forward confirmed */
     434           0 :       votes_blk->flags = fd_uchar_set_bit( votes_blk->flags, i+4 );
     435           0 :       publishes_push_head( ctx->publishes, (publish_t){ .sig = FD_TOWER_SIG_SLOT_CONFIRMED, .msg = { .slot_confirmed = (fd_tower_slot_confirmed_t){ .level = levels[i], .fwd = 1, .slot = votes_blk->key.slot, .block_id = votes_blk->key.block_id } } } );
     436             : 
     437             :       /* If we have a tower_blk for the slot, but not a ghost blk, this
     438             :          implies we replayed one version of the slot that is not the
     439             :          same as the one getting confirmed. */
     440             : 
     441           0 :       if( FD_LIKELY( tower_blk ) ) {
     442           0 :         FD_TEST( 0!=memcmp( &tower_blk->replayed_block_id, &votes_blk->key.block_id, sizeof(fd_hash_t) ) );
     443           0 :         tower_blk->confirmed          = 1;
     444           0 :         tower_blk->confirmed_block_id = votes_blk->key.block_id;
     445           0 :       }
     446           0 :       continue;
     447           0 :     }
     448             : 
     449             :     /* Otherwise if they are present, then we know this is not a forward
     450             :        confirmation and thus we have replayed and confirmed the block,
     451             :        which also implies we have replayed and confirmed its ancestry.
     452             :        So publish confirmations for all ancestors (short-circuit at the
     453             :        first ancestor that was already confirmed).
     454             : 
     455             :        We use ghost to walk up the ancestry and also mark ghost and
     456             :        tower blocks as confirmed as we walk if this is the duplicate
     457             :        confirmation level. */
     458             : 
     459           0 :     fd_ghost_blk_t * ghost_anc = ghost_blk;
     460           0 :     fd_tower_blk_t * tower_anc = tower_blk;
     461           0 :     fd_votes_blk_t * votes_anc = votes_blk;
     462           0 :     while( FD_LIKELY( ghost_anc ) ) {
     463             : 
     464           0 :       tower_anc = fd_tower_blocks_query( ctx->tower_blocks, ghost_anc->slot );
     465           0 :       votes_anc = fd_votes_query( ctx->votes, ghost_anc->slot, &ghost_anc->id );
     466           0 :       if( FD_UNLIKELY( !tower_anc || !votes_anc ) ) break;
     467             : 
     468             :       /* Terminate at the first ancestor that has already reached this
     469             :          confirmation level. */
     470             : 
     471           0 :       if( FD_LIKELY( fd_uchar_extract_bit( votes_anc->flags, i ) ) ) break;
     472             : 
     473             :       /* Mark the ancestor as confirmed at this level.  If this is the
     474             :          duplicate confirmation level, also mark the ghost and tower
     475             :          blocks as confirmed. */
     476             : 
     477           0 :       votes_anc->flags = fd_uchar_set_bit( votes_anc->flags, i );
     478           0 :       publishes_push_head( ctx->publishes, (publish_t){ .sig = FD_TOWER_SIG_SLOT_CONFIRMED, .msg = { .slot_confirmed = (fd_tower_slot_confirmed_t){ .level = levels[i], .fwd = 0, .slot = ghost_anc->slot, .block_id = ghost_anc->id } } } );
     479           0 :       if( FD_UNLIKELY( levels[i]==FD_TOWER_SLOT_CONFIRMED_PROPAGATED ) ) {
     480           0 :         tower_anc->propagated = 1;
     481           0 :       }
     482           0 :       if( FD_UNLIKELY( levels[i]==FD_TOWER_SLOT_CONFIRMED_DUPLICATE ) ) {
     483           0 :         tower_anc->confirmed          = 1;
     484           0 :         tower_anc->confirmed_block_id = ghost_anc->id;
     485           0 :       }
     486             : 
     487             :       /* Walk up to next ancestor. */
     488             : 
     489           0 :       ghost_anc = fd_ghost_parent( ctx->ghost, ghost_anc );
     490           0 :     }
     491           0 :   }
     492           0 : }
     493             : 
     494             : /* count_vote counts vote txns from Gossip, TPU and Replay.  Note these
     495             :    txns have already been parsed and sigverified before they are sent to
     496             :    tower.  Replay votes have been successfully executed.  They are
     497             :    counted towards votes and hfork (see point 2 in the top-level
     498             :    documentation). */
     499             : 
     500             : static void
     501             : count_vote( fd_tower_tile_t * ctx,
     502             :             fd_txn_t const *  txn,
     503          24 :             uchar const *     payload ) {
     504             : 
     505          24 :   if( FD_UNLIKELY( ctx->root_slot==ULONG_MAX ) ) { ctx->metrics.txn_not_ready++; return; }
     506             : 
     507             :   /* We are a little stricter than Agave here when validating the vote
     508             :      because we use the same validation as pack ie. is_simple_vote which
     509             :      includes a check that there are at most two signers, whereas
     510             :      Agave's gossip vote parser does not perform that same check (the
     511             :      only two signers are the identity key and vote authority, which may
     512             :      optionally be the same).
     513             : 
     514             :      Being a little stricter here is ok because even if we drop some
     515             :      votes with extraneous signers that Agave would consider valid
     516             :      (unlikely), gossip votes are in general considered unreliable and
     517             :      ultimately consensus is reached through replaying the vote txns.
     518             : 
     519             :      The remaining checks mirror Agave as closely as possible (and are
     520             :      documented throughout below). */
     521             : 
     522          24 :   if( FD_UNLIKELY( !fd_txn_is_simple_vote_transaction( txn, payload ) ) ) return;
     523             : 
     524             :   /* TODO check the authorized voter for this vote account (from epoch
     525             :      stakes) is one of the signers. */
     526             : 
     527             :   /* Filter any non-TowerSync vote txns. */
     528             : 
     529             :   /* TODO SECURITY ensure SIMD-0138 is activated */
     530             : 
     531          24 :   fd_txn_instr_t const * instr      = &txn->instr[0];
     532          24 :   uchar const *          instr_data = payload + instr->data_off;
     533          24 :   if( FD_UNLIKELY( instr->data_sz<4 ) ) { ctx->metrics.txn_bad_deser++; return; }
     534             : 
     535          24 :   uint kind = fd_uint_load_4_fast( instr_data );
     536          24 :   if( FD_UNLIKELY( kind!=FD_VOTE_IX_KIND_TOWER_SYNC && kind!=FD_VOTE_IX_KIND_TOWER_SYNC_SWITCH ) ) { ctx->metrics.txn_not_tower_sync++; return; };
     537             : 
     538             :   /* Deserialize the TowerSync out of the vote txn. */
     539             : 
     540          24 :   int err = fd_compact_tower_sync_de( &ctx->compact_tower_sync_serde, instr_data + sizeof(uint), instr->data_sz - sizeof(uint) );
     541          24 :   if( FD_UNLIKELY( err==-1 ) ) { ctx->metrics.txn_bad_deser++; return; }
     542          24 :   ulong slot = ctx->compact_tower_sync_serde.root;
     543          24 :   fd_tower_remove_all( ctx->scratch_tower );
     544         150 :   for( ulong i = 0; i < ctx->compact_tower_sync_serde.lockouts_cnt; i++ ) {
     545         126 :     slot += ctx->compact_tower_sync_serde.lockouts[i].offset;
     546         126 :     fd_tower_push_tail( ctx->scratch_tower, (fd_tower_vote_t){ .slot = slot, .conf = ctx->compact_tower_sync_serde.lockouts[i].confirmation_count } );
     547         126 :   }
     548             : 
     549             :   /* Return early if their tower is empty. */
     550             : 
     551          24 :   if( FD_UNLIKELY( fd_tower_empty( ctx->scratch_tower ) ) ) { ctx->metrics.txn_empty_tower++; return; };
     552             : 
     553             :   /* Validate the tower. */
     554             : 
     555          21 :   fd_tower_vote_t const * prev = fd_tower_peek_head_const( ctx->scratch_tower );
     556          21 :   if( FD_UNLIKELY( prev->conf > FD_TOWER_VOTE_MAX ) ) { ctx->metrics.txn_bad_tower++; return; }
     557             : 
     558          18 :   fd_tower_iter_t iter = fd_tower_iter_next( ctx->scratch_tower, fd_tower_iter_init( ctx->scratch_tower ) );
     559         117 :   for( ; !fd_tower_iter_done( ctx->scratch_tower, iter ); iter = fd_tower_iter_next( ctx->scratch_tower, iter ) ) {
     560         105 :     fd_tower_vote_t const * vote = fd_tower_iter_ele( ctx->scratch_tower, iter );
     561         105 :     if( FD_UNLIKELY( vote->slot <= prev->slot        ) ) { ctx->metrics.txn_bad_tower++; return; }
     562         105 :     if( FD_UNLIKELY( vote->conf >= prev->conf        ) ) { ctx->metrics.txn_bad_tower++; return; }
     563          99 :     if( FD_UNLIKELY( vote->conf >  FD_TOWER_VOTE_MAX ) ) { ctx->metrics.txn_bad_tower++; return; }
     564          99 :     prev = vote;
     565          99 :   }
     566             : 
     567          12 :   if( FD_UNLIKELY( 0==memcmp( &ctx->compact_tower_sync_serde.block_id, &hash_null, sizeof(fd_hash_t) ) ) ) { ctx->metrics.votes_unknown_block_id++; return; };
     568             : 
     569           0 :   fd_pubkey_t const * accs     = (fd_pubkey_t const *)fd_type_pun_const( payload + txn->acct_addr_off );
     570           0 :   fd_pubkey_t const * vote_acc = NULL;
     571           0 :   if( FD_UNLIKELY( txn->signature_cnt==1 ) ) vote_acc = (fd_pubkey_t const *)fd_type_pun_const( &accs[1] ); /* identity and authority same, account idx 1 is the vote account address */
     572           0 :   else                                       vote_acc = (fd_pubkey_t const *)fd_type_pun_const( &accs[2] ); /* identity and authority diff, account idx 2 is the vote account address */
     573             : 
     574             :   /* The vote txn contains a block id and bank hash for their last vote
     575             :      slot in the tower.  Agave always counts the last vote.
     576             : 
     577             :      https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L476-L487 */
     578             : 
     579           0 :   fd_tower_vote_t const * their_last_vote = fd_tower_peek_tail_const( ctx->scratch_tower );
     580           0 :   fd_hash_t const *       their_block_id  = &ctx->compact_tower_sync_serde.block_id;
     581           0 :   fd_hash_t const *       their_bank_hash = &ctx->compact_tower_sync_serde.hash;
     582             : 
     583             :   /* Return early if their last vote is too old. */
     584             : 
     585           0 :   if( FD_UNLIKELY( their_last_vote->slot <= ctx->root_slot ) ) { ctx->metrics.votes_too_old++; return; }
     586             : 
     587             :   /* Similar to what Agave does in cluster_info_vote_listener, we use
     588             :      the stake associated with a vote account as of our current root's
     589             :      epoch (which could potentially be a different epoch than the vote
     590             :      we are counting or when we observe the vote).  They default stake
     591             :      to 0 for voters who are not found. */
     592             : 
     593           0 :   fd_tower_stakes_vtr_xid_t xid = { .addr = *vote_acc, .slot = ctx->root_slot };
     594           0 :   fd_tower_stakes_vtr_t *   vtr = fd_tower_stakes_vtr_map_ele_query( ctx->tower_stakes->vtr_map, &xid, NULL, ctx->tower_stakes->vtr_pool );
     595           0 :   if( FD_UNLIKELY( !vtr ) ) return; /* voter is not staked in current root's epoch */
     596             : 
     597           0 :   ulong their_stake = vtr->stake;
     598           0 :   ulong total_stake = fd_ghost_root( ctx->ghost )->total_stake;
     599             : 
     600           0 :   int hfork_err = fd_hfork_count_vote( ctx->hfork, vote_acc, their_block_id, their_bank_hash, their_last_vote->slot, their_stake, total_stake );
     601           0 :   update_metrics_hfork( ctx, hfork_err, their_last_vote->slot, their_block_id );
     602             : 
     603           0 :   int votes_err = fd_votes_count_vote( ctx->votes, vote_acc, their_last_vote->slot, their_block_id );
     604           0 :   update_metrics_votes( ctx, votes_err );
     605           0 :   if( FD_LIKELY( votes_err==FD_VOTES_SUCCESS ) ) confirm_block( ctx, their_last_vote->slot, their_block_id, total_stake );
     606             : 
     607             :   /* Agave decides to count intermediate vote slots in the tower only if
     608             :      1. they've replayed the slot and 2. their replay bank hash matches
     609             :      the vote's bank hash.  We do the same thing, but using block_ids
     610             :      instead of bank hashes.
     611             : 
     612             :      It's possible we haven't yet replayed this slot being voted on
     613             :      because gossip votes can be ahead of our replay.
     614             : 
     615             :      https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L483-L487 */
     616             : 
     617           0 :   if( FD_UNLIKELY( !fd_tower_blk_query( ctx->tower_blocks->blk_map, their_last_vote->slot, NULL ) ) ) { ctx->metrics.votes_unknown_slot++; return; }; /* we haven't replayed this block yet */
     618           0 :   fd_hash_t const * our_block_id = fd_tower_blocks_canonical_block_id( ctx->tower_blocks, their_last_vote->slot );
     619           0 :   if( FD_UNLIKELY( 0!=memcmp( our_block_id, their_block_id, sizeof(fd_hash_t) ) ) ) { ctx->metrics.votes_unknown_block_id++; return; } /* we don't recognize this block id */
     620             : 
     621             :   /* At this point, we know we have replayed the same slot and also have
     622             :      a matching block id, so we can count the intermediate votes. */
     623             : 
     624           0 :   int skipped_last_vote = 0;
     625           0 :   for( fd_tower_iter_t iter = fd_tower_iter_init_rev( ctx->scratch_tower       );
     626           0 :                              !fd_tower_iter_done_rev( ctx->scratch_tower, iter );
     627           0 :                        iter = fd_tower_iter_prev    ( ctx->scratch_tower, iter ) ) {
     628           0 :     if( FD_UNLIKELY( !skipped_last_vote ) ) { skipped_last_vote = 1; continue; }
     629           0 :     fd_tower_vote_t const * their_intermediate_vote = fd_tower_iter_ele_const( ctx->scratch_tower, iter );
     630             : 
     631             :     /* If we don't recognize an intermediate vote slot in their tower,
     632             :        it means their tower either:
     633             : 
     634             :        1. Contains intermediate vote slots that are too old (older than
     635             :           our root) so we already pruned them for tower_forks.  Normally
     636             :           if the descendant (last vote slot) is in tower forks, then all
     637             :           of its ancestors should be in there too.
     638             : 
     639             :        2. Is invalid.  Even though at this point we have successfully
     640             :           sigverified and deserialized their vote txn, the tower itself
     641             :           might still be invalid because unlike TPU vote txns, we have
     642             :           not plumbed through the vote program, but obviously gossip
     643             :           votes do not so we need to do some light validation here.
     644             : 
     645             :        We could throwaway this voter's tower, but we handle it the same
     646             :        way as Agave which is to just skip this intermediate vote slot:
     647             : 
     648             :        https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L513-L518 */
     649             : 
     650           0 :     if( FD_UNLIKELY( their_intermediate_vote->slot <= ctx->root_slot ) ) { ctx->metrics.votes_too_old++; continue; }
     651             : 
     652           0 :     fd_tower_blk_t * tower_blk = fd_tower_blocks_query( ctx->tower_blocks, their_intermediate_vote->slot );
     653           0 :     if( FD_UNLIKELY( !tower_blk ) ) { ctx->metrics.votes_unknown_slot++; continue; }
     654             : 
     655             :     /* Otherwise, we count the vote using our own block id for that slot
     656             :        (again, mirroring what Agave does albeit with bank hashes).
     657             : 
     658             :        Agave uses the current root bank's total stake when counting vote
     659             :        txns from gossip / replay:
     660             : 
     661             :        https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L500 */
     662             : 
     663           0 :     fd_hash_t const * intermediate_block_id = fd_tower_blocks_canonical_block_id( ctx->tower_blocks, their_intermediate_vote->slot );
     664           0 :     int votes_err = fd_votes_count_vote( ctx->votes, vote_acc, their_intermediate_vote->slot, intermediate_block_id );
     665           0 :     update_metrics_votes( ctx, votes_err );
     666           0 :     if( FD_LIKELY( votes_err==FD_VOTES_SUCCESS ) ) confirm_block( ctx, their_intermediate_vote->slot, intermediate_block_id, total_stake );
     667           0 :   }
     668           0 : }
     669             : 
     670             : static int
     671             : deser_auth_vtr( fd_tower_tile_t * ctx,
     672             :                 ulong             epoch,
     673             :                 int               vote_acc_found,
     674             :                 fd_pubkey_t *     authority_out,
     675           0 :                 ulong *           authority_idx_out ) {
     676             : 
     677           0 :   if( FD_UNLIKELY( !vote_acc_found ) ) return 0;
     678             : 
     679           0 :   fd_bincode_decode_ctx_t decode_ctx = {
     680           0 :     .data    = ctx->our_vote_acct,
     681           0 :     .dataend = ctx->our_vote_acct + ctx->our_vote_acct_sz,
     682           0 :   };
     683             : 
     684           0 :   uchar __attribute__((aligned(FD_VOTE_STATE_VERSIONED_ALIGN))) vote_state_versioned[ FD_VOTE_STATE_VERSIONED_FOOTPRINT ];
     685             : 
     686           0 :   fd_vote_state_versioned_t * vsv = fd_vote_state_versioned_decode( vote_state_versioned, &decode_ctx );
     687           0 :   FD_CRIT( vsv, "unable to decode vote state versioned" );
     688             : 
     689           0 :   fd_pubkey_t const * auth_vtr_addr = NULL;
     690           0 :   switch( vsv->discriminant ) {
     691           0 :     case fd_vote_state_versioned_enum_v1_14_11:
     692           0 :       for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->inner.v1_14_11.authorized_voters.treap, vsv->inner.v1_14_11.authorized_voters.pool );
     693           0 :            !fd_vote_authorized_voters_treap_rev_iter_done( iter );
     694           0 :            iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->inner.v1_14_11.authorized_voters.pool ) ) {
     695           0 :         fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->inner.v1_14_11.authorized_voters.pool );
     696           0 :         if( FD_LIKELY( ele->epoch<=epoch ) ) {
     697           0 :           auth_vtr_addr = &ele->pubkey;
     698           0 :           break;
     699           0 :         }
     700           0 :       }
     701           0 :       break;
     702           0 :     case fd_vote_state_versioned_enum_v3:
     703           0 :       for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->inner.v3.authorized_voters.treap, vsv->inner.v3.authorized_voters.pool );
     704           0 :           !fd_vote_authorized_voters_treap_rev_iter_done( iter );
     705           0 :           iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->inner.v3.authorized_voters.pool ) ) {
     706           0 :         fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->inner.v3.authorized_voters.pool );
     707           0 :         if( FD_LIKELY( ele->epoch<=epoch ) ) {
     708           0 :           auth_vtr_addr = &ele->pubkey;
     709           0 :           break;
     710           0 :         }
     711           0 :       }
     712           0 :       break;
     713           0 :     case fd_vote_state_versioned_enum_v4:
     714           0 :       for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->inner.v4.authorized_voters.treap, vsv->inner.v4.authorized_voters.pool );
     715           0 :           !fd_vote_authorized_voters_treap_rev_iter_done( iter );
     716           0 :           iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->inner.v4.authorized_voters.pool ) ) {
     717           0 :         fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->inner.v4.authorized_voters.pool );
     718           0 :         if( FD_LIKELY( ele->epoch<=epoch ) ) {
     719           0 :           auth_vtr_addr = &ele->pubkey;
     720           0 :           break;
     721           0 :         }
     722           0 :       }
     723           0 :       break;
     724           0 :     default:
     725           0 :       FD_LOG_CRIT(( "unsupported vote state versioned discriminant: %u", vsv->discriminant ));
     726           0 :   }
     727             : 
     728           0 :   FD_CRIT( auth_vtr_addr, "unable to find authorized voter, likely corrupt vote account state" );
     729             : 
     730           0 :   if( fd_pubkey_eq( auth_vtr_addr, ctx->identity_key ) ) {
     731           0 :     *authority_idx_out = ULONG_MAX;
     732           0 :     *authority_out     = *auth_vtr_addr;
     733           0 :     return 1;
     734           0 :   }
     735             : 
     736           0 :   auth_vtr_t * auth_vtr = auth_vtr_query( ctx->auth_vtr, *auth_vtr_addr, NULL );
     737           0 :   if( FD_LIKELY( auth_vtr ) ) {
     738           0 :     *authority_idx_out = auth_vtr->paths_idx;
     739           0 :     *authority_out     = *auth_vtr_addr;
     740           0 :     return 1;
     741           0 :   }
     742             : 
     743           0 :   return 0;
     744           0 : }
     745             : 
     746             : static ulong
     747             : update_voters( fd_tower_tile_t * ctx,
     748             :                ulong             bank_idx,
     749           0 :                ulong             slot ) {
     750             : 
     751           0 :   fd_bank_t * bank = fd_banks_bank_query( ctx->banks, bank_idx );
     752           0 :   if( FD_UNLIKELY( !bank ) ) FD_LOG_CRIT(( "invariant violation: bank %lu is missing", bank_idx ));
     753             : 
     754           0 :   fd_tower_voters_t * tower_voters = ctx->tower_voters;
     755           0 :   fd_tower_voters_remove_all( tower_voters );
     756             : 
     757           0 :   ulong total_stake    = 0UL;
     758           0 :   ulong prev_voter_idx = ULONG_MAX;
     759             : 
     760           0 :   fd_accdb_ro_pipe_t ro_pipe[1];
     761           0 :   fd_funk_txn_xid_t  xid = { .ul = { slot, bank_idx } };
     762           0 :   fd_accdb_ro_pipe_init( ro_pipe, ctx->accdb, &xid );
     763             : 
     764           0 :   fd_top_votes_t const * top_votes_t_2 = fd_bank_top_votes_t_2_query( bank );
     765           0 :   uchar __attribute__((aligned(FD_TOP_VOTES_ITER_ALIGN))) iter_mem[ FD_TOP_VOTES_ITER_FOOTPRINT ];
     766             : 
     767           0 :   ulong pending_cnt = 0UL;
     768           0 :   fd_top_votes_iter_t * iter = fd_top_votes_iter_init( top_votes_t_2, iter_mem );
     769           0 :   for(;;) {
     770           0 :     if( FD_UNLIKELY( fd_top_votes_iter_done( top_votes_t_2, iter ) ) ) {
     771           0 :       if( !pending_cnt ) break;
     772           0 :       fd_accdb_ro_pipe_flush( ro_pipe );
     773           0 :     } else {
     774           0 :       fd_pubkey_t vote_acc;
     775           0 :       ulong       stake;
     776           0 :       int         is_valid = fd_top_votes_iter_ele( top_votes_t_2, iter, &vote_acc, NULL, &stake, NULL, NULL, NULL );
     777           0 :       fd_top_votes_iter_next( top_votes_t_2, iter );
     778           0 :       if( FD_UNLIKELY( !is_valid ) ) continue;
     779             : 
     780           0 :       fd_accdb_ro_pipe_enqueue( ro_pipe, vote_acc.key );
     781           0 :       pending_cnt++;
     782           0 :     }
     783             : 
     784           0 :     fd_accdb_ro_t * ro;
     785           0 :     while( FD_LIKELY( ro = fd_accdb_ro_pipe_poll( ro_pipe ) ) ) {
     786           0 :       pending_cnt--;
     787           0 :       fd_pubkey_t const * vote_acc = fd_accdb_ref_address( ro );
     788             : 
     789           0 :       ulong stake;
     790           0 :       int   is_valid = fd_top_votes_query( top_votes_t_2, vote_acc, NULL, &stake, NULL, NULL, NULL );
     791           0 :       if( FD_UNLIKELY( !is_valid ) ) continue;
     792             : 
     793           0 :       FD_TEST( fd_tower_voters_cnt( tower_voters )<=VTR_MAX );
     794           0 :       FD_TEST( fd_accdb_ref_lamports( ro ) && fd_vsv_is_correct_size_and_initialized( ro->meta ) );
     795             : 
     796           0 :       total_stake += stake;
     797             : 
     798           0 :       fd_tower_voters_t * acct = fd_tower_voters_push_tail_nocopy( tower_voters );
     799           0 :       fd_tower_remove_all( acct->tower );
     800           0 :       fd_tower_from_vote_acc( acct->tower, &acct->root, fd_accdb_ref_data_const( ro ) );
     801           0 :       acct->id_key   = fd_vsv_get_node_account( fd_accdb_ref_data_const( ro ) );
     802           0 :       acct->vote_acc = *vote_acc;
     803           0 :       acct->stake    = stake;
     804           0 :       prev_voter_idx = fd_tower_stakes_insert( ctx->tower_stakes, slot, vote_acc, stake, prev_voter_idx );
     805           0 :       }
     806           0 :   }
     807             : 
     808           0 :   fd_accdb_ro_pipe_fini( ro_pipe );
     809             : 
     810           0 :   return total_stake;
     811           0 : }
     812             : 
     813             : static void
     814             : replay_slot_completed( fd_tower_tile_t *            ctx,
     815             :                        fd_replay_slot_completed_t * slot_completed,
     816             :                        ulong                        tsorig,
     817           0 :                        fd_stem_context_t *          stem ) {
     818             : 
     819             :   /* Sanity checks. */
     820             : 
     821           0 :   FD_TEST( 0!=memcmp( &slot_completed->block_id, &hash_null, sizeof(fd_hash_t) ) );
     822           0 :   FD_TEST( ctx->init_slot==ULONG_MAX || 0!=memcmp( &slot_completed->block_id, &hash_null, sizeof(fd_hash_t) ) );
     823             : 
     824             :   /* Update voters. */
     825             : 
     826           0 :   fd_tower_stakes_remove( ctx->tower_stakes, slot_completed->slot ); /* no-op for 99% of cases except for eqvoc */
     827           0 :   ulong total_stake = update_voters( ctx, slot_completed->bank_idx, slot_completed->slot );
     828             : 
     829             :   /* The first replay_slot_completed is always either the snapshot slot
     830             :      or genesis slot, which we use to initialize our slot and
     831             :      epoch-related metadata. */
     832             : 
     833           0 :   if( FD_UNLIKELY( ctx->init_slot==ULONG_MAX ) ) {
     834           0 :     ctx->init_slot = slot_completed->slot;
     835           0 :     ctx->root_slot = slot_completed->slot;
     836           0 :     fd_votes_publish( ctx->votes, slot_completed->slot );
     837           0 :     fd_votes_update_voters( ctx->votes, ctx->tower_voters, ctx->tower_stakes, slot_completed->slot );
     838           0 :     fd_eqvoc_update_voters( ctx->eqvoc, ctx->tower_voters );
     839           0 :     fd_hfork_update_voters( ctx->hfork, ctx->tower_voters );
     840           0 :   }
     841             : 
     842             :   /* Due to asynchronous frag processing, it's possible this block from
     843             :      replay_slot_completed is on a minority fork Tower already pruned
     844             :      after publishing a new root. */
     845             : 
     846           0 :   if( FD_UNLIKELY( slot_completed->slot!=ctx->init_slot && !fd_ghost_query( ctx->ghost, &slot_completed->parent_block_id ) ) ) {
     847           0 :     ctx->metrics.ignored_cnt++;
     848           0 :     ctx->metrics.ignored_slot = slot_completed->slot;
     849             : 
     850             :     /* Still need to return a message to replay so the refcnt on the
     851             :        bank is decremented. */
     852             : 
     853           0 :     fd_tower_slot_ignored_t * msg = fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
     854           0 :     msg->slot     = slot_completed->slot;
     855           0 :     msg->bank_idx = slot_completed->bank_idx;
     856             : 
     857           0 :     fd_stem_publish( stem, OUT_IDX, FD_TOWER_SIG_SLOT_IGNORED, ctx->out_chunk, sizeof(fd_tower_slot_ignored_t), 0UL, tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
     858           0 :     ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_slot_ignored_t), ctx->out_chunk0, ctx->out_wmark );
     859           0 :     ctx->out_seq   = stem->seqs[ OUT_IDX ];
     860           0 :     return;
     861           0 :   }
     862             : 
     863             :   /* Reconcile our local tower with the on-chain tower (stored inside
     864             :      our vote account). */
     865             : 
     866           0 :   ulong our_vote_acct_bal = ULONG_MAX;
     867           0 :   int found = 0;
     868           0 :   fd_funk_txn_xid_t xid = { .ul = { slot_completed->slot, slot_completed->bank_idx } };
     869           0 :   fd_accdb_ro_t ro[1];
     870           0 :   if( FD_LIKELY( fd_accdb_open_ro( ctx->accdb, ro, &xid, ctx->vote_account ) ) ) {
     871           0 :     found = 1;
     872           0 :     ctx->our_vote_acct_sz = fd_ulong_min( fd_accdb_ref_data_sz( ro ), FD_VOTE_STATE_DATA_MAX );
     873           0 :     our_vote_acct_bal = fd_accdb_ref_lamports( ro );
     874           0 :     fd_memcpy( ctx->our_vote_acct, fd_accdb_ref_data_const( ro ), ctx->our_vote_acct_sz );
     875           0 :     fd_accdb_close_ro( ctx->accdb, ro );
     876           0 :     fd_tower_reconcile( ctx->tower, ctx->root_slot, ctx->our_vote_acct, ctx->tower_blocks );
     877           0 :   }
     878             : 
     879             :   /* Check for equivocation (already received a replay_slot_completed
     880             :      for this slot). */
     881             : 
     882           0 :   fd_tower_blk_t * eqvoc_tower_blk = NULL;
     883           0 :   if( FD_UNLIKELY( eqvoc_tower_blk = fd_tower_blocks_query( ctx->tower_blocks, slot_completed->slot ) ) ) {
     884             : 
     885             :     /* As fd_replay_slot_completed guarantees, we process at most 2
     886             :        equivocating blocks for a given slot.  fd_tower_{...} only stores
     887             :        one version of a block (by design, given the tower protocol's
     888             :        limitations ... see notes in fd_tower_blocks.h).  We also know
     889             :        when seeing the same slot a second time that second block is
     890             :        confirmed, also guaranteed by fd_replay_slot_completed ... see
     891             :        notes in fd_replay_tile.h).
     892             : 
     893             :        So we retain the existing tower_block we have (which contains the
     894             :        first voted_block_id if we did indeed vote for it).  We update
     895             :        the replayed_block_id to the newer replayed version, and also
     896             :        update the parent if it is different.  We clear out the slot from
     897             :        the other tower adjacent structures, and re-insert into them with
     898             :        the confirmed version of the slot. */
     899             : 
     900           0 :     FD_TEST( eqvoc_tower_blk->confirmed ); /* check the confirmed bit is set (second replay_slot_completed version must be confirmed) */
     901           0 :     fd_tower_lockos_remove( ctx->tower_lockos, slot_completed->slot );
     902           0 :     eqvoc_tower_blk->parent_slot       = slot_completed->parent_slot;
     903           0 :     eqvoc_tower_blk->replayed_block_id = slot_completed->block_id;
     904             : 
     905           0 :     ctx->metrics.eqvoc_cnt++;
     906           0 :     ctx->metrics.eqvoc_slot = slot_completed->slot;
     907           0 :   } else {
     908             : 
     909             :     /* Otherwise this is the first replay of this block, so insert a new
     910             :        tower_blk. */
     911             : 
     912           0 :     fd_tower_blk_t * tower_blk   = fd_tower_blocks_insert( ctx->tower_blocks, slot_completed->slot, slot_completed->parent_slot );
     913           0 :     tower_blk->parent_slot       = slot_completed->parent_slot;
     914           0 :     tower_blk->epoch             = slot_completed->epoch;
     915           0 :     tower_blk->replayed          = 1;
     916           0 :     tower_blk->replayed_block_id = slot_completed->block_id;
     917           0 :     tower_blk->voted             = 0;
     918           0 :     tower_blk->confirmed         = 0;
     919           0 :     tower_blk->leader            = slot_completed->is_leader;
     920           0 :     tower_blk->propagated        = 0;
     921             : 
     922             :     /* Set the prev_leader_slot. */
     923             : 
     924           0 :     if( FD_UNLIKELY( tower_blk->leader ) ) {
     925           0 :       tower_blk->prev_leader_slot = slot_completed->parent_slot;
     926           0 :     } else if ( FD_UNLIKELY( ctx->init_slot==slot_completed->slot ) ) {
     927           0 :       tower_blk->prev_leader_slot = ULONG_MAX;
     928           0 :     } else {
     929           0 :       fd_tower_blk_t * parent_tower_blk = fd_tower_blocks_query( ctx->tower_blocks, slot_completed->parent_slot );
     930           0 :       FD_TEST( parent_tower_blk );
     931           0 :       tower_blk->prev_leader_slot = parent_tower_blk->prev_leader_slot;
     932           0 :     }
     933           0 :   }
     934             : 
     935             :   /* Insert into ghost */
     936             : 
     937           0 :   fd_ghost_blk_t * ghost_blk = fd_ghost_insert( ctx->ghost, &slot_completed->block_id, fd_ptr_if( slot_completed->slot!=ctx->init_slot, &slot_completed->parent_block_id, NULL ), slot_completed->slot );
     938           0 :   ghost_blk->total_stake = total_stake;
     939             : 
     940             :   /* Insert into hard fork detector. */
     941             : 
     942           0 :   fd_hfork_blk_t * hfork_blk = fd_hfork_record_our_bank_hash( ctx->hfork, &slot_completed->block_id, &slot_completed->bank_hash, fd_ghost_root( ctx->ghost )->total_stake );
     943           0 :   update_metrics_hfork( ctx, hfork_blk->flag, slot_completed->slot, &slot_completed->block_id );
     944             : 
     945           0 :   fd_tower_voters_t * tower_voters = ctx->tower_voters;
     946           0 :   for( fd_tower_voters_iter_t iter = fd_tower_voters_iter_init( tower_voters );
     947           0 :                                     !fd_tower_voters_iter_done( tower_voters, iter );
     948           0 :                               iter = fd_tower_voters_iter_next( tower_voters, iter ) ) {
     949           0 :     fd_tower_voters_t * acct = fd_tower_voters_iter_ele( tower_voters, iter );
     950             : 
     951             :     /* 1. Update forks with lockouts. */
     952             : 
     953           0 :     fd_tower_lockos_insert( ctx->tower_lockos, slot_completed->slot, &acct->vote_acc, acct );
     954             : 
     955             :     /* 2. Count the last vote slot in the vote state towards ghost. */
     956             : 
     957           0 :     ulong vote_slot = fd_tower_empty( acct->tower ) ? ULONG_MAX : fd_tower_peek_tail_const( acct->tower )->slot;
     958           0 :     if( FD_LIKELY( vote_slot!=ULONG_MAX && /* has voted */
     959           0 :                    vote_slot>=fd_ghost_root( ctx->ghost )->slot ) ) { /* vote not too old */
     960             : 
     961             :       /* We search up the ghost ancestry to find the ghost block for
     962             :          this vote slot.  In Agave, they look this value up using a
     963             :          hashmap of slot->bank hash ("fork progress"), but that approach
     964             :          only works because they dump and repair (so there's only ever
     965             :          one canonical bank hash).  We retain multiple block ids, both
     966             :          the original and confirmed one. */
     967             : 
     968           0 :       fd_ghost_blk_t * ancestor_blk = fd_ghost_slot_ancestor( ctx->ghost, ghost_blk, vote_slot ); /* FIXME potentially slow */
     969             : 
     970             :       /* It is impossible for ancestor_blk to be missing, because
     971             :           these are vote accounts on a given fork, not vote txns across
     972             :           forks.  So we know these towers must contain slots we know
     973             :           about as long as they are >= root, which we checked above. */
     974             : 
     975           0 :       if( FD_UNLIKELY( !ancestor_blk ) ) {
     976           0 :         FD_BASE58_ENCODE_32_BYTES( acct->vote_acc.key, pubkey_b58 );
     977           0 :         FD_LOG_CRIT(( "missing ancestor. replay slot %lu vote slot %lu voter %s", slot_completed->slot, vote_slot, pubkey_b58 ));
     978           0 :       }
     979             : 
     980           0 :       int ghost_err = fd_ghost_count_vote( ctx->ghost, ancestor_blk, &acct->vote_acc, acct->stake, vote_slot );
     981           0 :       update_metrics_ghost( ctx, ghost_err );
     982           0 :     }
     983           0 :   }
     984             : 
     985             :   /* Determine reset, vote, and root slots.  There may not be a vote or
     986             :      root slot but there is always a reset slot. */
     987             : 
     988           0 :   fd_tower_out_t out = fd_tower_vote_and_reset( ctx->tower, ctx->tower_blocks, ctx->tower_lockos, ctx->tower_stakes, ctx->tower_voters, ctx->ghost, ctx->votes );
     989             : 
     990             :   /* Update forks if there is a vote slot. */
     991             : 
     992           0 :   if( FD_LIKELY( out.vote_slot!=ULONG_MAX ) ) {
     993             : 
     994             :     /* We might not be voting for the current replay slot because the
     995             :        best_blk (according to ghost) could be another fork. */
     996             : 
     997           0 :     fd_tower_blk_t * vote_tower_blk = fd_tower_blocks_query( ctx->tower_blocks, out.vote_slot );
     998           0 :     vote_tower_blk->voted           = 1;
     999           0 :     vote_tower_blk->voted_block_id  = out.vote_block_id;
    1000           0 :   }
    1001             : 
    1002             :   /* Publish structures if there is a new root. */
    1003             : 
    1004           0 :   if( FD_UNLIKELY( out.root_slot!=ULONG_MAX ) ) {
    1005           0 :     if( FD_UNLIKELY( 0==memcmp( &out.root_block_id, &hash_null, sizeof(fd_hash_t) ) ) ) {
    1006           0 :       FD_LOG_CRIT(( "invariant violation: root block id is null at slot %lu", out.root_slot ));
    1007           0 :     }
    1008             : 
    1009           0 :     fd_tower_blk_t * oldr_tower_blk = fd_tower_blocks_query( ctx->tower_blocks, ctx->root_slot );
    1010           0 :     fd_tower_blk_t * newr_tower_blk = fd_tower_blocks_query( ctx->tower_blocks, out.root_slot );
    1011           0 :     FD_TEST( oldr_tower_blk );
    1012           0 :     FD_TEST( newr_tower_blk );
    1013             : 
    1014             :     /* It is a Solana consensus protocol invariant that a validator must
    1015             :        make at least one root in an epoch, so the root's epoch cannot
    1016             :        advance by more than one.  */
    1017             : 
    1018           0 :     FD_TEST( oldr_tower_blk->epoch==newr_tower_blk->epoch || oldr_tower_blk->epoch+1==newr_tower_blk->epoch  ); /* root can only move forward one epoch */
    1019             : 
    1020             :     /* Publish votes: 1. reindex if it's a new epoch. 2. publish the new
    1021             :        root to votes. */
    1022             : 
    1023           0 :     if( FD_UNLIKELY( oldr_tower_blk->epoch+1==newr_tower_blk->epoch ) ) {
    1024           0 :       FD_TEST( newr_tower_blk->epoch==slot_completed->epoch ); /* new root's epoch must be same as current slot_completed */
    1025           0 :       fd_votes_update_voters( ctx->votes, ctx->tower_voters, ctx->tower_stakes, out.root_slot );
    1026           0 :       fd_eqvoc_update_voters( ctx->eqvoc, ctx->tower_voters );
    1027           0 :       fd_hfork_update_voters( ctx->hfork, ctx->tower_voters );
    1028           0 :     }
    1029           0 :     fd_votes_publish( ctx->votes, out.root_slot );
    1030             : 
    1031             :     /* Publish tower_blocks and tower_stakes by removing any entries
    1032             :        older than the new root. */
    1033             : 
    1034           0 :     for( ulong slot = ctx->root_slot; slot < out.root_slot; slot++ ) {
    1035           0 :       fd_tower_blocks_remove( ctx->tower_blocks, slot );
    1036           0 :       fd_tower_lockos_remove( ctx->tower_lockos, slot );
    1037           0 :       fd_tower_stakes_remove( ctx->tower_stakes, slot );
    1038           0 :     }
    1039             : 
    1040             :     /* Publish roots by walking up the ghost ancestry to publish new root
    1041             :        frags for intermediate slots we couldn't vote for. */
    1042             : 
    1043           0 :     fd_ghost_blk_t * newr = fd_ghost_query( ctx->ghost, &out.root_block_id );
    1044           0 :     fd_ghost_blk_t * oldr = fd_ghost_root( ctx->ghost );
    1045             : 
    1046             :     /* oldr is not guaranteed to be the immediate parent of newr, but is
    1047             :        rather an arbitrary ancestor.  This can happen if we couldn't
    1048             :        vote for those intermediate slot(s).  We publish those slots as
    1049             :        intermediate roots. */
    1050             : 
    1051           0 :     fd_ghost_blk_t * intr = newr;
    1052           0 :     while( FD_LIKELY( intr!=oldr ) ) {
    1053           0 :       publishes_push_head( ctx->publishes, (publish_t){ .sig = FD_TOWER_SIG_SLOT_ROOTED, .msg = { .slot_rooted = { .slot = intr->slot, .block_id = intr->id } } } );
    1054           0 :       intr = fd_ghost_parent( ctx->ghost, intr );
    1055           0 :     }
    1056             : 
    1057             :     /* Publish ghost. */
    1058             : 
    1059           0 :     fd_ghost_publish( ctx->ghost, newr );
    1060             : 
    1061             :     /* Update the new root. */
    1062             : 
    1063           0 :     ctx->root_slot = out.root_slot;
    1064           0 :   }
    1065             : 
    1066             :   /* Publish a slot_done frag to tower_out. */
    1067             : 
    1068           0 :   fd_tower_slot_done_t * msg = fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
    1069           0 :   msg->replay_slot           = slot_completed->slot;
    1070           0 :   msg->active_fork_cnt       = fd_ghost_width( ctx->ghost );
    1071           0 :   msg->vote_slot             = out.vote_slot;
    1072           0 :   msg->reset_slot            = out.reset_slot;
    1073           0 :   msg->reset_block_id        = out.reset_block_id;
    1074           0 :   msg->root_slot             = out.root_slot;
    1075           0 :   msg->root_block_id         = out.root_block_id;
    1076           0 :   msg->replay_bank_idx       = slot_completed->bank_idx;
    1077           0 :   msg->vote_acct_bal         = our_vote_acct_bal;
    1078             : 
    1079             :   /* Populate slot_done with a vote txn representing our current tower
    1080             :      (regardless of whether there was a new vote slot or not).
    1081             : 
    1082             :      TODO only do this on refresh_last_vote? */
    1083             : 
    1084           0 :   ulong       authority_idx = ULONG_MAX;
    1085           0 :   fd_pubkey_t authority[1];
    1086           0 :   int         found_authority = deser_auth_vtr( ctx, slot_completed->epoch, found, authority, &authority_idx );
    1087             : 
    1088           0 :   if( FD_LIKELY( found_authority ) ) {
    1089           0 :     msg->has_vote_txn = 1;
    1090           0 :     fd_txn_p_t          txn[1];
    1091           0 :     fd_tower_to_vote_txn( ctx->tower, ctx->root_slot, &slot_completed->bank_hash, &slot_completed->block_id, &slot_completed->block_hash, ctx->identity_key, authority, ctx->vote_account, txn );
    1092           0 :     FD_TEST( !fd_tower_empty( ctx->tower ) );
    1093           0 :     FD_TEST( txn->payload_sz && txn->payload_sz<=FD_TPU_MTU );
    1094           0 :     fd_memcpy( msg->vote_txn, txn->payload, txn->payload_sz );
    1095           0 :     msg->vote_txn_sz   = txn->payload_sz;
    1096           0 :     msg->authority_idx = authority_idx;
    1097           0 :   } else {
    1098           0 :     msg->has_vote_txn = 0;
    1099           0 :   }
    1100             : 
    1101           0 :   msg->tower_cnt = 0UL;
    1102           0 :   if( FD_LIKELY( found ) ) msg->tower_cnt = fd_tower_with_lat_from_vote_acc( msg->tower, ctx->our_vote_acct );
    1103             : 
    1104           0 :   fd_stem_publish( stem, OUT_IDX, FD_TOWER_SIG_SLOT_DONE, ctx->out_chunk, sizeof(fd_tower_slot_done_t), 0UL, tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
    1105           0 :   ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_slot_done_t), ctx->out_chunk0, ctx->out_wmark );
    1106           0 :   ctx->out_seq   = stem->seqs[ OUT_IDX ];
    1107             : 
    1108             :   /* Write out metrics. */
    1109             : 
    1110           0 :   ctx->metrics.replay_slot = slot_completed->slot;
    1111           0 :   if( FD_LIKELY( out.vote_slot!=ULONG_MAX ) ) ctx->metrics.last_vote_slot = out.vote_slot;
    1112           0 :   ctx->metrics.reset_slot  = out.reset_slot; /* always set */
    1113           0 :   ctx->metrics.root_slot   = ctx->root_slot;
    1114           0 :   ctx->metrics.init_slot   = ctx->init_slot;
    1115             : 
    1116           0 :   ctx->metrics.ancestor_rollback += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_ANCESTOR_ROLLBACK );
    1117           0 :   ctx->metrics.sibling_confirmed += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SIBLING_CONFIRMED );
    1118           0 :   ctx->metrics.same_fork         += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SAME_FORK         );
    1119           0 :   ctx->metrics.switch_pass       += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SWITCH_PASS       );
    1120           0 :   ctx->metrics.switch_fail       += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SWITCH_FAIL       );
    1121           0 :   ctx->metrics.lockout_fail      += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_LOCKOUT_FAIL      );
    1122           0 :   ctx->metrics.threshold_fail    += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_THRESHOLD_FAIL    );
    1123           0 :   ctx->metrics.propagated_fail   += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_PROPAGATED_FAIL   );
    1124             : 
    1125             :   /* Log out structures. */
    1126             : 
    1127           0 :   char cstr[4096]; ulong cstr_sz;
    1128           0 :   FD_LOG_DEBUG(( "\n\n%s", fd_ghost_to_cstr( ctx->ghost, fd_ghost_root( ctx->ghost ), cstr, sizeof(cstr), &cstr_sz ) ));
    1129           0 :   FD_LOG_DEBUG(( "\n\n%s", fd_tower_to_cstr( ctx->tower, ctx->root_slot, cstr ) ));
    1130           0 : }
    1131             : 
    1132             : FD_FN_CONST static inline ulong
    1133           0 : scratch_align( void ) {
    1134           0 :   return 128UL;
    1135           0 : }
    1136             : 
    1137             : FD_FN_PURE static inline ulong
    1138           0 : scratch_footprint( fd_topo_tile_t const * tile ) {
    1139           0 :   ulong slot_max    = fd_ulong_pow2_up( tile->tower.max_live_slots );
    1140           0 :   ulong per_vtr_max = PER_VTR_MAX;
    1141           0 :   ulong vtr_max     = fd_ulong_pow2_up( VTR_MAX );
    1142           0 :   ulong blk_max     = slot_max * EQVOC_MAX;
    1143           0 :   ulong fec_max     = slot_max * FD_SHRED_BLK_MAX / FD_FEC_SHRED_CNT;
    1144           0 :   ulong pub_max     = slot_max * FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT;
    1145             : 
    1146           0 :   ulong l = FD_LAYOUT_INIT;
    1147           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t)                                       );
    1148           0 :   l = FD_LAYOUT_APPEND( l, auth_vtr_align(),         auth_vtr_footprint()                                          );
    1149             :   /* auth_vtr_keyswitch */
    1150           0 :   l = FD_LAYOUT_APPEND( l, fd_eqvoc_align(),         fd_eqvoc_footprint( slot_max, fec_max, per_vtr_max, vtr_max ) );
    1151           0 :   l = FD_LAYOUT_APPEND( l, fd_ghost_align(),         fd_ghost_footprint( blk_max, vtr_max )                        );
    1152           0 :   l = FD_LAYOUT_APPEND( l, fd_hfork_align(),         fd_hfork_footprint( per_vtr_max, vtr_max )                    );
    1153           0 :   l = FD_LAYOUT_APPEND( l, fd_votes_align(),         fd_votes_footprint( slot_max, vtr_max )          );
    1154           0 :   l = FD_LAYOUT_APPEND( l, fd_tower_align(),         fd_tower_footprint()                                          );
    1155           0 :   l = FD_LAYOUT_APPEND( l, fd_tower_align(),         fd_tower_footprint()                                          );
    1156           0 :   l = FD_LAYOUT_APPEND( l, fd_tower_blocks_align(),  fd_tower_blocks_footprint( slot_max )                         );
    1157           0 :   l = FD_LAYOUT_APPEND( l, fd_tower_lockos_align(),  fd_tower_lockos_footprint( slot_max, vtr_max )                );
    1158           0 :   l = FD_LAYOUT_APPEND( l, fd_tower_stakes_align(),  fd_tower_stakes_footprint( slot_max, vtr_max )                );
    1159           0 :   l = FD_LAYOUT_APPEND( l, fd_tower_voters_align(),  fd_tower_voters_footprint( vtr_max )                          );
    1160           0 :   l = FD_LAYOUT_APPEND( l, FD_TOWER_ALIGN,           FD_TOWER_FOOTPRINT * vtr_max                                  );
    1161           0 :   l = FD_LAYOUT_APPEND( l, publishes_align(),        publishes_footprint( pub_max )                                );
    1162           0 :   return FD_LAYOUT_FINI( l, scratch_align() );
    1163           0 : }
    1164             : 
    1165             : static void
    1166           0 : during_housekeeping( fd_tower_tile_t * ctx ) {
    1167           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->auth_vtr_keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
    1168           0 :     fd_keyswitch_state( ctx->auth_vtr_keyswitch, FD_KEYSWITCH_STATE_UNLOCKED );
    1169           0 :   }
    1170             : 
    1171           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->auth_vtr_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
    1172           0 :     fd_pubkey_t pubkey = *(fd_pubkey_t const *)fd_type_pun_const( ctx->auth_vtr_keyswitch->bytes );
    1173           0 :     if( FD_UNLIKELY( auth_vtr_query( ctx->auth_vtr, pubkey, NULL ) ) ) FD_LOG_CRIT(( "keyswitch: duplicate authorized voter key, keys not synced up with sign tile" ));
    1174           0 :     if( FD_UNLIKELY( ctx->auth_vtr_path_cnt==AUTH_VOTERS_MAX ) ) FD_LOG_CRIT(( "keyswitch: too many authorized voters, keys not synced up with sign tile" ));
    1175             : 
    1176           0 :     auth_vtr_t * auth_vtr = auth_vtr_insert( ctx->auth_vtr, pubkey );
    1177           0 :     auth_vtr->paths_idx = ctx->auth_vtr_path_cnt;
    1178           0 :     ctx->auth_vtr_path_cnt++;
    1179           0 :     fd_keyswitch_state( ctx->auth_vtr_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
    1180             : 
    1181           0 :   }
    1182             : 
    1183             :   /* FIXME: Currently, the tower tile doesn't support set-identity with
    1184             :      a tower file.  When support for a tower file is added, we need to
    1185             :      swap the file that is running and sync it to the local state of
    1186             :      the tower.  Because a tower file is not supported, if another
    1187             :      validator was running with the identity that was switched to, then
    1188             :      it is possible that the original validator and the fallback (this
    1189             :      node), may have tower files which are out of sync.  This could lead
    1190             :      to consensus violations such as double voting or duplicate
    1191             :      confirmations.  Currently it is unsafe for a validator operator to
    1192             :      switch identities without a 512 slot delay: the reason for this
    1193             :      delay is to account for the worst case number of slots a vote
    1194             :      account can be locked out for. */
    1195             : 
    1196           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->identity_keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
    1197           0 :     FD_LOG_DEBUG(( "keyswitch: unhalting signing" ));
    1198           0 :     FD_CRIT( ctx->halt_signing, "state machine corruption" );
    1199           0 :     ctx->halt_signing = 0;
    1200           0 :     fd_keyswitch_state( ctx->identity_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
    1201           0 :   }
    1202             : 
    1203           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->identity_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
    1204           0 :     FD_LOG_DEBUG(( "keyswitch: halting signing" ));
    1205           0 :     memcpy( ctx->identity_key, ctx->identity_keyswitch->bytes, 32UL );
    1206           0 :     fd_keyswitch_state( ctx->identity_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
    1207           0 :     ctx->halt_signing = 1;
    1208           0 :     ctx->identity_keyswitch->result  = ctx->out_seq;
    1209           0 :   }
    1210           0 : }
    1211             : 
    1212             : static inline void
    1213           0 : metrics_write( fd_tower_tile_t * ctx ) {
    1214           0 :   FD_MCNT_SET  ( TOWER, IGNORED_CNT,   ctx->metrics.ignored_cnt   );
    1215           0 :   FD_MGAUGE_SET( TOWER, IGNORED_SLOT,  ctx->metrics.ignored_slot  );
    1216             : 
    1217           0 :   FD_MGAUGE_SET( TOWER, REPLAY_SLOT,     ctx->metrics.replay_slot     );
    1218           0 :   FD_MGAUGE_SET( TOWER, VOTE_SLOT,       ctx->metrics.last_vote_slot  );
    1219           0 :   FD_MGAUGE_SET( TOWER, RESET_SLOT,      ctx->metrics.reset_slot      );
    1220           0 :   FD_MGAUGE_SET( TOWER, ROOT_SLOT,    ctx->metrics.root_slot    );
    1221           0 :   FD_MGAUGE_SET( TOWER, INIT_SLOT,    ctx->metrics.init_slot    );
    1222             : 
    1223           0 :   FD_MCNT_SET( TOWER, ANCESTOR_ROLLBACK, ctx->metrics.ancestor_rollback );
    1224           0 :   FD_MCNT_SET( TOWER, SIBLING_CONFIRMED, ctx->metrics.sibling_confirmed );
    1225           0 :   FD_MCNT_SET( TOWER, SAME_FORK,         ctx->metrics.same_fork         );
    1226           0 :   FD_MCNT_SET( TOWER, SWITCH_PASS,       ctx->metrics.switch_pass       );
    1227           0 :   FD_MCNT_SET( TOWER, SWITCH_FAIL,       ctx->metrics.switch_fail       );
    1228           0 :   FD_MCNT_SET( TOWER, LOCKOUT_FAIL,      ctx->metrics.lockout_fail      );
    1229           0 :   FD_MCNT_SET( TOWER, THRESHOLD_FAIL,    ctx->metrics.threshold_fail    );
    1230           0 :   FD_MCNT_SET( TOWER, PROPAGATED_FAIL,   ctx->metrics.propagated_fail   );
    1231             : 
    1232           0 :   FD_MCNT_SET( TOWER, TXN_BAD_DESER,         ctx->metrics.txn_bad_deser           );
    1233           0 :   FD_MCNT_SET( TOWER, TXN_BAD_TOWER,         ctx->metrics.txn_bad_tower           );
    1234           0 :   FD_MCNT_SET( TOWER, TXN_NOT_TOWER_SYNC,    ctx->metrics.txn_not_tower_sync      );
    1235           0 :   FD_MCNT_SET( TOWER, TXN_EMPTY_TOWER,       ctx->metrics.txn_empty_tower         );
    1236           0 :   FD_MCNT_SET( TOWER, TXN_NOT_READY,        ctx->metrics.txn_not_ready           );
    1237             : 
    1238           0 :   FD_MCNT_SET( TOWER, VOTES_TOO_OLD,         ctx->metrics.votes_too_old           );
    1239           0 :   FD_MCNT_SET( TOWER, VOTES_TOO_NEW,         ctx->metrics.votes_too_new           );
    1240           0 :   FD_MCNT_SET( TOWER, VOTES_UNKNOWN_VTR,     ctx->metrics.votes_unknown_vtr       );
    1241           0 :   FD_MCNT_SET( TOWER, VOTES_ALREADY_VOTED,   ctx->metrics.votes_already_voted     );
    1242           0 :   FD_MCNT_SET( TOWER, VOTES_UNKNOWN_SLOT,    ctx->metrics.votes_unknown_slot      );
    1243           0 :   FD_MCNT_SET( TOWER, VOTES_UNKNOWN_BLOCK_ID, ctx->metrics.votes_unknown_block_id );
    1244             : 
    1245           0 :   FD_MCNT_SET( TOWER, EQVOC_SUCCESS_MERKLE,  ctx->metrics.eqvoc_success_merkle  );
    1246           0 :   FD_MCNT_SET( TOWER, EQVOC_SUCCESS_META,    ctx->metrics.eqvoc_success_meta    );
    1247           0 :   FD_MCNT_SET( TOWER, EQVOC_SUCCESS_LAST,    ctx->metrics.eqvoc_success_last    );
    1248           0 :   FD_MCNT_SET( TOWER, EQVOC_SUCCESS_OVERLAP, ctx->metrics.eqvoc_success_overlap );
    1249           0 :   FD_MCNT_SET( TOWER, EQVOC_SUCCESS_CHAINED, ctx->metrics.eqvoc_success_chained );
    1250             : 
    1251           0 :   FD_MCNT_SET( TOWER, EQVOC_ERR_SERDE,     ctx->metrics.eqvoc_err_serde     );
    1252           0 :   FD_MCNT_SET( TOWER, EQVOC_ERR_SLOT,      ctx->metrics.eqvoc_err_slot      );
    1253           0 :   FD_MCNT_SET( TOWER, EQVOC_ERR_VERSION,   ctx->metrics.eqvoc_err_version   );
    1254           0 :   FD_MCNT_SET( TOWER, EQVOC_ERR_TYPE,      ctx->metrics.eqvoc_err_type      );
    1255           0 :   FD_MCNT_SET( TOWER, EQVOC_ERR_MERKLE,    ctx->metrics.eqvoc_err_merkle    );
    1256           0 :   FD_MCNT_SET( TOWER, EQVOC_ERR_SIGNATURE, ctx->metrics.eqvoc_err_signature );
    1257             : 
    1258           0 :   FD_MCNT_SET( TOWER, EQVOC_ERR_CHUNK_CNT, ctx->metrics.eqvoc_err_chunk_cnt );
    1259           0 :   FD_MCNT_SET( TOWER, EQVOC_ERR_CHUNK_IDX, ctx->metrics.eqvoc_err_chunk_idx );
    1260           0 :   FD_MCNT_SET( TOWER, EQVOC_ERR_CHUNK_LEN, ctx->metrics.eqvoc_err_chunk_len );
    1261             : 
    1262           0 :   FD_MCNT_SET( TOWER, EQVOC_ERR_IGNORED_FROM, ctx->metrics.eqvoc_err_ignored_from );
    1263           0 :   FD_MCNT_SET( TOWER, EQVOC_ERR_IGNORED_SLOT, ctx->metrics.eqvoc_err_ignored_slot );
    1264             : 
    1265           0 :   FD_MCNT_SET( TOWER, EQVOC_PROOF_CONSTRUCTED, ctx->metrics.eqvoc_proof_constructed );
    1266           0 :   FD_MCNT_SET( TOWER, EQVOC_PROOF_VERIFIED,    ctx->metrics.eqvoc_proof_verified    );
    1267             : 
    1268           0 :   FD_MCNT_SET( TOWER, GHOST_NOT_VOTED,      ctx->metrics.ghost_not_voted      );
    1269           0 :   FD_MCNT_SET( TOWER, GHOST_TOO_OLD,        ctx->metrics.ghost_too_old        );
    1270           0 :   FD_MCNT_SET( TOWER, GHOST_ALREADY_VOTED,  ctx->metrics.ghost_already_voted  );
    1271             : 
    1272           0 :   FD_MCNT_SET( TOWER, HFORK_UNKNOWN_VTR,    ctx->metrics.hfork_unknown_vtr    );
    1273           0 :   FD_MCNT_SET( TOWER, HFORK_ALREADY_VOTED,  ctx->metrics.hfork_already_voted  );
    1274           0 :   FD_MCNT_SET( TOWER, HFORK_TOO_OLD,        ctx->metrics.hfork_too_old        );
    1275             : 
    1276           0 :   FD_MGAUGE_SET( TOWER, HFORK_MATCHED_SLOT,    ctx->metrics.hfork_matched_slot    );
    1277           0 :   FD_MGAUGE_SET( TOWER, HFORK_MISMATCHED_SLOT, ctx->metrics.hfork_mismatched_slot );
    1278           0 : }
    1279             : 
    1280             : static inline void
    1281             : after_credit( fd_tower_tile_t *   ctx,
    1282             :               fd_stem_context_t * stem,
    1283             :               int *               opt_poll_in,
    1284           0 :               int *               charge_busy ) {
    1285           0 :   if( FD_LIKELY( !publishes_empty( ctx->publishes ) ) ) {
    1286           0 :     publish_t * pub = publishes_pop_head_nocopy( ctx->publishes );
    1287           0 :     memcpy( fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk ), &pub->msg, sizeof(fd_tower_msg_t) );
    1288           0 :     fd_stem_publish( stem, OUT_IDX, pub->sig, ctx->out_chunk, sizeof(fd_tower_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
    1289           0 :     ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_msg_t), ctx->out_chunk0, ctx->out_wmark );
    1290           0 :     ctx->out_seq   = stem->seqs[ OUT_IDX ];
    1291           0 :     *opt_poll_in = 0; /* drain the publishes */
    1292           0 :     *charge_busy = 1;
    1293           0 :   }
    1294           0 : }
    1295             : 
    1296             : static inline int
    1297             : returnable_frag( fd_tower_tile_t *   ctx,
    1298             :                  ulong               in_idx,
    1299             :                  ulong               seq FD_PARAM_UNUSED,
    1300             :                  ulong               sig,
    1301             :                  ulong               chunk,
    1302             :                  ulong               sz,
    1303             :                  ulong               ctl FD_PARAM_UNUSED,
    1304             :                  ulong               tsorig,
    1305             :                  ulong               tspub FD_PARAM_UNUSED,
    1306           0 :                  fd_stem_context_t * stem ) {
    1307             : 
    1308           0 :   if( FD_UNLIKELY( !ctx->in[ in_idx ].mcache_only && ( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) ) )
    1309           0 :     FD_LOG_ERR(( "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 ));
    1310             : 
    1311           0 :   switch( ctx->in_kind[ in_idx ] ) {
    1312           0 :   case IN_KIND_DEDUP: {
    1313           0 :     if( FD_UNLIKELY( ctx->root_slot==ULONG_MAX ) ) return 1;
    1314           0 :     fd_txn_m_t * txnm = (fd_txn_m_t *)fd_chunk_to_laddr( ctx->in[in_idx].mem, chunk );
    1315           0 :     FD_TEST( txnm->payload_sz<=FD_TPU_MTU );
    1316           0 :     FD_TEST( txnm->txn_t_sz<=FD_TXN_MAX_SZ );
    1317           0 :     count_vote( ctx, fd_txn_m_txn_t_const( txnm ), fd_txn_m_payload_const( txnm ) );
    1318           0 :     return 0;
    1319           0 :   }
    1320           0 :   case IN_KIND_EPOCH: {
    1321           0 :     fd_multi_epoch_leaders_epoch_msg_init( ctx->mleaders, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ) );
    1322           0 :     fd_multi_epoch_leaders_epoch_msg_fini( ctx->mleaders );
    1323           0 :     return 0;
    1324           0 :   }
    1325           0 :   case IN_KIND_GOSSIP: {
    1326           0 :     if( FD_LIKELY( sig==FD_GOSSIP_UPDATE_TAG_DUPLICATE_SHRED ) ) {
    1327           0 :       fd_gossip_update_message_t const  * msg             = (fd_gossip_update_message_t const *)fd_type_pun_const( fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ) );
    1328           0 :       fd_gossip_duplicate_shred_t const * duplicate_shred = msg->duplicate_shred;
    1329           0 :       fd_pubkey_t const                 * from            = (fd_pubkey_t const *)fd_type_pun_const( msg->origin );
    1330           0 :       fd_epoch_leaders_t const *          lsched          = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, duplicate_shred->slot );
    1331           0 :       fd_tower_slot_duplicate_t         * out             = fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
    1332           0 :       int eqvoc_err = fd_eqvoc_chunk_insert( ctx->eqvoc, ctx->shred_version, ctx->root_slot, lsched, from, duplicate_shred, out->chunks );
    1333           0 :       update_metrics_eqvoc( ctx, eqvoc_err );
    1334           0 :       if( FD_UNLIKELY( eqvoc_err>0 ) ) {
    1335           0 :         ctx->metrics.eqvoc_proof_verified++;
    1336           0 :         fd_stem_publish( stem, OUT_IDX, FD_TOWER_SIG_SLOT_DUPLICATE, ctx->out_chunk, sizeof(fd_tower_slot_duplicate_t), 0UL, tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
    1337           0 :         ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_slot_duplicate_t), ctx->out_chunk0, ctx->out_wmark );
    1338           0 :         ctx->out_seq = stem->seqs[ OUT_IDX ];
    1339           0 :       }
    1340           0 :     }
    1341           0 :     return 0;
    1342           0 :   }
    1343           0 :   case IN_KIND_IPECHO: {
    1344           0 :     FD_TEST( sig!=0UL && sig<=USHORT_MAX );
    1345           0 :     ctx->shred_version = (ushort)sig;
    1346           0 :     return 0;
    1347           0 :   }
    1348           0 :   case IN_KIND_REPLAY: {
    1349             :     /* In the case that the tower tile is halting signing, we don't
    1350             :        want to process any replay fragments that will cause us to
    1351             :        produce a vote txn. */
    1352           0 :     if( FD_UNLIKELY( ctx->halt_signing ) ) return 1;
    1353             : 
    1354           0 :     if( FD_LIKELY( sig==REPLAY_SIG_TXN_EXECUTED ) ) {
    1355             : 
    1356             :       /* Agave only counts replay vote txns that executed successfully.
    1357             :          https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/bank_utils.rs#L53 */
    1358             : 
    1359           0 :       fd_replay_txn_executed_t * txn_executed = fd_type_pun( fd_chunk_to_laddr( ctx->in[in_idx].mem, chunk ) );
    1360           0 :       if( FD_UNLIKELY( !txn_executed->is_committable || txn_executed->is_fees_only || txn_executed->txn_err ) ) return 0;
    1361           0 :       count_vote( ctx, TXN(txn_executed->txn), txn_executed->txn->payload );
    1362           0 :     } else if( FD_LIKELY( sig==REPLAY_SIG_SLOT_COMPLETED ) ) {
    1363           0 :       fd_replay_slot_completed_t * slot_completed = (fd_replay_slot_completed_t *)fd_type_pun( fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
    1364           0 :       replay_slot_completed( ctx, slot_completed, tsorig, stem );
    1365           0 :     } else if( FD_LIKELY( sig==REPLAY_SIG_SLOT_DEAD ) ) {
    1366           0 :       fd_replay_slot_dead_t * slot_dead = (fd_replay_slot_dead_t *)fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
    1367           0 :       fd_hfork_blk_t        * hfork_blk = fd_hfork_record_our_bank_hash( ctx->hfork, &slot_dead->block_id, NULL, fd_ghost_root( ctx->ghost )->total_stake );
    1368           0 :       update_metrics_hfork( ctx, hfork_blk->flag, slot_dead->slot, &slot_dead->block_id );
    1369           0 :     }
    1370           0 :     return 0;
    1371           0 :   }
    1372           0 :   case IN_KIND_SHRED: {
    1373           0 :     if( FD_LIKELY( sz==FD_SHRED_MIN_SZ || sz==FD_SHRED_MAX_SZ ) ) { /* TODO depends on pending shred_out changes */
    1374           0 :       fd_shred_t                * shred = (fd_shred_t *)fd_type_pun( fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
    1375           0 :       fd_tower_slot_duplicate_t * out   = fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
    1376           0 :       fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, shred->slot );
    1377           0 :       int eqvoc_err = fd_eqvoc_shred_insert( ctx->eqvoc, ctx->shred_version, ctx->root_slot, lsched, shred, out->chunks );
    1378           0 :       update_metrics_eqvoc( ctx, eqvoc_err );
    1379           0 :       if( FD_UNLIKELY( eqvoc_err>0 ) ) {
    1380           0 :         ctx->metrics.eqvoc_proof_constructed++;
    1381           0 :         fd_stem_publish( stem, OUT_IDX, FD_TOWER_SIG_SLOT_DUPLICATE, ctx->out_chunk, sizeof(fd_tower_slot_duplicate_t), 0UL, tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
    1382           0 :         ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_slot_duplicate_t), ctx->out_chunk0, ctx->out_wmark );
    1383           0 :         ctx->out_seq = stem->seqs[ OUT_IDX ];
    1384           0 :       }
    1385           0 :     }
    1386           0 :     return 0;
    1387           0 :   }
    1388           0 :   default: {
    1389           0 :     FD_LOG_ERR(( "unexpected input kind %d", ctx->in_kind[ in_idx ] ));
    1390           0 :   }
    1391           0 :   }
    1392           0 : }
    1393             : 
    1394             : static void
    1395             : privileged_init( fd_topo_t *      topo,
    1396           0 :                  fd_topo_tile_t * tile ) {
    1397           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1398           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1399           0 :   fd_tower_tile_t * ctx      = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t),   sizeof(fd_tower_tile_t)        );
    1400           0 :   void            * auth_vtr = FD_SCRATCH_ALLOC_APPEND( l, auth_vtr_align(), auth_vtr_footprint() );
    1401           0 :   FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
    1402             : 
    1403           0 :   FD_TEST( fd_rng_secure( &ctx->seed, sizeof(ctx->seed) ) );
    1404             : 
    1405           0 :   if( FD_UNLIKELY( !strcmp( tile->tower.identity_key, "" ) ) ) FD_LOG_ERR(( "identity_key_path not set" ));
    1406           0 :   ctx->identity_key[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.identity_key, /* pubkey only: */ 1 ) );
    1407             : 
    1408             :   /* The vote key can be specified either directly as a base58 encoded
    1409             :      pubkey, or as a file path.  We first try to decode as a pubkey. */
    1410             : 
    1411           0 :   uchar * vote_key = fd_base58_decode_32( tile->tower.vote_account, ctx->vote_account->uc );
    1412           0 :   if( FD_UNLIKELY( !vote_key ) ) ctx->vote_account[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.vote_account, /* pubkey only: */ 1 ) );
    1413             : 
    1414           0 :   ctx->auth_vtr = auth_vtr_join( auth_vtr_new( auth_vtr ) );
    1415           0 :   for( ulong i=0UL; i<tile->tower.authorized_voter_paths_cnt; i++ ) {
    1416           0 :     fd_pubkey_t pubkey = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.authorized_voter_paths[ i ], /* pubkey only: */ 1 ) );
    1417           0 :     if( FD_UNLIKELY( auth_vtr_query( ctx->auth_vtr, pubkey, NULL ) ) ) {
    1418           0 :       FD_BASE58_ENCODE_32_BYTES( pubkey.uc, pubkey_b58 );
    1419           0 :       FD_LOG_ERR(( "authorized voter key duplicate %s", pubkey_b58 ));
    1420           0 :     }
    1421             : 
    1422           0 :     auth_vtr_t * auth_vtr = auth_vtr_insert( ctx->auth_vtr, pubkey );
    1423           0 :     auth_vtr->paths_idx = i;
    1424           0 :   }
    1425           0 :   ctx->auth_vtr_path_cnt = tile->tower.authorized_voter_paths_cnt;
    1426             : 
    1427             :   /* The tower file is used to checkpt and restore the state of the
    1428             :      local tower. */
    1429             : 
    1430           0 :   char path[ PATH_MAX ];
    1431           0 :   FD_BASE58_ENCODE_32_BYTES( ctx->identity_key->uc, identity_key_b58 );
    1432           0 :   FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "%s/tower-1_9-%s.bin.new", tile->tower.base_path, identity_key_b58 ) );
    1433           0 :   ctx->checkpt_fd = open( path, O_WRONLY|O_CREAT|O_TRUNC, 0600 );
    1434           0 :   if( FD_UNLIKELY( -1==ctx->checkpt_fd ) ) FD_LOG_ERR(( "open(`%s`) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
    1435             : 
    1436           0 :   FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "%s/tower-1_9-%s.bin", tile->tower.base_path, identity_key_b58 ) );
    1437           0 :   ctx->restore_fd = open( path, O_RDONLY );
    1438           0 :   if( FD_UNLIKELY( -1==ctx->restore_fd && errno!=ENOENT ) ) FD_LOG_ERR(( "open(`%s`) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
    1439           0 : }
    1440             : 
    1441             : static void
    1442             : unprivileged_init( fd_topo_t *      topo,
    1443           0 :                    fd_topo_tile_t * tile ) {
    1444           0 :   ulong slot_max    = fd_ulong_pow2_up( tile->tower.max_live_slots );
    1445           0 :   ulong per_vtr_max = PER_VTR_MAX;
    1446           0 :   ulong vtr_max     = fd_ulong_pow2_up( VTR_MAX );
    1447           0 :   ulong blk_max     = slot_max * EQVOC_MAX;
    1448           0 :   ulong fec_max     = slot_max * FD_SHRED_BLK_MAX / FD_FEC_SHRED_CNT;
    1449           0 :   ulong pub_max     = slot_max * FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT;
    1450             : 
    1451           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1452           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1453           0 :   fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t)                                       );
    1454           0 :   void  * auth_vtr      = FD_SCRATCH_ALLOC_APPEND( l, auth_vtr_align(),         auth_vtr_footprint()                                          );
    1455           0 :   void  * eqvoc         = FD_SCRATCH_ALLOC_APPEND( l, fd_eqvoc_align(),         fd_eqvoc_footprint( slot_max, fec_max, per_vtr_max, vtr_max ) );
    1456           0 :   void  * ghost         = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_align(),         fd_ghost_footprint( blk_max, vtr_max )                        );
    1457           0 :   void  * hfork         = FD_SCRATCH_ALLOC_APPEND( l, fd_hfork_align(),         fd_hfork_footprint( per_vtr_max, vtr_max )                    );
    1458           0 :   void  * votes         = FD_SCRATCH_ALLOC_APPEND( l, fd_votes_align(),         fd_votes_footprint( slot_max, vtr_max )          );
    1459           0 :   void  * tower         = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_align(),         fd_tower_footprint()                                          );
    1460           0 :   void  * scratch_tower = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_align(),         fd_tower_footprint()                                          );
    1461           0 :   void  * tower_blocks  = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_blocks_align(),  fd_tower_blocks_footprint( slot_max )                         );
    1462           0 :   void  * tower_lockos  = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_lockos_align(),  fd_tower_lockos_footprint( slot_max, vtr_max )                );
    1463           0 :   void  * tower_stakes  = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_stakes_align(),  fd_tower_stakes_footprint( slot_max, vtr_max )                );
    1464           0 :   void  * tower_voters  = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_voters_align(),  fd_tower_voters_footprint( vtr_max )                          );
    1465           0 :   uchar * voter_towers  = FD_SCRATCH_ALLOC_APPEND( l, FD_TOWER_ALIGN,           FD_TOWER_FOOTPRINT * vtr_max                                 );
    1466           0 :   void  * publishes     = FD_SCRATCH_ALLOC_APPEND( l, publishes_align(),        publishes_footprint( pub_max )                                );
    1467           0 :   FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
    1468             : 
    1469           0 :   ctx->wksp               = topo->workspaces[ topo->objs[ tile->tile_obj_id ].wksp_id ].wksp;
    1470           0 :   ctx->identity_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
    1471           0 :   ctx->auth_vtr_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->av_keyswitch_obj_id ) );
    1472           0 :   (void)auth_vtr; /* privileged_init */
    1473           0 :   ctx->eqvoc              = fd_eqvoc_join              ( fd_eqvoc_new              ( eqvoc, slot_max, fec_max, per_vtr_max, vtr_max, ctx->seed ) );
    1474           0 :   ctx->ghost              = fd_ghost_join              ( fd_ghost_new              ( ghost, blk_max, vtr_max, ctx->seed )                        );
    1475           0 :   ctx->hfork              = fd_hfork_join              ( fd_hfork_new              ( hfork, per_vtr_max, vtr_max, ctx->seed )                    );
    1476           0 :   ctx->votes              = fd_votes_join              ( fd_votes_new              ( votes, slot_max, vtr_max, ctx->seed )                       );
    1477           0 :   ctx->tower              = fd_tower_join              ( fd_tower_new              ( tower )                                                     );
    1478           0 :   ctx->scratch_tower      = fd_tower_join              ( fd_tower_new              ( scratch_tower )                                             );
    1479           0 :   ctx->tower_blocks       = fd_tower_blocks_join       ( fd_tower_blocks_new       ( tower_blocks, slot_max, ctx->seed )                         );
    1480           0 :   ctx->tower_lockos       = fd_tower_lockos_join       ( fd_tower_lockos_new       ( tower_lockos, slot_max, vtr_max, ctx->seed )                );
    1481           0 :   ctx->tower_stakes       = fd_tower_stakes_join       ( fd_tower_stakes_new       ( tower_stakes, slot_max, vtr_max, ctx->seed )                );
    1482           0 :   ctx->tower_voters       = fd_tower_voters_join       ( fd_tower_voters_new       ( tower_voters, vtr_max )                                     );
    1483           0 :   ctx->voter_towers       = voter_towers;
    1484           0 :   for( ulong i = 0; i < vtr_max; i++ ) {
    1485           0 :     fd_tower_voters_t * entry = fd_tower_voters_push_tail_nocopy( ctx->tower_voters );
    1486           0 :     entry->tower = fd_tower_join( fd_tower_new( voter_towers + i * FD_TOWER_FOOTPRINT ) );
    1487           0 :   }
    1488           0 :   fd_tower_voters_remove_all( ctx->tower_voters );
    1489           0 :   ctx->publishes          = publishes_join             ( publishes_new             ( publishes, pub_max )                                     );
    1490           0 :   ctx->mleaders           = fd_multi_epoch_leaders_join( fd_multi_epoch_leaders_new( ctx->mleaders_mem )                                      );
    1491             : 
    1492           0 :   FD_TEST( ctx->wksp  );
    1493           0 :   FD_TEST( ctx->identity_keyswitch );
    1494           0 :   FD_TEST( ctx->auth_vtr_keyswitch );
    1495           0 :   FD_TEST( ctx->auth_vtr );
    1496           0 :   FD_TEST( ctx->eqvoc );
    1497           0 :   FD_TEST( ctx->ghost );
    1498           0 :   FD_TEST( ctx->hfork );
    1499           0 :   FD_TEST( ctx->votes );
    1500           0 :   FD_TEST( ctx->tower );
    1501           0 :   FD_TEST( ctx->scratch_tower );
    1502           0 :   FD_TEST( ctx->tower_blocks );
    1503           0 :   FD_TEST( ctx->tower_lockos );
    1504           0 :   FD_TEST( ctx->tower_stakes );
    1505           0 :   FD_TEST( ctx->tower_voters );
    1506           0 :   FD_TEST( ctx->voter_towers );
    1507           0 :   FD_TEST( ctx->publishes );
    1508           0 :   FD_TEST( ctx->mleaders );
    1509             : 
    1510           0 :   memset( ctx->chunks, 0, sizeof(ctx->chunks) );
    1511           0 :   memset( &ctx->compact_tower_sync_serde, 0, sizeof(ctx->compact_tower_sync_serde) );
    1512           0 :   memset( ctx->vote_txn, 0, sizeof(ctx->vote_txn) );
    1513             : 
    1514           0 :   ctx->halt_signing    = 0;
    1515           0 :   ctx->hard_fork_fatal = tile->tower.hard_fork_fatal;
    1516           0 :   ctx->shred_version   = 0;
    1517           0 :   ctx->init_slot       = ULONG_MAX;
    1518           0 :   ctx->root_slot       = ULONG_MAX;
    1519             : 
    1520           0 :   ulong banks_obj_id = fd_pod_query_ulong( topo->props, "banks", ULONG_MAX );
    1521           0 :   FD_TEST( banks_obj_id!=ULONG_MAX );
    1522           0 :   ctx->banks = fd_banks_join( fd_topo_obj_laddr( topo, banks_obj_id ) );
    1523           0 :   FD_TEST( ctx->banks );
    1524             : 
    1525           0 :   fd_accdb_init_from_topo( ctx->accdb, topo, tile, tile->tower.accdb_max_depth );
    1526             : 
    1527           0 :   FD_TEST( tile->in_cnt<sizeof(ctx->in_kind)/sizeof(ctx->in_kind[0]) );
    1528           0 :   for( ulong i=0UL; i<tile->in_cnt; i++ ) {
    1529           0 :     fd_topo_link_t * link = &topo->links[ tile->in_link_id[ i ] ];
    1530           0 :     fd_topo_wksp_t * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
    1531             : 
    1532           0 :     if     ( FD_LIKELY( !strcmp( link->name, "dedup_resolv"  ) ) ) ctx->in_kind[ i ] = IN_KIND_DEDUP;
    1533           0 :     else if( FD_LIKELY( !strcmp( link->name, "replay_epoch"  ) ) ) ctx->in_kind[ i ] = IN_KIND_EPOCH;
    1534           0 :     else if( FD_LIKELY( !strcmp( link->name, "gossip_out"    ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP;
    1535           0 :     else if( FD_LIKELY( !strcmp( link->name, "ipecho_out"    ) ) ) ctx->in_kind[ i ] = IN_KIND_IPECHO;
    1536           0 :     else if( FD_LIKELY( !strcmp( link->name, "replay_out"    ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY;
    1537           0 :     else if( FD_LIKELY( !strcmp( link->name, "shred_out"     ) ) ) ctx->in_kind[ i ] = IN_KIND_SHRED;
    1538           0 :     else     FD_LOG_ERR(( "tower tile has unexpected input link %lu %s", i, link->name ));
    1539             : 
    1540           0 :     ctx->in[ i ].mcache_only = !link->mtu;
    1541           0 :     if( FD_LIKELY( !ctx->in[ i ].mcache_only ) ) {
    1542           0 :       ctx->in[ i ].mem    = link_wksp->wksp;
    1543           0 :       ctx->in[ i ].mtu    = link->mtu;
    1544           0 :       ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
    1545           0 :       ctx->in[ i ].wmark  = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
    1546           0 :     }
    1547           0 :   }
    1548             : 
    1549           0 :   ctx->out_mem    = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ 0 ] ].dcache_obj_id ].wksp_id ].wksp;
    1550           0 :   ctx->out_chunk0 = fd_dcache_compact_chunk0( ctx->out_mem, topo->links[ tile->out_link_id[ 0 ] ].dcache );
    1551           0 :   ctx->out_wmark  = fd_dcache_compact_wmark ( ctx->out_mem, topo->links[ tile->out_link_id[ 0 ] ].dcache, topo->links[ tile->out_link_id[ 0 ] ].mtu );
    1552           0 :   ctx->out_chunk  = ctx->out_chunk0;
    1553           0 :   ctx->out_seq    = 0UL;
    1554             : 
    1555           0 :   memset( &ctx->metrics, 0, sizeof(ctx->metrics) );
    1556           0 :   ctx->metrics.last_vote_slot = ULONG_MAX;
    1557           0 : }
    1558             : 
    1559             : static ulong
    1560             : populate_allowed_seccomp( fd_topo_t const *      topo,
    1561             :                           fd_topo_tile_t const * tile,
    1562             :                           ulong                  out_cnt,
    1563           0 :                           struct sock_filter *   out ) {
    1564           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1565           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1566           0 :   fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
    1567             : 
    1568           0 :   populate_sock_filter_policy_fd_tower_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), (uint)ctx->checkpt_fd, (uint)ctx->restore_fd );
    1569           0 :   return sock_filter_policy_fd_tower_tile_instr_cnt;
    1570           0 : }
    1571             : 
    1572             : static ulong
    1573             : populate_allowed_fds( fd_topo_t const *      topo,
    1574             :                       fd_topo_tile_t const * tile,
    1575             :                       ulong                  out_fds_cnt,
    1576           0 :                       int *                  out_fds ) {
    1577           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1578           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1579           0 :   fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
    1580             : 
    1581           0 :   if( FD_UNLIKELY( out_fds_cnt<4UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
    1582             : 
    1583           0 :   ulong out_cnt = 0UL;
    1584           0 :   out_fds[ out_cnt++ ] = 2; /* stderr */
    1585           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
    1586           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
    1587           0 :   if( FD_LIKELY( ctx->checkpt_fd!=-1 ) ) out_fds[ out_cnt++ ] = ctx->checkpt_fd;
    1588           0 :   if( FD_LIKELY( ctx->restore_fd!=-1 ) ) out_fds[ out_cnt++ ] = ctx->restore_fd;
    1589           0 :   return out_cnt;
    1590           0 : }
    1591             : 
    1592           0 : #define STEM_BURST (2UL)        /* MAX( slot_confirmed, slot_rooted AND (slot_done OR slot_ignored) ) */
    1593           0 : #define STEM_LAZY  (128L*3000L) /* see explanation in fd_pack */
    1594             : 
    1595           0 : #define STEM_CALLBACK_CONTEXT_TYPE        fd_tower_tile_t
    1596           0 : #define STEM_CALLBACK_CONTEXT_ALIGN       alignof(fd_tower_tile_t)
    1597           0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
    1598           0 : #define STEM_CALLBACK_METRICS_WRITE       metrics_write
    1599           0 : #define STEM_CALLBACK_AFTER_CREDIT        after_credit
    1600           0 : #define STEM_CALLBACK_RETURNABLE_FRAG     returnable_frag
    1601             : 
    1602             : #include "../../disco/stem/fd_stem.c"
    1603             : 
    1604             : fd_topo_run_tile_t fd_tile_tower = {
    1605             :   .name                     = "tower",
    1606             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    1607             :   .populate_allowed_fds     = populate_allowed_fds,
    1608             :   .scratch_align            = scratch_align,
    1609             :   .scratch_footprint        = scratch_footprint,
    1610             :   .unprivileged_init        = unprivileged_init,
    1611             :   .privileged_init          = privileged_init,
    1612             :   .run                      = stem_run,
    1613             : };

Generated by: LCOV version 1.14