LCOV - code coverage report
Current view: top level - discof/tower - fd_tower_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 977 0.0 %
Date: 2026-06-29 05:51:35 Functions: 0 86 0.0 %

          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_serdes.h"
      10             : #include "../../choreo/tower/fd_tower_stakes.h"
      11             : #include "../../disco/fd_txn_p.h"
      12             : #include "../../disco/events/generated/fd_event_gen.h"
      13             : #include "../../disco/shred/fd_shred_tile.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/node_info/fd_node_info.h"
      18             : #include "../../disco/topo/fd_topo.h"
      19             : #include "../../disco/fd_txn_m.h"
      20             : #include "../../discof/replay/fd_replay_tile.h"
      21             : #include "../../flamenco/leaders/fd_multi_epoch_leaders.h"
      22             : #include "../../flamenco/runtime/fd_bank.h"
      23             : #include "../../flamenco/leaders/fd_multi_epoch_leaders.h"
      24             : #include "../../flamenco/runtime/fd_system_ids.h"
      25             : #include "../../flamenco/runtime/program/vote/fd_vote_state_versioned.h"
      26             : #include "../../flamenco/runtime/program/vote/fd_vote_codec.h"
      27             : #include "../../util/pod/fd_pod.h"
      28             : 
      29             : #include <errno.h>
      30             : #include <fcntl.h>
      31             : #include <unistd.h>
      32             : 
      33             : /* The Tower tile broadly processes three classes of frags, leading to
      34             :    three distinct kinds of frag processing:
      35             : 
      36             :    1. Processing vote _accounts_ (after replaying a block)
      37             : 
      38             :       When Replay finishes executing a block, Tower reads back the vote
      39             :       account state for every staked validator.  This is deterministic:
      40             :       the vote account state is the result of executing all vote txns in
      41             :       the block through the vote program, so it is guaranteed to
      42             :       converge with Agave's view of the same accounts.  Tower uses these
      43             :       accounts to run the fork choice rule (fd_ghost) and TowerBFT
      44             :       (fd_tower).
      45             : 
      46             :    2. Processing vote _transactions_ (at arbitrary points in time)
      47             : 
      48             :       Tower also receives vote txns from Gossip and TPU.  These arrive
      49             :       at arbitrary, nondeterministic times because Gossip and TPU are
      50             :       both unreliable mediums: there's no guarantee we observe all the
      51             :       same vote txns as Agave (nor another Firedancer, for that matter).
      52             : 
      53             :       Tower is stricter than Agave when validating these vote txns (e.g.
      54             :       we use is_simple_vote which requires at most two signers, whereas
      55             :       Agave's Gossip vote parser does not).  Being stricter is
      56             :       acceptable given vote txns from Gossip and TPU are inherently
      57             :       unreliable, so dropping a small number of votes that Agave allows
      58             :       but Firedancer does not is not significant to convergence.
      59             : 
      60             :       However, these same vote txns are (redundantly) transmitted as
      61             :       part of a block as well ie. through Replay.  The validation of
      62             :       these Replay-sourced vote txns _is_ one-to-one with Agave (namely
      63             :       the Vote Program), and critical for convergence.  Specifically, we
      64             :       only process Replay vote txns that have been successfully executed
      65             :       when counting them towards confirmations.
      66             : 
      67             :       The guarantee is "eventual consistency": even though individual
      68             :       Gossip or TPU vote txns may be lost, we are guaranteed to
      69             :       "eventually" confirm a block and converge with Agave as long as we
      70             :       receive the block and replay its contained vote txns, because our
      71             :       vote programs match 1-1.  Gossip / TPU can provide a fast-path for
      72             :       earlier confirmations as well as a source of security via
      73             :       redundancy in case we are not receiving the blocks from the rest
      74             :       of the network.
      75             : 
      76             :       The processing of vote txns is important to (as already alluded)
      77             :       fd_votes and fd_hfork.
      78             : 
      79             :   3. Processing "other" frags.  Vote account and vote transaction
      80             :      processing (1 and 2 above) is the meat and potatoes, but Tower also
      81             :      processes several auxiliary frag types:
      82             : 
      83             :       a. Duplicate shred gossip messages (from the gossip tile): Tower
      84             :          receives duplicate shred proofs from other validators via
      85             :          gossip.  These proofs arrive in chunks (fd_eqvoc_chunk_insert)
      86             :          and are reassembled and cryptographically verified before being
      87             :          accepted.
      88             : 
      89             :       b. Epoch stake updates (from the replay tile): Tower receives
      90             :          epoch stake information to maintain the leader schedule via
      91             :          fd_stake_ci, which is needed by eqvoc for signature
      92             :          verification of shred proofs.
      93             : 
      94             :       c. Shred version (from the ipecho tile): Tower receives the shred
      95             :          version from ipecho to configure eqvoc's shred version
      96             :          filtering for proof verification.
      97             : 
      98             :       d. Shreds (from the shred tile): Tower checks incoming shreds for
      99             :          equivocation via fd_eqvoc.  If two conflicting shreds are
     100             :          detected for the same FEC set, Tower constructs a duplicate
     101             :          proof and publishes it (FD_TOWER_SIG_SLOT_DUPLICATE).
     102             : 
     103             :       e. Slot dead (from the replay tile): Tower records a NULL bank
     104             :          hash for dead slots in the hard fork detector (fd_hfork).
     105             : 
     106             :    Tower signals to other tiles about events that occur as a result of
     107             :    those three modes, such as what block to vote on, what block to reset
     108             :    onto as leader, what block got rooted, what blocks are duplicates,
     109             :    and what blocks are confirmed.
     110             : 
     111             :    In general, Tower uses "block_id" as the identifier for a block.  The
     112             :    block_id is the merkle root of the last FEC set for a block.  Unlike
     113             :    slot numbers, this is guaranteed to be unique for a given block and
     114             :    is therefore a canonical identifier because slot numbers can identify
     115             :    multiple blocks, if a leader equivocates (produces multiple blocks
     116             :    for the same slot), whereas it is not feasible for a leader to
     117             :    produce block_id collisions.
     118             : 
     119             :    However, the block_id was only introduced into the Solana protocol
     120             :    recently, and TowerBFT still uses the "legacy" identifier of slot
     121             :    numbers for blocks.  So the tile (and relevant modules) will use
     122             :    block_id when possible to interface with the protocol but otherwise
     123             :    fallback to slot number when block_id is unsupported due to limits of
     124             :    the protocol. */
     125             : 
     126             : #define LOGGING 0
     127             : 
     128           0 : #define IN_KIND_DEDUP  (0)
     129           0 : #define IN_KIND_EPOCH  (1)
     130           0 : #define IN_KIND_REPLAY (2)
     131           0 : #define IN_KIND_GOSSIP (3)
     132           0 : #define IN_KIND_IPECHO (4)
     133           0 : #define IN_KIND_SHRED  (5)
     134             : 
     135           0 : #define OUT_IDX 0 /* only a single out link tower_out */
     136           0 : #define AUTH_VTR_LG_MAX (5) /* The Solana Vote Interface supports up to 32 authorized voters. */
     137             : FD_STATIC_ASSERT( 1<<AUTH_VTR_LG_MAX==32, AUTH_VTR_LG_MAX );
     138             : 
     139             : /* Tower processes at most 2 equivocating blocks for a given slot: the
     140             :    first block is the first one we observe for a slot, and the second
     141             :    block is the one that gets duplicate confirmed.  Most of the time,
     142             :    they are the same (ie. the block we first saw is the block that gets
     143             :    duplicate confirmed), but we size for the worst case which is every
     144             :    block in slot_max equivocates and we always see 2 blocks for every
     145             :    slot. */
     146             : 
     147           0 : #define EQVOC_MAX (2)
     148             : 
     149             : /* The Alpenglow VAT caps the voting set of validators to 2000.  Only
     150             :    the top 2000 voters by stake will be counted towards consensus rules.
     151             :    Firedancer uses the same bound for TowerBFT.
     152             : 
     153             :    Note module implementations may round the max capacity of various
     154             :    structures to pow2 for performance, but the consensus logic will only
     155             :    retain at most 2000 voters.
     156             : 
     157             :    https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0357-alpenglow_validator_admission_ticket.md */
     158             : 
     159           0 : #define VTR_MAX (2000) /* the maximum # of unique voters ie. node pubkeys. */
     160             : 
     161             : /* PER_VTR_MAX controls how many "entries" a validator is allowed to
     162             :    occupy in various vote-tracking structures.  This is set somewhat
     163             :    arbitrarily based on expected worst-case usage by an honest validator
     164             :    and is set to guard against a malicious spamming validator attempting
     165             :    to oom Firedancer structures. */
     166             : 
     167           0 : #define PER_VTR_MAX (512) /* the maximum amount of slot history the sysvar retains */
     168             : 
     169             : struct publish {
     170             :   ulong          sig;
     171             :   fd_tower_msg_t msg;
     172             : };
     173             : typedef struct publish publish_t;
     174             : 
     175             : #define DEQUE_NAME publishes
     176           0 : #define DEQUE_T    publish_t
     177             : #include "../../util/tmpl/fd_deque_dynamic.c"
     178             : 
     179             : struct auth_vtr {
     180             :   fd_pubkey_t addr;      /* map key, vote account address */
     181             :   uint        hash;      /* reserved for use by fd_map */
     182             :   ulong       paths_idx; /* index in authorized voter paths */
     183             : };
     184             : typedef struct auth_vtr auth_vtr_t;
     185             : 
     186             : #define MAP_NAME               auth_vtr
     187           0 : #define MAP_T                  auth_vtr_t
     188           0 : #define MAP_LG_SLOT_CNT        AUTH_VTR_LG_MAX
     189           0 : #define MAP_KEY                addr
     190           0 : #define MAP_KEY_T              fd_pubkey_t
     191           0 : #define MAP_KEY_NULL           (fd_pubkey_t){0}
     192           0 : #define MAP_KEY_EQUAL(k0,k1)   (!(memcmp((k0).key,(k1).key,sizeof(fd_pubkey_t))))
     193           0 : #define MAP_KEY_INVAL(k)       (MAP_KEY_EQUAL((k),MAP_KEY_NULL))
     194             : #define MAP_KEY_EQUAL_IS_SLOW  1
     195           0 : #define MAP_KEY_HASH(k)        ((uint)fd_ulong_hash( fd_ulong_load_8( (k).uc ) ))
     196             : #include "../../util/tmpl/fd_map.c"
     197             : 
     198             : struct epoch_vtr {
     199             :   fd_pubkey_t vote_acc;
     200             :   ulong       stake;
     201             :   fd_pubkey_t auth_vtr; /* authorized voter for vote_acc at this map's target epoch; all-zero if unavailable */
     202             :   ulong       next; /* reserved for fd_pool and fd_map_chain */
     203             : };
     204             : typedef struct epoch_vtr epoch_vtr_t;
     205             : 
     206             : #define POOL_NAME epoch_vtr_pool
     207           0 : #define POOL_T    epoch_vtr_t
     208             : #include "../../util/tmpl/fd_pool.c"
     209             : 
     210             : #define MAP_NAME               epoch_vtr_map
     211             : #define MAP_ELE_T              epoch_vtr_t
     212           0 : #define MAP_KEY                vote_acc
     213             : #define MAP_KEY_T              fd_pubkey_t
     214           0 : #define MAP_KEY_EQ(k0,k1)      (!memcmp((k0),(k1),sizeof(fd_pubkey_t)))
     215           0 : #define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_pubkey_t)))
     216           0 : #define MAP_NEXT               next
     217             : #include "../../util/tmpl/fd_map_chain.c"
     218             : 
     219             : #define AUTH_VOTERS_MAX (16UL)
     220             : 
     221             : struct in_ctx {
     222             :   int         mcache_only;
     223             :   fd_wksp_t * mem;
     224             :   ulong       chunk0;
     225             :   ulong       wmark;
     226             :   ulong       mtu;
     227             : };
     228             : typedef struct in_ctx in_ctx_t;
     229             : 
     230             : struct fd_tower_tile {
     231             :   ulong            seed; /* map seed */
     232             :   int              checkpt_fd;
     233             :   int              restore_fd;
     234             :   fd_pubkey_t      identity_key[1];
     235             :   fd_pubkey_t      vote_account[1];
     236             :   ulong            auth_vtr_path_cnt;  /* number of authorized voter paths passed to tile */
     237             :   uchar            our_vote_acct[FD_VOTE_STATE_DATA_MAX]; /* buffer for reading back our own vote acct data */
     238             :   ulong            our_vote_acct_sz;
     239             : 
     240             :   /* owned joins */
     241             : 
     242             :   fd_wksp_t *      wksp; /* workspace */
     243             :   fd_keyswitch_t * identity_keyswitch;
     244             :   auth_vtr_t *     auth_vtr;
     245             :   fd_keyswitch_t * auth_vtr_keyswitch; /* authorized voter keyswitch */
     246             : 
     247             :   fd_eqvoc_t * eqvoc;
     248             :   fd_ghost_t * ghost;
     249             :   fd_hfork_t * hfork;
     250             :   fd_votes_t * votes;
     251             :   fd_tower_t * tower;
     252             : 
     253             :   fd_vote_instruction_t scratch_ix;
     254             :   fd_tower_vote_t *     scratch_tower; /* spare deque used during vote txn processing */
     255             : 
     256             :   publish_t *                publishes; /* deque of slot_confirmed msgs queued for publishing */
     257             :   fd_multi_epoch_leaders_t * mleaders; /* multi-epoch leaders */
     258             : 
     259             :   /* borrowed joins */
     260             : 
     261             :   fd_banks_t * banks;
     262             :   fd_accdb_t * accdb;
     263             : 
     264             :   /* static structures */
     265             : 
     266             :   fd_pubkey_t                   id_keys  [VTR_MAX]; /* identity keys */
     267             :   fd_pubkey_t                   vote_accs[VTR_MAX]; /* vote account addresses */
     268             :   ulong                         vtr_cnt;            /* actual cnt of elements in above arrays */
     269             :   fd_gossip_duplicate_shred_t   duplicate_chunks[FD_EQVOC_CHUNK_CNT];
     270             :   fd_compact_tower_sync_serde_t compact_tower_sync_serde;
     271             :   uchar                         vote_txn[FD_TPU_PARSED_MTU];
     272             : 
     273             :   uchar __attribute__((aligned(FD_MULTI_EPOCH_LEADERS_ALIGN))) mleaders_mem[ FD_MULTI_EPOCH_LEADERS_FOOTPRINT ];
     274             :   uchar __attribute__((aligned(FD_TOP_VOTES_ITER_ALIGN     ))) iter_mem    [ FD_TOP_VOTES_ITER_FOOTPRINT      ];
     275             : 
     276             :   ulong             root_epoch;
     277             :   ulong             root_epoch_total_stake;
     278             :   ulong             next_epoch_total_stake;
     279             :   epoch_vtr_t     * root_epoch_vtr_pool;
     280             :   epoch_vtr_map_t * root_epoch_vtr_map;
     281             :   epoch_vtr_t     * next_epoch_vtr_pool;
     282             :   epoch_vtr_map_t * next_epoch_vtr_map;
     283             : 
     284             :   /* metadata */
     285             : 
     286             :   int    halt_signing;
     287             :   int    hard_fork_fatal;
     288             :   int    wfs;           /* 1 if booted with wait_for_supermajority */
     289             :   ushort shred_version;
     290             :   int    init; /* 1 after ghost_init has been called */
     291             : 
     292             :   /* in/out link setup */
     293             : 
     294             :   int      in_kind[ 64UL ];
     295             :   in_ctx_t in     [ 64UL ];
     296             : 
     297             :   fd_wksp_t * out_mem;
     298             :   ulong       out_chunk0;
     299             :   ulong       out_wmark;
     300             :   ulong       out_chunk;
     301             :   ulong       out_seq;
     302             : 
     303             :   /* metrics */
     304             : 
     305             :   struct {
     306             :     ulong not_ready;
     307             : 
     308             :     ulong ignored_cnt;
     309             :     ulong ignored_slot;
     310             :     ulong eqvoc_cnt;
     311             :     ulong eqvoc_slot;
     312             : 
     313             :     ulong replay_slot;
     314             :     ulong last_vote_slot;
     315             :     ulong reset_slot;
     316             :     ulong root_slot;
     317             :     ulong init_slot;
     318             : 
     319             :     ulong fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_CNT ];
     320             :     ulong gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_CNT ];
     321             : 
     322             :     ulong votes     [ FD_METRICS_ENUM_VOTE_TXN_RESULT_CNT         ];
     323             :     ulong vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_CNT        ];
     324             :     ulong gate_int  [ FD_METRICS_ENUM_VOTE_INTERMEDIATE_GATE_CNT  ];
     325             : 
     326             :     ulong eqvoc_success;
     327             :     ulong eqvoc_err;
     328             : 
     329             :     ulong ghost[ FD_METRICS_ENUM_GHOST_VOTE_RESULT_CNT ];
     330             : 
     331             :     ulong hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_CNT ];
     332             : 
     333             :     ulong hfork_matched_slot;
     334             :     ulong hfork_mismatched_slot;
     335             :   } metrics;
     336             : };
     337             : typedef struct fd_tower_tile fd_tower_tile_t;
     338             : 
     339             : /* Compile-time dependency injection.  This macro defaults to the
     340             :    production implementation defined below.  Tests can #define it before
     341             :    #include-ing this file to substitute a mock. */
     342             : 
     343             : #ifndef QUERY_TOWERS
     344           0 : #define QUERY_TOWERS query_towers
     345             : #endif
     346             : 
     347             : #ifndef QUERY_VOTERS
     348           0 : #define QUERY_VOTERS query_voters
     349             : #endif
     350             : 
     351             : ulong QUERY_TOWERS( fd_tower_tile_t *, fd_replay_slot_completed_t *, fd_ghost_blk_t *, int *, ulong * );
     352             : void  QUERY_VOTERS( fd_tower_tile_t *, fd_replay_slot_completed_t *, ulong );
     353             : 
     354             : /* vote_account_config extracts configuration of this validator's vote
     355             :    account (on-chain state).  data points to the first byte of the
     356             :    vote account's data.  Sets:
     357             :    - *authority_out to the selected authorized voter's public key
     358             :    - *authority_idx_out to the tile's auth_vtr index (matches sign tile)
     359             :      or ULONG_MAX it the authorized voter is the node identity
     360             :      or LONG_MAX if it matches neither
     361             :    - *node_pubkey to the vote account's pubkey
     362             :   Returns 1 if the validator has a key for the found vote authority,
     363             :   and 0 otherwise. */
     364             : 
     365             : static int
     366             : vote_account_config( fd_tower_tile_t * ctx,
     367             :                      uchar const *     data,
     368             :                      ulong             data_sz,
     369             :                      ulong             epoch,
     370             :                      fd_pubkey_t *     authority_out,
     371             :                      ulong *           authority_idx_out,
     372           0 :                      fd_pubkey_t *     node_pubkey_out ) {
     373             : 
     374           0 :   fd_vote_state_versioned_t vsv[1];
     375           0 :   FD_CHECK_CRIT( fd_vote_state_versioned_deserialize( vsv, data, data_sz ), "unable to decode vote state versioned" );
     376             : 
     377           0 :   fd_pubkey_t const * auth_vtr_addr = NULL;
     378           0 :   switch( vsv->kind ) {
     379           0 :     case fd_vote_state_versioned_enum_v1_14_11:
     380           0 :       *node_pubkey_out = vsv->v1_14_11.node_pubkey;
     381           0 :       for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->v1_14_11.authorized_voters.treap, vsv->v1_14_11.authorized_voters.pool );
     382           0 :            !fd_vote_authorized_voters_treap_rev_iter_done( iter );
     383           0 :            iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->v1_14_11.authorized_voters.pool ) ) {
     384           0 :         fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->v1_14_11.authorized_voters.pool );
     385           0 :         if( FD_LIKELY( ele->epoch<=epoch ) ) {
     386           0 :           auth_vtr_addr = &ele->pubkey;
     387           0 :           break;
     388           0 :         }
     389           0 :       }
     390           0 :       break;
     391           0 :     case fd_vote_state_versioned_enum_v3:
     392           0 :       *node_pubkey_out = vsv->v3.node_pubkey;
     393           0 :       for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->v3.authorized_voters.treap, vsv->v3.authorized_voters.pool );
     394           0 :           !fd_vote_authorized_voters_treap_rev_iter_done( iter );
     395           0 :           iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->v3.authorized_voters.pool ) ) {
     396           0 :         fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->v3.authorized_voters.pool );
     397           0 :         if( FD_LIKELY( ele->epoch<=epoch ) ) {
     398           0 :           auth_vtr_addr = &ele->pubkey;
     399           0 :           break;
     400           0 :         }
     401           0 :       }
     402           0 :       break;
     403           0 :     case fd_vote_state_versioned_enum_v4:
     404           0 :       *node_pubkey_out = vsv->v4.node_pubkey;
     405           0 :       for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->v4.authorized_voters.treap, vsv->v4.authorized_voters.pool );
     406           0 :           !fd_vote_authorized_voters_treap_rev_iter_done( iter );
     407           0 :           iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->v4.authorized_voters.pool ) ) {
     408           0 :         fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->v4.authorized_voters.pool );
     409           0 :         if( FD_LIKELY( ele->epoch<=epoch ) ) {
     410           0 :           auth_vtr_addr = &ele->pubkey;
     411           0 :           break;
     412           0 :         }
     413           0 :       }
     414           0 :       break;
     415           0 :     default:
     416           0 :       FD_LOG_CRIT(( "unsupported vote state versioned discriminant: %u", vsv->kind ));
     417           0 :   }
     418             : 
     419           0 :   FD_CHECK_CRIT( auth_vtr_addr, "unable to find authorized voter, likely corrupt vote account state" );
     420           0 :   *authority_out = *auth_vtr_addr;
     421             : 
     422           0 :   if( fd_pubkey_eq( auth_vtr_addr, ctx->identity_key ) ) {
     423           0 :     *authority_idx_out = ULONG_MAX;
     424           0 :     return 1;
     425           0 :   }
     426             : 
     427           0 :   auth_vtr_t * auth_vtr = auth_vtr_query( ctx->auth_vtr, *auth_vtr_addr, NULL );
     428           0 :   if( FD_LIKELY( auth_vtr ) ) {
     429           0 :     *authority_idx_out = auth_vtr->paths_idx;
     430           0 :     return 1;
     431           0 :   }
     432             : 
     433           0 :   *authority_idx_out = LONG_MAX;
     434           0 :   return 0;
     435           0 : }
     436             : 
     437             : static void
     438             : update_metrics_eqvoc( fd_tower_tile_t * ctx,
     439           0 :                       int               err ) {
     440           0 :   ctx->metrics.eqvoc_success += (ulong)(err==FD_EQVOC_SUCCESS);
     441           0 :   ctx->metrics.eqvoc_err     += (ulong)(err<0);
     442           0 : }
     443             : 
     444             : static void
     445             : update_metrics_ghost( fd_tower_tile_t * ctx,
     446           0 :                       int               err ) {
     447           0 :   ctx->metrics.ghost[ FD_METRICS_ENUM_GHOST_VOTE_RESULT_V_SUCCESS_IDX       ] += (ulong)(err==FD_GHOST_SUCCESS);
     448           0 :   ctx->metrics.ghost[ FD_METRICS_ENUM_GHOST_VOTE_RESULT_V_NOT_VOTED_IDX     ] += (ulong)(err==FD_GHOST_ERR_NOT_VOTED);
     449           0 :   ctx->metrics.ghost[ FD_METRICS_ENUM_GHOST_VOTE_RESULT_V_TOO_OLD_IDX       ] += (ulong)(err==FD_GHOST_ERR_VOTE_TOO_OLD);
     450           0 :   ctx->metrics.ghost[ FD_METRICS_ENUM_GHOST_VOTE_RESULT_V_ALREADY_VOTED_IDX ] += (ulong)(err==FD_GHOST_ERR_ALREADY_VOTED);
     451           0 : }
     452             : 
     453             : static void
     454             : update_metrics_hfork( fd_tower_tile_t * ctx,
     455             :                       int               hfork_err,
     456             :                       ulong             slot,
     457           0 :                       fd_hash_t const * block_id ) {
     458           0 :   switch( hfork_err ) {
     459           0 :   case FD_HFORK_SUCCESS_MATCHED:
     460           0 :     ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_SUCCESS_MATCHED_IDX ]++;
     461           0 :     ctx->metrics.hfork_matched_slot = fd_ulong_max( ctx->metrics.hfork_matched_slot, slot );
     462           0 :     break;
     463           0 :   case FD_HFORK_SUCCESS:
     464           0 :     ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_SUCCESS_IDX ]++;
     465           0 :     break;
     466           0 :   case FD_HFORK_ERR_MISMATCHED:
     467           0 :     ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_MISMATCHED_IDX ]++;
     468           0 :     if( FD_UNLIKELY( ctx->hard_fork_fatal ) ) {
     469           0 :       FD_BASE58_ENCODE_32_BYTES( block_id->uc, _block_id );
     470           0 :       FD_LOG_ERR(( "HARD FORK DETECTED for slot %lu block ID `%s`", slot, _block_id ));
     471           0 :     }
     472           0 :     ctx->metrics.hfork_mismatched_slot = fd_ulong_max( ctx->metrics.hfork_mismatched_slot, slot );
     473           0 :     break;
     474           0 :   case FD_HFORK_ERR_UNKNOWN_VTR:
     475           0 :     ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_UNKNOWN_VOTER_IDX ]++;
     476           0 :     break;
     477           0 :   case FD_HFORK_ERR_ALREADY_VOTED:
     478           0 :     ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_ALREADY_VOTED_IDX ]++;
     479           0 :     break;
     480           0 :   case FD_HFORK_ERR_VOTE_TOO_OLD:
     481           0 :     ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_TOO_OLD_IDX ]++;
     482           0 :     break;
     483           0 :   default:
     484           0 :     FD_LOG_ERR(( "unhandled hfork_err %d", hfork_err ));
     485           0 :   }
     486           0 : }
     487             : 
     488             : static void
     489             : update_metrics_vote_slot( fd_tower_tile_t * ctx,
     490           0 :                           int               err ) {
     491           0 :   ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_SUCCESS_IDX       ] += (ulong)(err==FD_VOTES_SUCCESS);
     492           0 :   ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_TOO_NEW_IDX       ] += (ulong)(err==FD_VOTES_ERR_VOTE_TOO_NEW);
     493           0 :   ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_UNKNOWN_VOTER_IDX   ] += (ulong)(err==FD_VOTES_ERR_UNKNOWN_VTR);
     494           0 :   ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_ALREADY_VOTED_IDX ] += (ulong)(err==FD_VOTES_ERR_ALREADY_VOTED);
     495           0 : }
     496             : 
     497             : static int
     498           0 : event_level_from_tower( int tower_level ) {
     499           0 :   switch( tower_level ) {
     500           0 :   case FD_TOWER_SLOT_CONFIRMED_PROPAGATED: return FD_EVENT_SLOT_CONFIRMED_LEVEL_PROPAGATED;
     501           0 :   case FD_TOWER_SLOT_CONFIRMED_DUPLICATE:  return FD_EVENT_SLOT_CONFIRMED_LEVEL_DUPLICATE;
     502           0 :   case FD_TOWER_SLOT_CONFIRMED_OPTIMISTIC: return FD_EVENT_SLOT_CONFIRMED_LEVEL_OPTIMISTIC;
     503           0 :   case FD_TOWER_SLOT_CONFIRMED_SUPER:      return FD_EVENT_SLOT_CONFIRMED_LEVEL_SUPER;
     504           0 :   default: FD_LOG_ERR(( "unexpected tower confirmation level %d", tower_level ));
     505           0 :   }
     506           0 : }
     507             : 
     508             : static void
     509             : report_slot_confirmed( ulong             bank_seq,
     510             :                        ulong             slot,
     511             :                        fd_hash_t const * block_id,
     512             :                        ulong             stake,
     513             :                        ulong             total_stake,
     514             :                        int               valid,
     515             :                        int               level,
     516           0 :                        int               forward ) {
     517           0 :   fd_event_slot_confirmed_t ev = {
     518           0 :     .bank_seq    = bank_seq,
     519           0 :     .slot        = slot,
     520           0 :     .stake       = stake,
     521           0 :     .total_stake = total_stake,
     522           0 :     .valid       = valid,
     523           0 :     .level       = level,
     524           0 :     .forward     = forward,
     525           0 :   };
     526           0 :   fd_memcpy( ev.block_id, block_id->uc, sizeof(fd_hash_t) );
     527           0 :   fd_event_report_slot_confirmed( &ev );
     528           0 : }
     529             : 
     530             : static void
     531             : publish_slot_confirmed( fd_tower_tile_t * ctx,
     532             :                         ulong             slot,
     533             :                         fd_hash_t const * block_id,
     534           0 :                         ulong             total_stake ) {
     535             : 
     536           0 :   fd_tower_blk_t * tower_blk = fd_tower_blocks_query( ctx->tower, slot );
     537           0 :   fd_ghost_blk_t * ghost_blk = fd_ghost_query( ctx->ghost, block_id );
     538           0 :   fd_votes_blk_t * votes_blk = fd_votes_query( ctx->votes, slot, block_id );
     539           0 :   if( FD_UNLIKELY( !votes_blk ) ) return;
     540             : 
     541           0 :   static double const ratios[FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT] = FD_TOWER_SLOT_CONFIRMED_RATIOS;
     542           0 :   int const           levels[FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT] = FD_TOWER_SLOT_CONFIRMED_LEVELS;
     543           0 :   for( int i = 0; i < FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT; i++ ) {
     544           0 :     if( FD_LIKELY( fd_uchar_extract_bit( votes_blk->flags, i ) ) ) continue; /* already contiguously confirmed */
     545           0 :     double ratio = (double)votes_blk->stake / (double)total_stake;
     546           0 :     if( FD_LIKELY( ratio <= ratios[i] ) ) break; /* threshold not met */
     547             : 
     548             :     /* If the ghost_blk is missing, then we know this is a forward
     549             :        confirmation (ie. we haven't replayed the block yet). */
     550             : 
     551           0 :     if( FD_UNLIKELY( !ghost_blk ) ) {
     552           0 :       if( fd_uchar_extract_bit( votes_blk->flags, i+4 ) ) continue; /* already forward confirmed */
     553           0 :       votes_blk->flags = fd_uchar_set_bit( votes_blk->flags, i+4 );
     554           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 } } } );
     555           0 :       report_slot_confirmed( 0UL, votes_blk->key.slot, &votes_blk->key.block_id, votes_blk->stake, total_stake, 1 /* valid */, event_level_from_tower( levels[ i ] ), 1 /* forward */ );
     556             : 
     557             :       /* If we have a tower_blk for the slot when the ghost_blk is
     558             :          missing, this implies we replayed an equivocating block_id that
     559             :          is not the confirmed_block_id.  This is only relevant for the
     560             :          duplicate confirmed level.  */
     561             : 
     562           0 :       if( FD_UNLIKELY( levels[i]==FD_TOWER_SLOT_CONFIRMED_DUPLICATE && tower_blk ) ) {
     563           0 :         FD_TEST( 0!=memcmp( &tower_blk->replayed_block_id, &votes_blk->key.block_id, sizeof(fd_hash_t) ) );
     564           0 :         tower_blk->confirmed          = 1;
     565           0 :         tower_blk->confirmed_block_id = votes_blk->key.block_id;
     566           0 :         FD_BASE58_ENCODE_32_BYTES( tower_blk->replayed_block_id.uc, eqvoc_blk_id );
     567           0 :         FD_LOG_DEBUG(( "[%s] equivocation detected via forward-confirmed block id mismatch (replayed before confirmed). slot: %lu. block_id: %s", __func__, votes_blk->key.slot, eqvoc_blk_id ));
     568           0 :         fd_ghost_eqvoc( ctx->ghost, &tower_blk->replayed_block_id );
     569           0 :       }
     570           0 :       continue;
     571           0 :     }
     572             : 
     573             :     /* Otherwise if they are present, then we know this is not a forward
     574             :        confirmation and thus we have replayed and confirmed the block,
     575             :        which also implies we have replayed and confirmed all its
     576             :        ancestors.  So we publish confirmations for all its ancestors
     577             :        (short-circuiting at the first ancestor already confirmed).
     578             : 
     579             :        We use ghost to walk up the ancestry and also mark ghost and
     580             :        tower blocks as confirmed as we walk if this is the duplicate
     581             :        confirmation level. */
     582             : 
     583           0 :     fd_ghost_blk_t * ghost_anc = ghost_blk;
     584           0 :     fd_tower_blk_t * tower_anc = tower_blk;
     585           0 :     fd_votes_blk_t * votes_anc = votes_blk;
     586           0 :     while( FD_LIKELY( ghost_anc ) ) {
     587             : 
     588           0 :       tower_anc = fd_tower_blocks_query( ctx->tower, ghost_anc->slot );
     589           0 :       votes_anc = fd_votes_query( ctx->votes, ghost_anc->slot, &ghost_anc->id );
     590           0 :       if( FD_UNLIKELY( !tower_anc || !votes_anc ) ) break;
     591             : 
     592             :       /* Terminate at the first ancestor that has already reached this
     593             :          confirmation level. */
     594             : 
     595           0 :       if( FD_LIKELY( fd_uchar_extract_bit( votes_anc->flags, i ) ) ) break;
     596             : 
     597             :       /* Mark the ancestor as confirmed at this level.  If this is the
     598             :          duplicate confirmation level, also mark the ghost and tower
     599             :          blocks as confirmed. */
     600             : 
     601           0 :       votes_anc->flags = fd_uchar_set_bit( votes_anc->flags, i );
     602           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 } } } );
     603           0 :       report_slot_confirmed( ghost_anc->bank_seq, ghost_anc->slot, &ghost_anc->id, votes_anc->stake, total_stake, ghost_anc->valid, event_level_from_tower( levels[ i ] ), 0 /* not forward */ );
     604           0 :       if( FD_UNLIKELY( levels[i]==FD_TOWER_SLOT_CONFIRMED_PROPAGATED ) ) {
     605           0 :         tower_anc->propagated = 1;
     606           0 :       }
     607           0 :       if( FD_UNLIKELY( levels[i]==FD_TOWER_SLOT_CONFIRMED_DUPLICATE ) ) {
     608           0 :         tower_anc->confirmed          = 1;
     609           0 :         tower_anc->confirmed_block_id = ghost_anc->id;
     610           0 :         fd_ghost_confirm( ctx->ghost, &ghost_anc->id );
     611           0 :         if( FD_UNLIKELY( memcmp( &tower_anc->replayed_block_id, &ghost_anc->id, sizeof(fd_hash_t) ) ) ) {
     612           0 :           FD_BASE58_ENCODE_32_BYTES( tower_anc->replayed_block_id.uc, eqvoc_blk_id );
     613           0 :           FD_LOG_DEBUG(( "[%s] equivocation detected via ancestor duplicate confirmation. slot: %lu. block_id: %s", __func__, ghost_anc->slot, eqvoc_blk_id ));
     614           0 :           fd_ghost_eqvoc( ctx->ghost, &tower_anc->replayed_block_id );
     615           0 :         }
     616           0 :       }
     617             : 
     618             :       /* Walk up to next ancestor. */
     619             : 
     620           0 :       ghost_anc = fd_ghost_parent( ctx->ghost, ghost_anc );
     621           0 :     }
     622           0 :   }
     623           0 : }
     624             : 
     625             : static void
     626             : publish_slot_done( fd_tower_tile_t *            ctx,
     627             :                    fd_replay_slot_completed_t * slot_completed,
     628             :                    fd_tower_out_t *             out,
     629             :                    int                          found,
     630             :                    ulong                        our_vote_acct_bal,
     631             :                    ulong                        tsorig FD_PARAM_UNUSED,
     632           0 :                    fd_stem_context_t *          stem FD_PARAM_UNUSED ) {
     633             : 
     634           0 :   publish_t * pub = publishes_push_head_nocopy( ctx->publishes );
     635           0 :   pub->sig = FD_TOWER_SIG_SLOT_DONE;
     636             : 
     637           0 :   fd_tower_slot_done_t * msg = &pub->msg.slot_done;
     638           0 :   msg->replay_slot           = slot_completed->slot;
     639           0 :   msg->active_fork_cnt       = fd_ghost_width( ctx->ghost );
     640           0 :   msg->vote_slot             = out->vote_slot;
     641           0 :   msg->reset_slot            = out->reset_slot;
     642           0 :   msg->reset_block_id        = out->reset_block_id;
     643           0 :   msg->root_slot             = out->root_slot;
     644           0 :   msg->root_block_id         = out->root_block_id;
     645           0 :   msg->replay_bank_idx       = slot_completed->bank_idx;
     646           0 :   msg->vote_acct_bal         = our_vote_acct_bal;
     647             : 
     648           0 :   ulong       authority_idx = ULONG_MAX;
     649           0 :   fd_pubkey_t authority[1];
     650           0 :   fd_pubkey_t identity[1];
     651             :   /* Refuse to vote if we don't have a matching vote authority key */
     652           0 :   int found_authority  = found && vote_account_config( ctx, ctx->our_vote_acct, ctx->our_vote_acct_sz, slot_completed->epoch, authority, &authority_idx, identity );
     653             :   /* Refuse to vote if our node identity does not match the one
     654             :      specified in the vote account (hot spare check) */
     655           0 :   int identity_matches = found_authority && fd_pubkey_eq( identity, ctx->identity_key );
     656           0 :   if( FD_LIKELY( out->vote_slot!=ULONG_MAX &&
     657           0 :                  found_authority &&
     658           0 :                  identity_matches &&
     659           0 :                  !fd_tower_vote_empty( ctx->tower->votes ) ) ) {
     660             :     /* The reason to use a historical blockhash and not the most recent
     661             :        one is because if a vote txn lands on another validator, they
     662             :        may not have finished processing the slot and therefore the
     663             :        newest blockhash may not be available to the leader yet; this is
     664             :        especially true for the first leader block in a rotation. */
     665           0 :     msg->has_vote_txn = 1;
     666           0 :     fd_txn_p_t       txn[1];
     667           0 :     fd_tower_blk_t * parent_tower_blk = fd_tower_blocks_query( ctx->tower, slot_completed->parent_slot );
     668           0 :     FD_TEST( parent_tower_blk );
     669           0 :     fd_hash_t const * recent_blockhash = &parent_tower_blk->block_hash;
     670           0 :     fd_tower_to_vote_txn( ctx->tower, &out->vote_bank_hash, &out->vote_block_id, recent_blockhash, ctx->identity_key, authority, ctx->vote_account, txn );
     671           0 :     FD_TEST( !fd_tower_vote_empty( ctx->tower->votes ) );
     672           0 :     FD_TEST( txn->payload_sz && txn->payload_sz<=FD_TPU_MTU );
     673           0 :     fd_memcpy( msg->vote_txn, txn->payload, txn->payload_sz );
     674           0 :     msg->vote_txn_sz   = txn->payload_sz;
     675           0 :     msg->authority_idx = authority_idx;
     676           0 :   } else {
     677           0 :     msg->has_vote_txn = 0;
     678           0 :   }
     679             : 
     680           0 :   msg->tower_cnt = 0UL; /* FIXME */
     681           0 :   if( FD_LIKELY( found ) ) msg->tower_cnt = fd_tower_with_lat_from_vote_acc( msg->tower, ctx->our_vote_acct, ctx->our_vote_acct_sz );
     682           0 : }
     683             : 
     684             : static void
     685             : publish_slot_ignored( fd_tower_tile_t *            ctx,
     686             :                       fd_replay_slot_completed_t * slot_completed,
     687             :                       ulong                        tsorig FD_PARAM_UNUSED,
     688           0 :                       fd_stem_context_t *          stem FD_PARAM_UNUSED ) {
     689           0 :   publishes_push_head( ctx->publishes, (publish_t){
     690           0 :     .sig = FD_TOWER_SIG_SLOT_IGNORED,
     691           0 :     .msg = { .slot_ignored = { .slot = slot_completed->slot, .bank_idx = slot_completed->bank_idx } }
     692           0 :   });
     693           0 : }
     694             : 
     695             : static void
     696             : publish_slot_rooted( fd_tower_tile_t * ctx,
     697             :                      ulong             slot,
     698           0 :                      fd_hash_t const * block_id ) {
     699           0 :   publishes_push_head( ctx->publishes, (publish_t){
     700           0 :     .sig = FD_TOWER_SIG_SLOT_ROOTED,
     701           0 :     .msg = { .slot_rooted = { .slot = slot, .block_id = *block_id } }
     702           0 :   });
     703           0 : }
     704             : 
     705             : static void
     706             : publish_slot_duplicate( fd_tower_tile_t *                ctx,
     707             :                         fd_gossip_duplicate_shred_t const chunks[static FD_EQVOC_CHUNK_CNT],
     708           0 :                         ulong                            slot ) {
     709           0 :   publish_t * pub = publishes_push_head_nocopy( ctx->publishes );
     710           0 :   pub->sig        = FD_TOWER_SIG_SLOT_DUPLICATE;
     711           0 :   memcpy( pub->msg.slot_duplicate.chunks, chunks, sizeof(pub->msg.slot_duplicate.chunks) );
     712             : 
     713             :   /* If we already have a tower blk for this just-proved duplicate
     714             :      slot, then we know we have replayed one of the equivocating
     715             :      blocks.  So determine:
     716             : 
     717             :      1. whether we already know what is the confirmed block_id
     718             :      2. if our replayed_block_id is the confirmed_block_id
     719             : 
     720             :      If either 1. or 2. are false (with 2. contingent on 1.), then
     721             :      mark the replayed block as eqvoc in ghost. */
     722             : 
     723           0 :   fd_tower_blk_t * tower_blk = fd_tower_blocks_query( ctx->tower, slot );
     724           0 :   int eqvoc = tower_blk && (!tower_blk->confirmed || memcmp( &tower_blk->replayed_block_id, &tower_blk->confirmed_block_id, sizeof(fd_hash_t) ) );
     725           0 :   if( FD_LIKELY( eqvoc ) ) {
     726           0 :     FD_BASE58_ENCODE_32_BYTES( tower_blk->replayed_block_id.uc, eqvoc_blk_id );
     727           0 :     FD_LOG_DEBUG(( "[%s] equivocation detected via duplicate shred proof. slot: %lu. block_id: %s", __func__, slot, eqvoc_blk_id ));
     728           0 :     fd_ghost_eqvoc( ctx->ghost, &tower_blk->replayed_block_id );
     729           0 :   }
     730           0 : }
     731             : 
     732             : static void
     733             : count_vote_acc( fd_tower_tile_t *            ctx,
     734             :                 fd_replay_slot_completed_t * slot_completed,
     735             :                 fd_ghost_blk_t *             ghost_blk,
     736             :                 fd_pubkey_t const *          vote_acc,
     737             :                 ulong                        stake,
     738             :                 uchar const *                data,
     739           0 :                 ulong                        data_sz ) {
     740             : 
     741           0 :   fd_tower_count_vote( ctx->tower, vote_acc, stake, data, data_sz );
     742             : 
     743           0 :   fd_tower_vtr_t const * vtr = fd_tower_vtr_peek_tail_const( ctx->tower->vtrs );
     744             : 
     745             :   /* 1. Update forks with lockouts. */
     746             : 
     747           0 :   fd_tower_lockos_insert( ctx->tower, slot_completed->slot, vote_acc, vtr->votes );
     748             : 
     749             :   /* 2. Count the last vote slot in the vote state towards ghost. */
     750             : 
     751           0 :   ulong vote_slot = fd_tower_vote_empty( vtr->votes ) ? ULONG_MAX : fd_tower_vote_peek_tail_const( vtr->votes )->slot;
     752           0 :   if( FD_LIKELY( vote_slot!=ULONG_MAX && /* has voted */
     753           0 :                  vote_slot>=fd_ghost_root( ctx->ghost )->slot ) ) { /* vote not too old */
     754             : 
     755           0 :     fd_ghost_blk_t * ancestor_blk = fd_ghost_slot_ancestor( ctx->ghost, ghost_blk, vote_slot ); /* FIXME potentially slow */
     756             : 
     757           0 :     if( FD_UNLIKELY( !ancestor_blk ) ) {
     758           0 :       FD_BASE58_ENCODE_32_BYTES( vote_acc->key, vote_acc_cstr );
     759           0 :       FD_LOG_CRIT(( "missing ancestor. replay slot %lu vote slot %lu voter %s", slot_completed->slot, vote_slot, vote_acc_cstr ));
     760           0 :     }
     761             : 
     762           0 :     int ghost_err = fd_ghost_count_vote( ctx->ghost, ancestor_blk, vote_acc, stake, vote_slot );
     763           0 :     update_metrics_ghost( ctx, ghost_err );
     764           0 :   }
     765             : 
     766           0 :   FD_TEST( !fd_vote_account_node_pubkey( data, data_sz, &ctx->id_keys[ctx->vtr_cnt] ) );
     767           0 :   ctx->vote_accs[ctx->vtr_cnt] = *vote_acc;
     768           0 :   ctx->vtr_cnt++;
     769           0 : }
     770             : 
     771             : /* Query all the relevant towers for running Tower rules on this slot:
     772             : 
     773             :    1. staked voter set from banks
     774             :    2. vote accounts (for each staked voter, which contains their tower)
     775             :       from accountsDB. */
     776             : 
     777             : FD_FN_UNUSED ulong
     778             : query_towers( fd_tower_tile_t *            ctx,
     779             :               fd_replay_slot_completed_t * slot_completed,
     780             :               fd_ghost_blk_t *             ghost_blk,
     781             :               int *                        found_our_vote_acct,
     782           0 :               ulong *                      our_vote_acct_bal ) {
     783             : 
     784           0 :   ulong total_stake    = 0UL;
     785           0 :   ulong prev_voter_idx = ULONG_MAX;
     786             : 
     787           0 :   fd_bank_t * bank = fd_banks_bank_query( ctx->banks, slot_completed->bank_idx );
     788           0 :   if( FD_UNLIKELY( !bank ) ) FD_LOG_CRIT(( "invariant violation: bank %lu is missing", slot_completed->bank_idx ));
     789             : 
     790           0 :   fd_top_votes_t const * top_votes_t_2 = fd_bank_top_votes_t_2_query( bank );
     791           0 :   uchar __attribute__((aligned(FD_TOP_VOTES_ITER_ALIGN))) iter_mem[ FD_TOP_VOTES_ITER_FOOTPRINT ];
     792             : 
     793           0 : #define BATCH 64UL
     794           0 :   fd_pubkey_t   vote_accs[ BATCH ];
     795           0 :   ulong         stakes[ BATCH ];
     796           0 :   uchar const * pubkeys[ BATCH ];
     797           0 :   int           writable[ BATCH ];
     798           0 :   fd_acc_t      accs[ BATCH ];
     799             : 
     800           0 :   fd_top_votes_iter_t * iter = fd_top_votes_iter_init( top_votes_t_2, iter_mem );
     801           0 :   while( !fd_top_votes_iter_done( top_votes_t_2, iter ) ) {
     802           0 :     ulong batch_n = 0UL;
     803           0 :     while( !fd_top_votes_iter_done( top_votes_t_2, iter ) && batch_n<BATCH ) {
     804           0 :       uchar is_valid;
     805           0 :       fd_top_votes_iter_ele( top_votes_t_2, iter, &vote_accs[ batch_n ], NULL, &stakes[ batch_n ], NULL, NULL, NULL, &is_valid );
     806           0 :       fd_top_votes_iter_next( top_votes_t_2, iter );
     807           0 :       total_stake += stakes[ batch_n ];
     808           0 :       if( FD_UNLIKELY( !is_valid ) ) continue;
     809           0 :       pubkeys[ batch_n ]  = vote_accs[ batch_n ].uc;
     810           0 :       writable[ batch_n ] = 0;
     811           0 :       batch_n++;
     812           0 :     }
     813           0 :     if( FD_UNLIKELY( !batch_n ) ) continue;
     814             : 
     815           0 :     fd_accdb_acquire( ctx->accdb, bank->accdb_fork_id, batch_n, pubkeys, writable, accs );
     816             : 
     817           0 :     for( ulong j=0UL; j<batch_n; j++ ) {
     818           0 :       FD_TEST( accs[ j ].lamports && fd_vsv_is_correct_size_owner_and_init( accs[ j ].owner, accs[ j ].data, accs[ j ].data_len ) );
     819           0 :       count_vote_acc( ctx, slot_completed, ghost_blk, &vote_accs[ j ], stakes[ j ], accs[ j ].data, accs[ j ].data_len );
     820           0 :       prev_voter_idx = fd_tower_stakes_insert( ctx->tower, slot_completed->slot, &vote_accs[ j ], stakes[ j ], prev_voter_idx );
     821           0 :     }
     822             : 
     823           0 :     fd_accdb_release( ctx->accdb, batch_n, accs );
     824           0 :   }
     825           0 : #undef BATCH
     826             : 
     827             :   /* Reconcile our local tower with the on-chain tower (stored inside
     828             :      our vote account).
     829             : 
     830             :      Skip reconciliation on the first replay_slot_completed if booted
     831             :      with wait_for_supermajority.  This prevents spurious lockout_check
     832             :      failures (slot <= last_vote_slot) and threshold_check failures
     833             :      (deep stale tower with no voter support) */
     834             : 
     835           0 :   *our_vote_acct_bal   = ULONG_MAX;
     836           0 :   *found_our_vote_acct = 0;
     837           0 :   fd_acc_t reconcile_ro = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, ctx->vote_account->uc );
     838           0 :   if( FD_LIKELY( reconcile_ro.lamports ) ) {
     839           0 :     *found_our_vote_acct = 1;
     840           0 :     ctx->our_vote_acct_sz = fd_ulong_min( reconcile_ro.data_len, FD_VOTE_STATE_DATA_MAX );
     841           0 :     *our_vote_acct_bal = reconcile_ro.lamports;
     842           0 :     fd_memcpy( ctx->our_vote_acct, reconcile_ro.data, ctx->our_vote_acct_sz );
     843           0 :     int skip_reconcile = !ctx->init && ctx->wfs;
     844           0 :     if( FD_LIKELY( !skip_reconcile ) ) {
     845           0 :       ulong root;
     846           0 :       fd_tower_vote_remove_all( ctx->scratch_tower );
     847           0 :       fd_tower_from_vote_acc( ctx->scratch_tower, &root, ctx->our_vote_acct, ctx->our_vote_acct_sz );
     848           0 :       fd_tower_reconcile( ctx->tower, ctx->scratch_tower, root );
     849           0 :     } else {
     850           0 :       FD_LOG_NOTICE(( "wait_for_supermajority: skipping tower reconcile on init slot %lu", slot_completed->slot ));
     851           0 :     }
     852           0 :   }
     853           0 :   fd_accdb_unread_one( ctx->accdb, &reconcile_ro );
     854             : 
     855           0 :   return total_stake;
     856           0 : }
     857             : 
     858             : /* validate_vote_txn is the C equivalent of Agave's
     859             :    parse_vote_transaction.  Returns the vote account on success, NULL
     860             :    on failure.  Deserializes the vote instruction into ctx->scratch_ix.
     861             : 
     862             :    https://github.com/anza-xyz/agave/blob/v2.3.7/sdk/src/transaction/versioned/mod.rs#L79 */
     863             : 
     864             : static fd_pubkey_t const *
     865             : validate_vote_txn( fd_tower_tile_t * ctx,
     866             :                    fd_txn_t const *  txn,
     867           0 :                    uchar const *     payload ) {
     868             : 
     869           0 :   if( FD_UNLIKELY( !txn->instr_cnt ) ) return NULL;
     870           0 :   fd_txn_instr_t const * instr = &txn->instr[ 0 ];
     871             : 
     872           0 :   fd_pubkey_t const * accs = (fd_pubkey_t const *)fd_type_pun_const( payload + txn->acct_addr_off );
     873           0 :   if( FD_UNLIKELY( 0!=memcmp( &accs[ instr->program_id ], &fd_solana_vote_program_id, FD_TXN_ACCT_ADDR_SZ ) ) ) return NULL;
     874             : 
     875           0 :   uchar const * instr_data = payload + instr->data_off;
     876           0 :   if( FD_UNLIKELY( !fd_vote_instruction_deserialize( &ctx->scratch_ix, instr_data, instr->data_sz ) ) ) return NULL;
     877             : 
     878           0 :   if( FD_UNLIKELY( !instr->acct_cnt ) ) return NULL;
     879           0 :   uchar const * instr_accts = payload + instr->acct_off;
     880           0 :   return (fd_pubkey_t const *)fd_type_pun_const( &accs[ instr_accts[ 0 ] ] );
     881           0 : }
     882             : 
     883             : /* count_vote_txn counts vote txns from Gossip, TPU and Replay.  Note
     884             :    these txns have already been parsed and sigverified before they are
     885             :    sent to tower.  In addition, vote txns coming from Replay have also
     886             :    been successfully executed.  They are counted towards hfork and votes
     887             :    (see point 2 in the top-level documentation). */
     888             : 
     889             : static void
     890             : count_vote_txn( fd_tower_tile_t * ctx,
     891             :                 fd_txn_t const *  txn,
     892           0 :                 uchar const *     payload ) {
     893             : 
     894             :   /* We are a little stricter than Agave here because Agave only does
     895             :      the is_simple_vote check on replay vote txns, whereas we are doing
     896             :      the check on both replay and gossip / TPU vote txns.
     897             : 
     898             :      Being a little stricter with non-replay vote txns is ok because
     899             :      even if we drop some votes that Agave would consider valid
     900             :      (unlikely unless they were sent by an actively malicious
     901             :      validator), gossip votes are in general considered unreliable and
     902             :      ultimately consensus (fork choice, tower rules, rooting, etc.) is
     903             :      reached with vote accounts updated by replaying blocks.
     904             : 
     905             :      See: https://github.com/anza-xyz/agave/blob/v4.1.0-beta.1/runtime/src/bank_utils.rs#L54 */
     906             : 
     907           0 :   if( FD_UNLIKELY( !fd_txn_is_simple_vote_transaction( txn, payload ) ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_SIMPLE_VOTE_IDX ]++; return; }
     908             : 
     909           0 :   fd_pubkey_t const * vote_acc = validate_vote_txn( ctx, txn, payload );
     910           0 :   if( FD_UNLIKELY( !vote_acc ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_DESER_IDX ]++; return; }
     911             : 
     912             :   /* Filter any non-TowerSync vote instructions.  For gossip / TPU this
     913             :      filters deprecated vote kinds; for replay this shouldn't happen
     914             :      after SIMD-0138 is activated. */
     915             : 
     916             :   /* TODO SECURITY ensure SIMD-0138 is activated */
     917             : 
     918           0 :   if( FD_UNLIKELY( ctx->scratch_ix.discriminant!=fd_vote_instruction_enum_tower_sync && ctx->scratch_ix.discriminant!=fd_vote_instruction_enum_tower_sync_switch ) ) {
     919           0 :     ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_TOWER_SYNC_IDX ]++;
     920           0 :     return;
     921           0 :   }
     922             : 
     923           0 :   fd_tower_sync_t * tower_sync = &ctx->scratch_ix.tower_sync; /* this is safe, because TowerSyncSwitch is the same as TowerSync except with 32-bytes appended */
     924           0 :   if( FD_UNLIKELY(  tower_sync->lockouts_cnt>FD_TOWER_VOTE_MAX ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX  ]++; return; }
     925           0 :   if( FD_UNLIKELY( !tower_sync->lockouts_cnt                   ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_EMPTY_TOWER_IDX ]++; return; }
     926             : 
     927           0 :   fd_tower_vote_remove_all( ctx->scratch_tower );
     928           0 :   for( ulong i = 0; i < tower_sync->lockouts_cnt; i++ ) {
     929           0 :     fd_vote_lockout_t const * lockout = deq_fd_vote_lockout_t_peek_index_const( tower_sync->lockouts, i );
     930           0 :     fd_tower_vote_push_tail( ctx->scratch_tower, (fd_tower_vote_t){ .slot = lockout->slot, .conf = lockout->confirmation_count } );
     931           0 :   }
     932             : 
     933             :   /* Validate the tower. */
     934             : 
     935           0 :   fd_tower_vote_t const * prev = fd_tower_vote_peek_head_const( ctx->scratch_tower );
     936           0 :   if( FD_UNLIKELY( prev->conf > FD_TOWER_VOTE_MAX ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]++; return; }
     937             : 
     938           0 :   fd_tower_vote_iter_t iter = fd_tower_vote_iter_next( ctx->scratch_tower, fd_tower_vote_iter_init( ctx->scratch_tower ) );
     939           0 :   for( ; !fd_tower_vote_iter_done( ctx->scratch_tower, iter ); iter = fd_tower_vote_iter_next( ctx->scratch_tower, iter ) ) {
     940           0 :     fd_tower_vote_t const * vote = fd_tower_vote_iter_ele( ctx->scratch_tower, iter );
     941           0 :     if( FD_UNLIKELY( vote->slot <= prev->slot        ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]++; return; }
     942           0 :     if( FD_UNLIKELY( vote->conf >= prev->conf        ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]++; return; }
     943           0 :     if( FD_UNLIKELY( vote->conf >  FD_TOWER_VOTE_MAX ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]++; return; }
     944           0 :     prev = vote;
     945           0 :   }
     946             : 
     947           0 :   if( FD_UNLIKELY( 0==memcmp( &tower_sync->block_id, &hash_null, sizeof(fd_hash_t) ) ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_UNKNOWN_BLOCK_ID_IDX ]++; return; };
     948             : 
     949             :   /* The vote txn contains a block id and bank hash for their last vote
     950             :      slot in the tower.  Agave always counts the last vote.
     951             : 
     952             :      https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L476-L487 */
     953             : 
     954           0 :   fd_tower_vote_t const * their_last_vote = fd_tower_vote_peek_tail_const( ctx->scratch_tower );
     955           0 :   fd_hash_t const *       their_block_id  = &tower_sync->block_id;
     956           0 :   fd_hash_t const *       their_bank_hash = &tower_sync->hash;
     957             : 
     958             :   /* Return early if their last vote is too old. */
     959             : 
     960           0 :   if( FD_UNLIKELY( their_last_vote->slot <= ctx->tower->root ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_TOO_OLD_IDX ]++; return; }
     961             : 
     962             :   /* Determine the epoch of the vote slot and look up the voter's stake
     963             :      for that epoch.  Votes can be at most 1 epoch ahead of root. */
     964             : 
     965           0 :   fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, their_last_vote->slot );
     966           0 :   if( FD_UNLIKELY( !lsched ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_DESER_IDX ]++; return; } /* no leader schedule to resolve the vote's epoch */
     967           0 :   ulong vote_epoch = lsched->epoch;
     968             : 
     969           0 :   epoch_vtr_t *     epoch_vtr_pool = NULL;
     970           0 :   epoch_vtr_map_t * epoch_vtr_map  = NULL;
     971           0 :   ulong             total_stake    = 0UL;
     972           0 :   if(      FD_LIKELY( vote_epoch==ctx->root_epoch     ) ) { epoch_vtr_pool = ctx->root_epoch_vtr_pool; epoch_vtr_map = ctx->root_epoch_vtr_map; total_stake = ctx->root_epoch_total_stake; }
     973           0 :   else if( FD_LIKELY( vote_epoch==ctx->root_epoch + 1 ) ) { epoch_vtr_pool = ctx->next_epoch_vtr_pool; epoch_vtr_map = ctx->next_epoch_vtr_map; total_stake = ctx->next_epoch_total_stake; }
     974           0 :   else                                                    { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_STAKED_IDX ]++; return;   }
     975           0 :   epoch_vtr_t * vtr = epoch_vtr_map_ele_query( epoch_vtr_map, vote_acc, NULL, epoch_vtr_pool );
     976           0 :   if( FD_UNLIKELY( !vtr ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_STAKED_IDX ]++; return; }
     977             : 
     978             :   /* Verify the authorized voter for this vote account at vote_epoch is
     979             :      among the txn signers.  Mirrors Agave's cluster_info_vote_listener
     980             :      check.  authorized_voter is cached on the epoch_vtr by
     981             :      query_voters; an all-zero value means we couldn't read it. */
     982             : 
     983           0 :   if( FD_UNLIKELY( 0==memcmp( &vtr->auth_vtr, &pubkey_null, sizeof(fd_pubkey_t) ) ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_SIGNER_IDX ]++; return; }
     984           0 :   fd_pubkey_t const * accs = (fd_pubkey_t const *)fd_type_pun_const( payload + txn->acct_addr_off );
     985           0 :   int signer_ok = 0;
     986           0 :   for( ulong i=0; i<txn->signature_cnt; i++ ) {
     987           0 :     if( 0==memcmp( &accs[i], &vtr->auth_vtr, sizeof(fd_pubkey_t) ) ) { signer_ok = 1; break; }
     988           0 :   }
     989           0 :   if( FD_UNLIKELY( !signer_ok ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_SIGNER_IDX ]++; return; }
     990             : 
     991             :   /* The txn passed all per-txn validation; we will now count its
     992             :      individual vote slots (per-slot metrics below). */
     993             : 
     994           0 :   ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_SUCCESS_IDX ]++;
     995             : 
     996           0 :   int hfork_err = fd_hfork_count_vote( ctx->hfork, vote_acc, their_block_id, their_bank_hash, their_last_vote->slot, vtr->stake, total_stake );
     997           0 :   update_metrics_hfork( ctx, hfork_err, their_last_vote->slot, their_block_id );
     998             : 
     999           0 :   int votes_err = fd_votes_count_vote( ctx->votes, vote_acc, vtr->stake, their_last_vote->slot, their_block_id );
    1000           0 :   update_metrics_vote_slot( ctx, votes_err );
    1001           0 :   if( FD_LIKELY( votes_err==FD_VOTES_SUCCESS ) ) publish_slot_confirmed( ctx, their_last_vote->slot, their_block_id, total_stake );
    1002             : 
    1003             :   /* Agave decides to count intermediate vote slots in the tower iff:
    1004             : 
    1005             :      1. they've replayed the slot
    1006             :      2. their replay bank hash matches the vote's bank hash.
    1007             : 
    1008             :      This guarantees the intermediate slots they are counting are in
    1009             :      fact for the correct ancestry (in case of equivocation).  We do the
    1010             :      same thing, but using block ids instead of bank hashes.
    1011             : 
    1012             :      It's possible we haven't yet replayed this slot being voted on
    1013             :      because gossip votes can be ahead of our replay.
    1014             : 
    1015             :      https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L483-L487 */
    1016             : 
    1017           0 :   if( FD_UNLIKELY( !fd_tower_blocks_query( ctx->tower, their_last_vote->slot ) ) ) { ctx->metrics.gate_int[ FD_METRICS_ENUM_VOTE_INTERMEDIATE_GATE_V_UNKNOWN_SLOT_IDX ]++; return; }; /* we haven't replayed this block yet */
    1018           0 :   fd_hash_t const * our_block_id = fd_tower_blocks_canonical_block_id( ctx->tower, their_last_vote->slot );
    1019           0 :   if( FD_UNLIKELY( 0!=memcmp( our_block_id, their_block_id, sizeof(fd_hash_t) ) ) ) { ctx->metrics.gate_int[ FD_METRICS_ENUM_VOTE_INTERMEDIATE_GATE_V_UNKNOWN_BLOCK_ID_IDX ]++; return; } /* we don't recognize this block id */
    1020             : 
    1021             :   /* At this point, we know we have replayed the same slot and also have
    1022             :      a matching block id, so we can count the intermediate votes. */
    1023             : 
    1024           0 :   ctx->metrics.gate_int[ FD_METRICS_ENUM_VOTE_INTERMEDIATE_GATE_V_PROCEED_IDX ]++;
    1025             : 
    1026           0 :   int skipped_last_vote = 0;
    1027           0 :   for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init_rev( ctx->scratch_tower       );
    1028           0 :                                   !fd_tower_vote_iter_done_rev( ctx->scratch_tower, iter );
    1029           0 :                             iter = fd_tower_vote_iter_prev    ( ctx->scratch_tower, iter ) ) {
    1030           0 :     if( FD_UNLIKELY( !skipped_last_vote ) ) { skipped_last_vote = 1; continue; }
    1031           0 :     fd_tower_vote_t const * their_intermediate_vote = fd_tower_vote_iter_ele_const( ctx->scratch_tower, iter );
    1032             : 
    1033             :     /* If we don't recognize an intermediate vote slot in their tower,
    1034             :        it means their tower either:
    1035             : 
    1036             :        1. Contains intermediate vote slots that are too old (older than
    1037             :           our root) so we already pruned them for tower_forks.  Normally
    1038             :           if the descendant (last vote slot) is in tower forks, then all
    1039             :           of its ancestors should be in there too.
    1040             : 
    1041             :        2. Is invalid.  Even though at this point we have successfully
    1042             :           sigverified and deserialized their vote txn, the tower itself
    1043             :           might still be invalid because unlike TPU vote txns, we have
    1044             :           not plumbed through the vote program, but obviously gossip
    1045             :           votes do not so we need to do some light validation here.
    1046             : 
    1047             :        We could throwaway this voter's tower, but we handle it the same
    1048             :        way as Agave which is to just skip this intermediate vote slot:
    1049             : 
    1050             :        https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L513-L518 */
    1051             : 
    1052           0 :     if( FD_UNLIKELY( their_intermediate_vote->slot <= ctx->tower->root ) ) { ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_TOO_OLD_IDX ]++; continue; }
    1053             : 
    1054           0 :     fd_tower_blk_t * tower_blk = fd_tower_blocks_query( ctx->tower, their_intermediate_vote->slot );
    1055           0 :     if( FD_UNLIKELY( !tower_blk ) ) { ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_UNKNOWN_SLOT_IDX ]++; continue; }
    1056             : 
    1057             :     /* Otherwise, we count the vote using our own block id for that slot
    1058             :        (again, mirroring what Agave does albeit with bank hashes).
    1059             : 
    1060             :        Agave uses the current root bank's total stake when counting vote
    1061             :        txns from gossip / replay:
    1062             : 
    1063             :        https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L500 */
    1064             : 
    1065           0 :     fd_hash_t const * intermediate_block_id = fd_tower_blocks_canonical_block_id( ctx->tower, their_intermediate_vote->slot );
    1066           0 :     int votes_err = fd_votes_count_vote( ctx->votes, vote_acc, vtr->stake, their_intermediate_vote->slot, intermediate_block_id );
    1067           0 :     update_metrics_vote_slot( ctx, votes_err );
    1068           0 :     if( FD_LIKELY( votes_err==FD_VOTES_SUCCESS ) ) publish_slot_confirmed( ctx, their_intermediate_vote->slot, intermediate_block_id, total_stake );
    1069           0 :   }
    1070           0 : }
    1071             : 
    1072             : /* Query the staked voters in the provided epoch:
    1073             : 
    1074             :    1. identity keys (aka. node pubkeys)
    1075             :    2. vote account addresses
    1076             :    3. associated stake (for the epoch)
    1077             :    4. authorized voter (for the epoch) */
    1078             : 
    1079             : static ulong
    1080             : query_epoch_voters( fd_tower_tile_t *      ctx,
    1081             :                     ulong                  epoch,
    1082             :                     fd_accdb_fork_id_t     fork_id,
    1083             :                     fd_top_votes_t const * top_votes,
    1084             :                     epoch_vtr_t *          pool,
    1085             :                     epoch_vtr_map_t *      map,
    1086           0 :                     int                    update_id_keys_vote_accs ) {
    1087             : 
    1088           0 :   epoch_vtr_pool_reset( pool );
    1089           0 :   epoch_vtr_map_reset( map );
    1090           0 :   ulong total_stake = 0UL;
    1091           0 :   for( fd_top_votes_iter_t * iter = fd_top_votes_iter_init( top_votes, ctx->iter_mem );
    1092           0 :                                    !fd_top_votes_iter_done( top_votes, iter );
    1093           0 :                                     fd_top_votes_iter_next( top_votes, iter ) ) {
    1094           0 :     fd_pubkey_t pubkey;
    1095           0 :     ulong       stake;
    1096           0 :     fd_top_votes_iter_ele( top_votes, iter, &pubkey, NULL, &stake, NULL, NULL, NULL, NULL );
    1097           0 :     FD_TEST( stake>0UL ); /* top_votes only holds staked voters */
    1098           0 :     total_stake += stake;
    1099           0 :     epoch_vtr_t * vtr = epoch_vtr_pool_ele_acquire( pool );
    1100           0 :     vtr->vote_acc = pubkey;
    1101           0 :     vtr->stake    = stake;
    1102           0 :     memset( &vtr->auth_vtr, 0, sizeof(fd_pubkey_t) );
    1103             : 
    1104             :     /* Cache the authorized voter for target_epoch.  Leaves
    1105             :        auth_vtr all-zero if the vote account is unreadable —
    1106             :        count_vote_txn will reject txns whose signer can't match. */
    1107             : 
    1108           0 :     fd_acc_t ro = fd_accdb_read_one( ctx->accdb, fork_id, pubkey.uc );
    1109           0 :     if( FD_LIKELY( ro.lamports ) ) {
    1110           0 :       fd_pubkey_t identity[1];
    1111           0 :       ulong dummy_idx;
    1112           0 :       vote_account_config( ctx, ro.data, ro.data_len, epoch, &vtr->auth_vtr, &dummy_idx, identity );
    1113           0 :       if( update_id_keys_vote_accs ) {
    1114           0 :         FD_TEST( 0==fd_vote_account_node_pubkey( ro.data, ro.data_len, &ctx->id_keys[ctx->vtr_cnt] ) ); /* check vote account is not corrupt */
    1115           0 :         ctx->vote_accs[ctx->vtr_cnt] = pubkey;
    1116           0 :         ctx->vtr_cnt++;
    1117           0 :       }
    1118           0 :     }
    1119           0 :     fd_accdb_unread_one( ctx->accdb, &ro );
    1120             : 
    1121           0 :     epoch_vtr_map_ele_insert( map, vtr, pool );
    1122           0 :   }
    1123           0 :   return total_stake;
    1124           0 : }
    1125             : 
    1126             : /* Update the cached voters for both the currently rooted epoch and the
    1127             :    next epoch, to allow processing vote transactions for vote slots that
    1128             :    span both these epochs. */
    1129             : 
    1130             : FD_FN_UNUSED void
    1131             : query_voters( fd_tower_tile_t *            ctx,
    1132             :               fd_replay_slot_completed_t * slot_completed,
    1133           0 :               ulong                        epoch ) {
    1134           0 :   if( FD_LIKELY( ctx->banks ) ) {
    1135           0 :     fd_bank_t * bank = fd_banks_bank_query( ctx->banks, slot_completed->bank_idx );
    1136           0 :     if( FD_UNLIKELY( !bank ) ) FD_LOG_CRIT(( "invariant violation: bank %lu is missing", slot_completed->bank_idx ));
    1137             : 
    1138           0 :     ctx->vtr_cnt = 0;
    1139           0 :     ctx->root_epoch_total_stake = query_epoch_voters( ctx, epoch,     bank->accdb_fork_id, fd_bank_top_votes_t_2_query( bank ), ctx->root_epoch_vtr_pool, ctx->root_epoch_vtr_map, 1 );
    1140           0 :     ctx->next_epoch_total_stake = query_epoch_voters( ctx, epoch+1UL, bank->accdb_fork_id, fd_bank_top_votes_t_1_query( bank ), ctx->next_epoch_vtr_pool, ctx->next_epoch_vtr_map, 0 );
    1141           0 :   }
    1142           0 :   ctx->root_epoch = epoch;
    1143             : 
    1144           0 :   fd_eqvoc_update_voters( ctx->eqvoc, ctx->id_keys,   ctx->vtr_cnt );
    1145           0 :   fd_hfork_update_voters( ctx->hfork, ctx->vote_accs, ctx->vtr_cnt );
    1146           0 :   fd_votes_update_voters( ctx->votes, ctx->vote_accs, ctx->vtr_cnt );
    1147           0 : }
    1148             : 
    1149             : static void
    1150             : replay_slot_completed( fd_tower_tile_t *            ctx,
    1151             :                        fd_replay_slot_completed_t * slot_completed,
    1152             :                        ulong                        tsorig,
    1153           0 :                        fd_stem_context_t *          stem ) {
    1154             : 
    1155             :   /* Sanity checks. */
    1156             : 
    1157           0 :   FD_TEST( 0!=memcmp( &slot_completed->block_id, &hash_null, sizeof(fd_hash_t) ) );
    1158             : 
    1159           0 :   fd_tower_stakes_remove( ctx->tower, slot_completed->slot ); /* no-op for 99% of cases except for eqvoc */
    1160           0 :   fd_tower_vtr_t * tower_voters = ctx->tower->vtrs;
    1161           0 :   fd_tower_vtr_remove_all( tower_voters );
    1162           0 :   ctx->vtr_cnt = 0;
    1163             : 
    1164             :   /* Insert into ghost. */
    1165             : 
    1166           0 :   fd_ghost_blk_t * ghost_blk;
    1167           0 :   if( FD_UNLIKELY( !ctx->init ) ) {
    1168             : 
    1169             :     /* This is the first replay_slot_completed (ie. the snapshot or
    1170             :        genesis slot), so initialize the ghost root. */
    1171             : 
    1172           0 :     ghost_blk = fd_ghost_init( ctx->ghost, slot_completed->bank_seq, slot_completed->slot, &slot_completed->block_id );
    1173             : 
    1174           0 :   } else if ( FD_UNLIKELY( !fd_ghost_query( ctx->ghost, &slot_completed->parent_block_id ) )) {
    1175             : 
    1176             :   /* Due to asynchronous frag processing, it's possible this block from
    1177             :      replay_slot_completed is on a minority fork Tower already pruned
    1178             :      after publishing a new root. */
    1179             : 
    1180           0 :     ctx->metrics.ignored_cnt++;
    1181           0 :     ctx->metrics.ignored_slot = slot_completed->slot;
    1182           0 :     publish_slot_ignored( ctx, slot_completed, tsorig, stem );
    1183           0 :     report_slot_confirmed( slot_completed->bank_seq, slot_completed->slot, &slot_completed->block_id, 0UL /* stake */, 0UL /* total_stake */, 1 /* valid */, FD_EVENT_SLOT_CONFIRMED_LEVEL_IGNORED, 0 /* not forward */ );
    1184           0 :     return; /* short-circuit processing this slot */
    1185             : 
    1186           0 :   } else {
    1187             : 
    1188             :     /* Common case. */
    1189             : 
    1190           0 :     ghost_blk = fd_ghost_insert( ctx->ghost, slot_completed->bank_seq, slot_completed->slot, &slot_completed->block_id, &slot_completed->parent_block_id );
    1191           0 :   }
    1192           0 :   FD_TEST( ghost_blk );
    1193             : 
    1194             :   /* Insert into tower. */
    1195             : 
    1196           0 :   fd_tower_blk_t * eqvoc_tower_blk = NULL;
    1197           0 :   if( FD_UNLIKELY( eqvoc_tower_blk = fd_tower_blocks_query( ctx->tower, slot_completed->slot ) ) ) {
    1198             : 
    1199             :     /* If eqvoc_tower_blk is not NULL, then we know this slot
    1200             :        equivocates (there are multiple blocks in the slot).
    1201             : 
    1202             :        Replay processes at most 2 equivocating blocks for a given slot,
    1203             :        and the latter block is guaranteed to be confirmed.
    1204             : 
    1205             :        At this point, we know we are processing the latter block, so we
    1206             :        record that in the tower_blk. */
    1207             : 
    1208           0 :     fd_tower_lockos_remove( ctx->tower, slot_completed->slot );
    1209             : 
    1210           0 :     ctx->metrics.eqvoc_cnt++;
    1211           0 :     ctx->metrics.eqvoc_slot = fd_ulong_max( ctx->metrics.eqvoc_slot, slot_completed->slot );
    1212             : 
    1213           0 :     fd_ghost_confirm( ctx->ghost, &slot_completed->block_id );
    1214           0 :     FD_BASE58_ENCODE_32_BYTES( eqvoc_tower_blk->replayed_block_id.uc, eqvoc_blk_id );
    1215           0 :     FD_LOG_DEBUG(( "[%s] equivocation detected via duplicate replay. slot: %lu. block_id: %s", __func__, slot_completed->slot, eqvoc_blk_id ));
    1216           0 :     fd_ghost_eqvoc( ctx->ghost, &eqvoc_tower_blk->replayed_block_id );
    1217             : 
    1218           0 :     eqvoc_tower_blk->parent_slot       = slot_completed->parent_slot;
    1219           0 :     eqvoc_tower_blk->bank_hash         = slot_completed->bank_hash;
    1220           0 :     eqvoc_tower_blk->block_hash        = slot_completed->block_hash;
    1221           0 :     eqvoc_tower_blk->replayed_block_id = slot_completed->block_id;
    1222           0 :   } else {
    1223             : 
    1224             :     /* Otherwise this is the first replay of this block, so insert a new
    1225             :        tower_blk. */
    1226             : 
    1227           0 :     fd_tower_blk_t * tower_blk   = fd_tower_blocks_insert( ctx->tower, slot_completed->slot, slot_completed->parent_slot );
    1228           0 :     tower_blk->parent_slot       = slot_completed->parent_slot;
    1229           0 :     tower_blk->epoch             = slot_completed->epoch;
    1230           0 :     tower_blk->bank_hash         = slot_completed->bank_hash;
    1231           0 :     tower_blk->block_hash        = slot_completed->block_hash;
    1232           0 :     tower_blk->replayed          = 1;
    1233           0 :     tower_blk->replayed_block_id = slot_completed->block_id;
    1234           0 :     tower_blk->voted             = 0;
    1235           0 :     tower_blk->confirmed         = 0;
    1236           0 :     tower_blk->leader            = slot_completed->is_leader;
    1237           0 :     tower_blk->propagated        = 0;
    1238             : 
    1239             :     /* Set the prev_leader_slot. */
    1240             : 
    1241           0 :     if( FD_UNLIKELY( tower_blk->leader ) ) {
    1242           0 :       tower_blk->prev_leader_slot = slot_completed->slot;
    1243           0 :     } else if ( FD_UNLIKELY( ghost_blk==fd_ghost_root( ctx->ghost ) ) ) {
    1244           0 :       tower_blk->prev_leader_slot = ULONG_MAX;
    1245           0 :     } else {
    1246           0 :       fd_tower_blk_t * parent_tower_blk = fd_tower_blocks_query( ctx->tower, slot_completed->parent_slot );
    1247           0 :       FD_TEST( parent_tower_blk );
    1248           0 :       tower_blk->prev_leader_slot = parent_tower_blk->prev_leader_slot;
    1249           0 :     }
    1250             : 
    1251           0 :     fd_votes_blk_t * fwd_votes_blk = fd_votes_query( ctx->votes, slot_completed->slot, NULL );
    1252           0 :     if( FD_UNLIKELY( fwd_votes_blk && fd_uchar_extract_bit( fwd_votes_blk->flags, FD_TOWER_SLOT_CONFIRMED_DUPLICATE+4 ) ) ) {
    1253             : 
    1254             :       /* A block_id for this slot was forward-confirmed at the duplicate
    1255             :          level before replay (publish_slot_confirmed ran when no
    1256             :          ghost_blk existed).  Resolve the pending confirmation now. */
    1257             : 
    1258           0 :       tower_blk->confirmed          = 1;
    1259           0 :       tower_blk->confirmed_block_id = fwd_votes_blk->key.block_id;
    1260             : 
    1261           0 :       if( FD_LIKELY( 0==memcmp( &tower_blk->replayed_block_id, &fwd_votes_blk->key.block_id, sizeof(fd_hash_t) ) ) ) {
    1262             : 
    1263             :         /* The forward-confirmed block_id matches what we replayed. */
    1264             : 
    1265           0 :         fd_ghost_confirm( ctx->ghost, &slot_completed->block_id );
    1266             : 
    1267           0 :       } else {
    1268             : 
    1269             :         /* The forward-confirmed block_id differs from what we replayed,
    1270             :            so our replayed block is an equivocating sibling. */
    1271             : 
    1272           0 :         FD_BASE58_ENCODE_32_BYTES( slot_completed->block_id.uc, eqvoc_blk_id );
    1273           0 :         FD_LOG_DEBUG(( "[%s] equivocation detected via forward-confirmed block id mismatch (confirmed before replayed). slot: %lu. block_id: %s", __func__, slot_completed->slot, eqvoc_blk_id ));
    1274           0 :         fd_ghost_eqvoc( ctx->ghost, &slot_completed->block_id );
    1275           0 :       }
    1276             : 
    1277           0 :     } else if( FD_UNLIKELY( fd_eqvoc_proof_verified( ctx->eqvoc, slot_completed->slot ) ) ) {
    1278             : 
    1279             :       /* Eqvoc already detected equivocation for this slot (via shreds
    1280             :          or gossip before replay).  Mark the ghost block invalid. */
    1281             : 
    1282           0 :       FD_BASE58_ENCODE_32_BYTES( slot_completed->block_id.uc, eqvoc_blk_id );
    1283           0 :       FD_LOG_DEBUG(( "[%s] equivocation detected via eqvoc shred proof before replay. slot: %lu. block_id: %s", __func__, slot_completed->slot, eqvoc_blk_id ));
    1284           0 :       fd_ghost_eqvoc( ctx->ghost, &slot_completed->block_id );
    1285           0 :     }
    1286           0 :   }
    1287             : 
    1288           0 :   if( FD_UNLIKELY( !ctx->init ) ) {
    1289           0 :     ctx->metrics.init_slot = slot_completed->slot;
    1290           0 :     ctx->tower->root       = slot_completed->slot;
    1291           0 :     fd_votes_publish( ctx->votes, slot_completed->slot );
    1292           0 :   }
    1293             : 
    1294             :   /* Count the vote accounts and reconcile our own vote account. */
    1295             : 
    1296           0 :   ulong our_vote_acct_bal = ULONG_MAX;
    1297           0 :   int   found             = 0;
    1298           0 :   ghost_blk->total_stake  = QUERY_TOWERS( ctx, slot_completed, ghost_blk, &found, &our_vote_acct_bal );
    1299             : 
    1300             :   /* Capture the values needed for the processed event now: advancing the
    1301             :      root below (fd_ghost_publish) can prune ghost_blk if this block was
    1302             :      replayed on a minority fork, freeing it before we report. */
    1303             : 
    1304           0 :   ulong processed_stake       = ghost_blk->stake;
    1305           0 :   ulong processed_total_stake = ghost_blk->total_stake;
    1306           0 :   int   processed_valid       = ghost_blk->valid;
    1307             : 
    1308             :   /* The first replay_slot_completed msg is used to initialize the tower
    1309             :      tile's various structures. */
    1310             : 
    1311           0 :   if( FD_UNLIKELY( !ctx->init ) ) {
    1312           0 :     ctx->init = 1;
    1313           0 :     QUERY_VOTERS( ctx, slot_completed, slot_completed->epoch );
    1314           0 :   }
    1315             : 
    1316             :   /* Insert into hard fork detector. */
    1317             : 
    1318           0 :   fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, slot_completed->slot );
    1319           0 :   int hfork_flag = fd_hfork_record_our_bank_hash( ctx->hfork, &slot_completed->block_id, &slot_completed->bank_hash, fd_ulong_if( lsched->epoch==ctx->root_epoch, ctx->root_epoch_total_stake, ctx->next_epoch_total_stake ) );
    1320           0 :   update_metrics_hfork( ctx, hfork_flag, slot_completed->slot, &slot_completed->block_id );
    1321             : 
    1322             :   /* Determine reset, vote, and root slots.  There may not be a vote or
    1323             :      root slot but there is always a reset slot. */
    1324             : 
    1325           0 :   fd_tower_out_t out = { .vote_slot = ULONG_MAX, .root_slot = ULONG_MAX };
    1326           0 :   out.flags = fd_tower_vote_and_reset( ctx->tower,      ctx->ghost,          ctx->votes,
    1327           0 :                                        &out.reset_slot, &out.reset_block_id,
    1328           0 :                                        &out.vote_slot,  &out.vote_block_id,  &out.vote_bank_hash,
    1329           0 :                                        &out.root_slot,  &out.root_block_id );
    1330           0 :   if( FD_LIKELY( out.vote_slot!=ULONG_MAX ) ) { /* if there is a vote slot we record it. */
    1331           0 :     fd_tower_blk_t * vote_tower_blk = fd_tower_blocks_query( ctx->tower, out.vote_slot );
    1332           0 :     vote_tower_blk->voted           = 1;
    1333           0 :     vote_tower_blk->voted_block_id  = out.vote_block_id;
    1334           0 :   }
    1335             : 
    1336             :   /* Publish structures if there is a new root. */
    1337             : 
    1338           0 :   if( FD_UNLIKELY( out.root_slot!=ULONG_MAX ) ) {
    1339           0 :     if( FD_UNLIKELY( 0==memcmp( &out.root_block_id, &hash_null, sizeof(fd_hash_t) ) ) ) {
    1340           0 :       FD_LOG_CRIT(( "invariant violation: root block id is null at slot %lu", out.root_slot ));
    1341           0 :     }
    1342             : 
    1343           0 :     fd_tower_blk_t * oldr_tower_blk = fd_tower_blocks_query( ctx->tower, ctx->tower->root );
    1344           0 :     fd_tower_blk_t * newr_tower_blk = fd_tower_blocks_query( ctx->tower, out.root_slot );
    1345           0 :     FD_TEST( oldr_tower_blk );
    1346           0 :     FD_TEST( newr_tower_blk );
    1347             : 
    1348             :     /* It is a Solana consensus protocol invariant that a validator must
    1349             :        make at least one root in an epoch, so the root's epoch cannot
    1350             :        advance by more than one.  */
    1351             : 
    1352           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 */
    1353             : 
    1354             :     /* Publish votes: 1. reindex if it's a new epoch. 2. publish the new
    1355             :        root to votes. */
    1356             : 
    1357           0 :     if( FD_UNLIKELY( oldr_tower_blk->epoch+1==newr_tower_blk->epoch ) ) {
    1358           0 :       FD_TEST( newr_tower_blk->epoch==slot_completed->epoch ); /* new root's epoch must be same as current slot_completed */
    1359           0 :       QUERY_VOTERS( ctx, slot_completed, newr_tower_blk->epoch );
    1360           0 :     }
    1361           0 :     fd_votes_publish( ctx->votes, out.root_slot );
    1362             : 
    1363             :     /* Publish tower_blocks and tower_stakes by removing any entries
    1364             :        older than the new root. */
    1365             : 
    1366           0 :     for( ulong slot = ctx->tower->root; slot < out.root_slot; slot++ ) {
    1367           0 :       fd_tower_blocks_remove( ctx->tower, slot );
    1368           0 :       fd_tower_lockos_remove( ctx->tower, slot );
    1369           0 :       fd_tower_stakes_remove( ctx->tower, slot );
    1370           0 :     }
    1371             : 
    1372             :     /* Publish roots by walking up the ghost ancestry to publish new root
    1373             :        frags for intermediate slots we couldn't vote for. */
    1374             : 
    1375           0 :     fd_ghost_blk_t * newr = fd_ghost_query( ctx->ghost, &out.root_block_id );
    1376           0 :     fd_ghost_blk_t * oldr = fd_ghost_root( ctx->ghost );
    1377             : 
    1378             :     /* oldr is not guaranteed to be the immediate parent of newr, but is
    1379             :        rather an arbitrary ancestor.  This can happen if we couldn't
    1380             :        vote for those intermediate slot(s).  We publish those slots as
    1381             :        intermediate roots. */
    1382             : 
    1383           0 :     fd_ghost_blk_t * intr = newr;
    1384           0 :     while( FD_LIKELY( intr!=oldr ) ) {
    1385           0 :       publish_slot_rooted( ctx, intr->slot, &intr->id );
    1386           0 :       report_slot_confirmed( intr->bank_seq, intr->slot, &intr->id, intr->stake, intr->total_stake, intr->valid, FD_EVENT_SLOT_CONFIRMED_LEVEL_ROOTED, 0 /* not forward */ );
    1387           0 :       intr = fd_ghost_parent( ctx->ghost, intr );
    1388           0 :     }
    1389             : 
    1390             :     /* Publish ghost. */
    1391             : 
    1392           0 :     fd_ghost_publish( ctx->ghost, newr );
    1393             : 
    1394             :     /* Update the new root. */
    1395             : 
    1396           0 :     ctx->tower->root = out.root_slot;
    1397           0 :   }
    1398             : 
    1399             :   /* Publish a slot_done frag to tower_out. */
    1400             : 
    1401           0 :   publish_slot_done( ctx, slot_completed, &out, found, our_vote_acct_bal, tsorig, stem );
    1402           0 :   report_slot_confirmed( slot_completed->bank_seq, slot_completed->slot, &slot_completed->block_id, processed_stake, processed_total_stake, processed_valid, FD_EVENT_SLOT_CONFIRMED_LEVEL_PROCESSED, 0 /* not forward */ );
    1403             : 
    1404             :   /* Write out metrics. */
    1405             : 
    1406           0 :   ctx->metrics.replay_slot    = slot_completed->slot;
    1407           0 :   if( FD_LIKELY( out.vote_slot!=ULONG_MAX ) ) ctx->metrics.last_vote_slot = out.vote_slot;
    1408           0 :   ctx->metrics.last_vote_slot = fd_ulong_if( out.vote_slot!=ULONG_MAX, out.vote_slot, ctx->metrics.last_vote_slot );
    1409           0 :   ctx->metrics.reset_slot     = out.reset_slot; /* always set */
    1410           0 :   ctx->metrics.root_slot      = ctx->tower->root;
    1411             : 
    1412             :   /* Fork-decision axis: fd_tower_vote_and_reset sets exactly one of these
    1413             :      five fork flags, except in the two no-vote-yet short-circuits (case 0a
    1414             :      and 0b) where it sets no flags.  Those are distinguished by vote_slot:
    1415             :      0a does not vote (vote_slot==ULONG_MAX), 0b votes (vote_slot set). */
    1416             : 
    1417           0 :   if(      fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_ANCESTOR_ROLLBACK ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_V_ANCESTOR_ROLLBACK_IDX ]++;
    1418           0 :   else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SIBLING_CONFIRMED ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_V_SIBLING_CONFIRMED_IDX ]++;
    1419           0 :   else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SAME_FORK         ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_V_SAME_FORK_IDX         ]++;
    1420           0 :   else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SWITCH_PASS       ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_V_SWITCH_PASS_IDX       ]++;
    1421           0 :   else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SWITCH_FAIL       ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_V_SWITCH_FAIL_IDX       ]++;
    1422           0 :   else if( out.vote_slot!=ULONG_MAX                                          ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_V_EMPTY_TOWER_VOTE_IDX   ]++;
    1423           0 :   else                                                                         ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_V_NO_VOTE_NOT_RECENT_IDX ]++;
    1424             : 
    1425             :   /* Vote-gate axis: if a votable block was selected, it is gated by the
    1426             :      lockout/threshold/propagated checks (at most one fails) or it passes
    1427             :      all of them and we vote.  If no votable block was selected, there is
    1428             :      no candidate to gate. */
    1429             : 
    1430           0 :   if(      out.vote_slot!=ULONG_MAX                                            ) ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_V_VOTED_IDX           ]++;
    1431           0 :   else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_LOCKOUT_FAIL    )    ) ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_V_LOCKOUT_FAIL_IDX    ]++;
    1432           0 :   else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_THRESHOLD_FAIL  )    ) ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_V_THRESHOLD_FAIL_IDX  ]++;
    1433           0 :   else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_PROPAGATED_FAIL )    ) ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_V_PROPAGATED_FAIL_IDX ]++;
    1434           0 :   else                                                                          ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_V_NO_CANDIDATE_IDX     ]++;
    1435             : 
    1436             :   /* Log out structures. */
    1437             : 
    1438           0 :   char cstr[4096]; ulong cstr_sz;
    1439           0 :   FD_LOG_DEBUG(( "\n\n%s", fd_ghost_to_cstr( ctx->ghost, fd_ghost_root( ctx->ghost ), cstr, sizeof(cstr), &cstr_sz ) ));
    1440           0 :   FD_LOG_DEBUG(( "\n\n%s", fd_tower_to_cstr( ctx->tower, cstr ) ));
    1441           0 : }
    1442             : 
    1443             : FD_FN_CONST static inline ulong
    1444           0 : scratch_align( void ) {
    1445           0 :   return 128UL;
    1446           0 : }
    1447             : 
    1448             : FD_FN_PURE static inline ulong
    1449           0 : scratch_footprint( fd_topo_tile_t const * tile ) {
    1450           0 :   ulong slot_max    = fd_ulong_pow2_up( tile->tower.max_live_slots );
    1451           0 :   ulong blk_max     = slot_max * EQVOC_MAX;
    1452           0 :   ulong fec_max     = slot_max * FD_SHRED_BLK_MAX / FD_FEC_SHRED_CNT;
    1453           0 :   ulong pub_max     = slot_max * FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT;
    1454             : 
    1455           0 :   ulong l = FD_LAYOUT_INIT;
    1456           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t)                                       );
    1457           0 :   l = FD_LAYOUT_APPEND( l, auth_vtr_align(),         auth_vtr_footprint()                                          );
    1458             :   /* auth_vtr_keyswitch */
    1459           0 :   l = FD_LAYOUT_APPEND( l, fd_eqvoc_align(),         fd_eqvoc_footprint( slot_max, fec_max, PER_VTR_MAX, VTR_MAX ) );
    1460           0 :   l = FD_LAYOUT_APPEND( l, fd_ghost_align(),         fd_ghost_footprint( blk_max, VTR_MAX )                        );
    1461           0 :   l = FD_LAYOUT_APPEND( l, fd_hfork_align(),         fd_hfork_footprint( PER_VTR_MAX, VTR_MAX )                    );
    1462           0 :   l = FD_LAYOUT_APPEND( l, fd_votes_align(),         fd_votes_footprint( slot_max, VTR_MAX )                       );
    1463           0 :   l = FD_LAYOUT_APPEND( l, fd_tower_align(),         fd_tower_footprint( slot_max, VTR_MAX )                       );
    1464           0 :   l = FD_LAYOUT_APPEND( l, fd_tower_vote_align(),    fd_tower_vote_footprint()                                     );
    1465           0 :   l = FD_LAYOUT_APPEND( l, publishes_align(),        publishes_footprint( pub_max )                                );
    1466           0 :   l = FD_LAYOUT_APPEND( l, fd_accdb_align(),         fd_accdb_footprint( tile->tower.max_live_slots )              );
    1467           0 :   ulong epoch_vtr_chain_cnt = epoch_vtr_map_chain_cnt_est( VTR_MAX );
    1468           0 :   l = FD_LAYOUT_APPEND( l, epoch_vtr_pool_align(),         epoch_vtr_pool_footprint( VTR_MAX )                     );
    1469           0 :   l = FD_LAYOUT_APPEND( l, epoch_vtr_map_align(),          epoch_vtr_map_footprint( epoch_vtr_chain_cnt )          );
    1470           0 :   l = FD_LAYOUT_APPEND( l, epoch_vtr_pool_align(),         epoch_vtr_pool_footprint( VTR_MAX )                     );
    1471           0 :   l = FD_LAYOUT_APPEND( l, epoch_vtr_map_align(),          epoch_vtr_map_footprint( epoch_vtr_chain_cnt )          );
    1472           0 :   return FD_LAYOUT_FINI( l, scratch_align() );
    1473           0 : }
    1474             : 
    1475             : /* init_choreo allocates and initializes all choreo consensus structures
    1476             :    from scratch memory.  scratch must be at least scratch_footprint
    1477             :    bytes aligned to scratch_align().  The seed field at the start of
    1478             :    scratch must be pre-initialized (eg. by privileged_init).  Returns a
    1479             :    handle to the fd_tower_tile_t in scratch. */
    1480             : 
    1481             : static fd_tower_tile_t *
    1482             : init_choreo( void                 * scratch,
    1483             :              fd_topo_t const      * topo,
    1484           0 :              fd_topo_tile_t const * tile ) {
    1485           0 :   ulong slot_max    = fd_ulong_pow2_up( tile->tower.max_live_slots );
    1486           0 :   ulong blk_max     = slot_max * EQVOC_MAX;
    1487           0 :   ulong fec_max     = slot_max * FD_SHRED_BLK_MAX / FD_FEC_SHRED_CNT;
    1488           0 :   ulong pub_max     = slot_max * FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT;
    1489             : 
    1490           0 :   void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->tower.accdb_obj_id );
    1491           0 :   fd_accdb_shmem_t * accdb_shmem = fd_accdb_shmem_join( _accdb_shmem );
    1492           0 :   FD_TEST( accdb_shmem );
    1493             : 
    1494           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1495           0 :   fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t)                                       );
    1496           0 :   void  * auth_vtr      = FD_SCRATCH_ALLOC_APPEND( l, auth_vtr_align(),         auth_vtr_footprint()                                          );
    1497           0 :   void  * eqvoc         = FD_SCRATCH_ALLOC_APPEND( l, fd_eqvoc_align(),         fd_eqvoc_footprint( slot_max, fec_max, PER_VTR_MAX, VTR_MAX ) );
    1498           0 :   void  * ghost         = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_align(),         fd_ghost_footprint( blk_max, VTR_MAX )                        );
    1499           0 :   void  * hfork         = FD_SCRATCH_ALLOC_APPEND( l, fd_hfork_align(),         fd_hfork_footprint( PER_VTR_MAX, VTR_MAX )                    );
    1500           0 :   void  * votes         = FD_SCRATCH_ALLOC_APPEND( l, fd_votes_align(),         fd_votes_footprint( slot_max, VTR_MAX )                       );
    1501           0 :   void  * tower         = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_align(),         fd_tower_footprint( slot_max, VTR_MAX )                       );
    1502           0 :   void  * scratch_tower = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_vote_align(),    fd_tower_vote_footprint()                                     );
    1503           0 :   void  * publishes     = FD_SCRATCH_ALLOC_APPEND( l, publishes_align(),        publishes_footprint( pub_max )                                );
    1504           0 :   void  * accdb         = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(),         fd_accdb_footprint( tile->tower.max_live_slots )              );
    1505           0 :   ulong epoch_vtr_chain_cnt = epoch_vtr_map_chain_cnt_est( VTR_MAX );
    1506           0 :   void  * root_epoch_vtr_pool   = FD_SCRATCH_ALLOC_APPEND( l, epoch_vtr_pool_align(),         epoch_vtr_pool_footprint( VTR_MAX )             );
    1507           0 :   void  * root_epoch_vtr_map    = FD_SCRATCH_ALLOC_APPEND( l, epoch_vtr_map_align(),          epoch_vtr_map_footprint( epoch_vtr_chain_cnt )  );
    1508           0 :   void  * next_epoch_vtr_pool   = FD_SCRATCH_ALLOC_APPEND( l, epoch_vtr_pool_align(),         epoch_vtr_pool_footprint( VTR_MAX )             );
    1509           0 :   void  * next_epoch_vtr_map    = FD_SCRATCH_ALLOC_APPEND( l, epoch_vtr_map_align(),          epoch_vtr_map_footprint( epoch_vtr_chain_cnt )  );
    1510           0 :   ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
    1511           0 :   if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
    1512           0 :     FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
    1513           0 :   (void)auth_vtr; /* privileged_init */
    1514           0 :   ctx->eqvoc              = fd_eqvoc_join              ( fd_eqvoc_new              ( eqvoc, slot_max, fec_max, PER_VTR_MAX, VTR_MAX, ctx->seed ) );
    1515           0 :   ctx->ghost              = fd_ghost_join              ( fd_ghost_new              ( ghost, blk_max, VTR_MAX, ctx->seed )                        );
    1516           0 :   ctx->hfork              = fd_hfork_join              ( fd_hfork_new              ( hfork, PER_VTR_MAX, VTR_MAX, ctx->seed )                    );
    1517           0 :   ctx->votes              = fd_votes_join              ( fd_votes_new              ( votes, slot_max, VTR_MAX, ctx->seed )                       );
    1518           0 :   ctx->tower              = fd_tower_join              ( fd_tower_new              ( tower, slot_max, VTR_MAX, ctx->seed )                       );
    1519           0 :   ctx->scratch_tower      = fd_tower_vote_join         ( fd_tower_vote_new         ( scratch_tower )                                             );
    1520           0 :   ctx->publishes          = publishes_join             ( publishes_new             ( publishes, pub_max )                                        );
    1521           0 :   ctx->accdb              = fd_accdb_join              ( fd_accdb_new              ( accdb, _accdb_shmem, FD_ACCDB_FD_RW, 0UL, NULL )            );
    1522           0 :   ctx->mleaders           = fd_multi_epoch_leaders_join( fd_multi_epoch_leaders_new( ctx->mleaders_mem )                                         );
    1523           0 :   ctx->root_epoch_vtr_pool = epoch_vtr_pool_join( epoch_vtr_pool_new( root_epoch_vtr_pool, VTR_MAX ) );
    1524           0 :   ctx->root_epoch_vtr_map  = epoch_vtr_map_join ( epoch_vtr_map_new ( root_epoch_vtr_map,  epoch_vtr_chain_cnt, ctx->seed ) );
    1525           0 :   ctx->next_epoch_vtr_pool = epoch_vtr_pool_join( epoch_vtr_pool_new( next_epoch_vtr_pool, VTR_MAX ) );
    1526           0 :   ctx->next_epoch_vtr_map  = epoch_vtr_map_join ( epoch_vtr_map_new ( next_epoch_vtr_map,  epoch_vtr_chain_cnt, ctx->seed ) );
    1527             : 
    1528           0 :   FD_TEST( ctx->eqvoc );
    1529           0 :   FD_TEST( ctx->ghost );
    1530           0 :   FD_TEST( ctx->hfork );
    1531           0 :   FD_TEST( ctx->votes );
    1532           0 :   FD_TEST( ctx->tower );
    1533           0 :   FD_TEST( ctx->scratch_tower );
    1534           0 :   FD_TEST( ctx->publishes );
    1535           0 :   FD_TEST( ctx->accdb );
    1536           0 :   FD_TEST( ctx->mleaders );
    1537           0 :   FD_TEST( ctx->root_epoch_vtr_pool );
    1538           0 :   FD_TEST( ctx->root_epoch_vtr_map  );
    1539           0 :   FD_TEST( ctx->next_epoch_vtr_pool );
    1540           0 :   FD_TEST( ctx->next_epoch_vtr_map  );
    1541             : 
    1542           0 :   memset( ctx->duplicate_chunks, 0, sizeof(ctx->duplicate_chunks) );
    1543           0 :   memset( &ctx->compact_tower_sync_serde, 0, sizeof(ctx->compact_tower_sync_serde) );
    1544           0 :   memset( ctx->vote_txn, 0, sizeof(ctx->vote_txn) );
    1545             : 
    1546           0 :   ctx->halt_signing    = 0;
    1547           0 :   ctx->hard_fork_fatal = tile->tower.hard_fork_fatal;
    1548           0 :   ctx->wfs             = tile->tower.wait_for_supermajority;
    1549           0 :   ctx->shred_version   = 0;
    1550           0 :   ctx->init            = 0;
    1551           0 :   ctx->root_epoch      = ULONG_MAX;
    1552             : 
    1553           0 :   memset( &ctx->metrics, 0, sizeof(ctx->metrics) );
    1554           0 :   ctx->metrics.last_vote_slot = ULONG_MAX;
    1555             : 
    1556           0 :   return ctx;
    1557           0 : }
    1558             : 
    1559             : static void
    1560           0 : during_housekeeping( fd_tower_tile_t * ctx ) {
    1561           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->auth_vtr_keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
    1562           0 :     fd_keyswitch_state( ctx->auth_vtr_keyswitch, FD_KEYSWITCH_STATE_UNLOCKED );
    1563           0 :   }
    1564             : 
    1565           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->auth_vtr_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
    1566           0 :     fd_pubkey_t pubkey = *(fd_pubkey_t const *)fd_type_pun_const( ctx->auth_vtr_keyswitch->bytes );
    1567           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" ));
    1568           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" ));
    1569             : 
    1570           0 :     auth_vtr_t * auth_vtr = auth_vtr_insert( ctx->auth_vtr, pubkey );
    1571           0 :     auth_vtr->paths_idx = ctx->auth_vtr_path_cnt;
    1572           0 :     ctx->auth_vtr_path_cnt++;
    1573           0 :     fd_keyswitch_state( ctx->auth_vtr_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
    1574           0 :   }
    1575             : 
    1576             :   /* FIXME: Currently, the tower tile doesn't support set-identity with
    1577             :      a tower file.  When support for a tower file is added, we need to
    1578             :      swap the file that is running and sync it to the local state of
    1579             :      the tower.  Because a tower file is not supported, if another
    1580             :      validator was running with the identity that was switched to, then
    1581             :      it is possible that the original validator and the fallback (this
    1582             :      node), may have tower files which are out of sync.  This could lead
    1583             :      to consensus violations such as double voting or duplicate
    1584             :      confirmations.  Currently it is unsafe for a validator operator to
    1585             :      switch identities without a 512 slot delay: the reason for this
    1586             :      delay is to account for the worst case number of slots a vote
    1587             :      account can be locked out for. */
    1588             : 
    1589           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->identity_keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
    1590           0 :     FD_LOG_DEBUG(( "keyswitch: unhalting signing" ));
    1591           0 :     FD_CHECK_CRIT( ctx->halt_signing, "state machine corruption" );
    1592           0 :     ctx->halt_signing = 0;
    1593           0 :     fd_keyswitch_state( ctx->identity_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
    1594           0 :   }
    1595             : 
    1596           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->identity_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
    1597           0 :     FD_LOG_DEBUG(( "keyswitch: halting signing" ));
    1598           0 :     memcpy( ctx->identity_key, ctx->identity_keyswitch->bytes, 32UL );
    1599           0 :     FD_BASE58_ENCODE_32_BYTES( ctx->identity_key->uc, pubkey_str );
    1600           0 :     FD_LOG_INFO(( "my identity key: %s (key switched)", pubkey_str ));
    1601           0 :     fd_keyswitch_state( ctx->identity_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
    1602           0 :     ctx->halt_signing = 1;
    1603           0 :     ctx->identity_keyswitch->result  = ctx->out_seq;
    1604           0 :   }
    1605           0 : }
    1606             : 
    1607             : static inline void
    1608           0 : metrics_write( fd_tower_tile_t * ctx ) {
    1609           0 :   FD_MCNT_SET( TOWER, FRAG_NOT_READY_DROPPED, ctx->metrics.not_ready );
    1610             : 
    1611           0 :   FD_MCNT_SET  ( TOWER, FRAG_IGNORED,  ctx->metrics.ignored_cnt  );
    1612           0 :   FD_MGAUGE_SET( TOWER, SLOT_LAST_IGNORED, ctx->metrics.ignored_slot );
    1613             : 
    1614           0 :   FD_MGAUGE_SET( TOWER, REPLAY_SLOT, ctx->metrics.replay_slot    );
    1615           0 :   FD_MGAUGE_SET( TOWER, VOTE_SLOT,   ctx->metrics.last_vote_slot );
    1616           0 :   FD_MGAUGE_SET( TOWER, RESET_SLOT,  ctx->metrics.reset_slot     );
    1617           0 :   FD_MGAUGE_SET( TOWER, ROOT_SLOT,   ctx->metrics.root_slot      );
    1618           0 :   FD_MGAUGE_SET( TOWER, INIT_SLOT,   ctx->metrics.init_slot      );
    1619             : 
    1620           0 :   FD_MCNT_ENUM_COPY( TOWER, FORK_DECISION, ctx->metrics.fork );
    1621           0 :   FD_MCNT_ENUM_COPY( TOWER, VOTE_GATE,     ctx->metrics.gate );
    1622             : 
    1623           0 :   FD_MCNT_ENUM_COPY( TOWER, VOTE_TXN,               ctx->metrics.votes      );
    1624           0 :   FD_MCNT_ENUM_COPY( TOWER, VOTE_SLOT_COUNTED,      ctx->metrics.vote_slots );
    1625           0 :   FD_MCNT_ENUM_COPY( TOWER, VOTE_INTERMEDIATE_GATE, ctx->metrics.gate_int   );
    1626             : 
    1627           0 :   ulong eqvoc_proof[ FD_METRICS_ENUM_EQVOC_PROOF_RESULT_CNT ];
    1628           0 :   eqvoc_proof[ FD_METRICS_ENUM_EQVOC_PROOF_RESULT_V_SUCCESS_IDX ] = ctx->metrics.eqvoc_success;
    1629           0 :   eqvoc_proof[ FD_METRICS_ENUM_EQVOC_PROOF_RESULT_V_ERROR_IDX   ] = ctx->metrics.eqvoc_err;
    1630           0 :   FD_MCNT_ENUM_COPY( TOWER, EQVOC_PROOF, eqvoc_proof );
    1631             : 
    1632           0 :   FD_MCNT_ENUM_COPY( TOWER, GHOST_VOTE, ctx->metrics.ghost );
    1633             : 
    1634           0 :   FD_MCNT_ENUM_COPY( TOWER, HARD_FORK_VOTE, ctx->metrics.hfork );
    1635             : 
    1636           0 :   FD_MGAUGE_SET( TOWER, HARD_FORK_MATCHED_SLOT,    ctx->metrics.hfork_matched_slot    );
    1637           0 :   FD_MGAUGE_SET( TOWER, HARD_FORK_MISMATCHED_SLOT, ctx->metrics.hfork_mismatched_slot );
    1638             : 
    1639           0 :   FD_ACCDB_METRICS_WRITE( TOWER, fd_accdb_metrics( ctx->accdb ) );
    1640           0 : }
    1641             : 
    1642             : static inline void
    1643             : after_credit( fd_tower_tile_t *   ctx,
    1644             :               fd_stem_context_t * stem,
    1645             :               int *               opt_poll_in,
    1646           0 :               int *               charge_busy ) {
    1647           0 :   if( FD_LIKELY( !publishes_empty( ctx->publishes ) ) ) {
    1648           0 :     publish_t * pub = publishes_pop_head_nocopy( ctx->publishes );
    1649           0 :     memcpy( fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk ), &pub->msg, sizeof(fd_tower_msg_t) );
    1650           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() ) );
    1651           0 :     ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_msg_t), ctx->out_chunk0, ctx->out_wmark );
    1652           0 :     ctx->out_seq   = stem->seqs[ OUT_IDX ];
    1653           0 :     *opt_poll_in   = 0; /* drain the publishes */
    1654           0 :     *charge_busy   = 1;
    1655           0 :   }
    1656           0 : }
    1657             : 
    1658             : static inline int
    1659             : returnable_frag( fd_tower_tile_t *   ctx,
    1660             :                  ulong               in_idx,
    1661             :                  ulong               seq FD_PARAM_UNUSED,
    1662             :                  ulong               sig,
    1663             :                  ulong               chunk,
    1664             :                  ulong               sz,
    1665             :                  ulong               ctl FD_PARAM_UNUSED,
    1666             :                  ulong               tsorig,
    1667             :                  ulong               tspub FD_PARAM_UNUSED,
    1668           0 :                  fd_stem_context_t * stem ) {
    1669             : 
    1670           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 ) ) )
    1671           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 ));
    1672             : 
    1673           0 :   switch( ctx->in_kind[ in_idx ] ) {
    1674           0 :   case IN_KIND_DEDUP:{
    1675           0 :     if( FD_UNLIKELY( !ctx->init ) ) { ctx->metrics.not_ready++; return 1; } /* backpressure vote txns on boot until we're ready */
    1676           0 :     fd_txn_m_t * txnm = (fd_txn_m_t *)fd_chunk_to_laddr( ctx->in[in_idx].mem, chunk );
    1677           0 :     count_vote_txn( ctx, fd_txn_m_txn_t_const( txnm ), fd_txn_m_payload_const( txnm ) );
    1678           0 :     return 0;
    1679           0 :   }
    1680           0 :   case IN_KIND_EPOCH: {
    1681           0 :     fd_epoch_info_msg_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
    1682           0 :     FD_TEST( msg->staked_vote_cnt<=MAX_COMPRESSED_STAKE_WEIGHTS );
    1683           0 :     FD_TEST( msg->staked_id_cnt<=MAX_SHRED_DESTS );
    1684           0 :     fd_multi_epoch_leaders_epoch_msg_init( ctx->mleaders, msg );
    1685           0 :     fd_multi_epoch_leaders_epoch_msg_fini( ctx->mleaders );
    1686           0 :     return 0;
    1687           0 :   }
    1688           0 :   case IN_KIND_GOSSIP: {
    1689           0 :     if( FD_UNLIKELY( !ctx->init ) ) { ctx->metrics.not_ready++; return 0; } /* don't backpressure gossip on boot */
    1690           0 :     if( FD_LIKELY( sig==FD_GOSSIP_UPDATE_TAG_DUPLICATE_SHRED ) ) {
    1691           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 ) );
    1692           0 :       fd_gossip_duplicate_shred_t const * duplicate_shred = msg->duplicate_shred;
    1693           0 :       fd_pubkey_t const                 * from            = (fd_pubkey_t const *)fd_type_pun_const( msg->origin );
    1694           0 :       fd_epoch_leaders_t const *          lsched          = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, duplicate_shred->slot );
    1695           0 :       if( FD_UNLIKELY( !lsched ) ) { ctx->metrics.not_ready++; return 0; }
    1696           0 :       int eqvoc_err = fd_eqvoc_chunk_insert( ctx->eqvoc, ctx->tower->root, ctx->shred_version, lsched, from, duplicate_shred, ctx->duplicate_chunks );
    1697           0 :       update_metrics_eqvoc( ctx, eqvoc_err );
    1698           0 :       if( FD_UNLIKELY( eqvoc_err==FD_EQVOC_SUCCESS ) ) {
    1699           0 :         publish_slot_duplicate( ctx, ctx->duplicate_chunks, duplicate_shred->slot );
    1700           0 :       }
    1701           0 :     }
    1702           0 :     return 0;
    1703           0 :   }
    1704           0 :   case IN_KIND_IPECHO: {
    1705           0 :     FD_TEST( sig && sig<=USHORT_MAX );
    1706           0 :     ctx->shred_version = (ushort)sig;
    1707           0 :     return 0;
    1708           0 :   }
    1709           0 :   case IN_KIND_REPLAY: {
    1710           0 :     switch( sig ) {
    1711           0 :     case REPLAY_SIG_SLOT_COMPLETED:;
    1712           0 :       if( FD_UNLIKELY( ctx->halt_signing ) ) return 1; /* backpressure replay_slot_completed during halt_signing. */
    1713           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 ) );
    1714           0 :       replay_slot_completed( ctx, slot_completed, tsorig, stem );
    1715           0 :       break;
    1716           0 :     case REPLAY_SIG_SLOT_DEAD:;
    1717           0 :       fd_replay_slot_dead_t * slot_dead = (fd_replay_slot_dead_t *)fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
    1718           0 :       if( FD_UNLIKELY( slot_dead->slot < ctx->tower->root ) ) return 0; /* ignore dead slots before root */
    1719           0 :       fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, slot_dead->slot );
    1720           0 :       FD_TEST( lsched );
    1721           0 :       FD_TEST( lsched->epoch==ctx->root_epoch || lsched->epoch==ctx->root_epoch + 1 );
    1722           0 :       ulong total_stake = fd_ulong_if( lsched->epoch==ctx->root_epoch, ctx->root_epoch_total_stake, ctx->next_epoch_total_stake );
    1723           0 :       int hfork_flag = fd_hfork_record_our_bank_hash( ctx->hfork, &slot_dead->block_id, NULL, total_stake );
    1724           0 :       update_metrics_hfork( ctx, hfork_flag, slot_dead->slot, &slot_dead->block_id );
    1725           0 :       break;
    1726           0 :     case REPLAY_SIG_TXN_EXECUTED:;
    1727           0 :       FD_TEST( ctx->init ); /* replay_txn_executed should never be received before replay_slot_completed, which sets init to 1. */
    1728           0 :       fd_replay_txn_executed_t * txn_executed = fd_type_pun( fd_chunk_to_laddr( ctx->in[in_idx].mem, chunk ) );
    1729           0 :       if( FD_UNLIKELY( !txn_executed->is_committable || txn_executed->is_fees_only || txn_executed->txn_err ) ) return 0;
    1730           0 :       count_vote_txn( ctx, TXN(txn_executed->txn), txn_executed->txn->payload );
    1731           0 :       break;
    1732           0 :     default:
    1733           0 :       break;
    1734           0 :     }
    1735           0 :     return 0;
    1736           0 :   }
    1737           0 :   case IN_KIND_SHRED: {
    1738           0 :     if( FD_LIKELY( fd_shred_sig_src( sig )==SHRED_SIG_SRC_TURBINE || fd_shred_sig_src( sig )==SHRED_SIG_SRC_REPAIR ) ) {
    1739           0 :       fd_shred_base_t * msg       = (fd_shred_base_t *)fd_type_pun( fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
    1740           0 :       fd_shred_t      * shred     = &msg->shred;
    1741           0 :       int               eqvoc_err = fd_eqvoc_shred_insert( ctx->eqvoc, fd_shred_sig_res( sig )==SHRED_SIG_RESULT_EQVOC, shred, ctx->duplicate_chunks );
    1742           0 :       update_metrics_eqvoc( ctx, eqvoc_err );
    1743           0 :       if( FD_UNLIKELY( eqvoc_err==FD_EQVOC_SUCCESS ) ) publish_slot_duplicate( ctx, ctx->duplicate_chunks, shred->slot );
    1744           0 :     }
    1745           0 :     return 0;
    1746           0 :   }
    1747           0 :   default: FD_LOG_ERR(( "unexpected input kind %d", ctx->in_kind[ in_idx ] ));
    1748           0 :   }
    1749           0 : }
    1750             : 
    1751             : static void
    1752             : privileged_init( fd_topo_t const *      topo,
    1753           0 :                  fd_topo_tile_t const * tile ) {
    1754           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1755           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1756           0 :   fd_tower_tile_t * ctx      = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t),   sizeof(fd_tower_tile_t)        );
    1757           0 :   void            * auth_vtr = FD_SCRATCH_ALLOC_APPEND( l, auth_vtr_align(), auth_vtr_footprint() );
    1758           0 :   ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
    1759           0 :   if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
    1760           0 :     FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
    1761             : 
    1762           0 :   FD_TEST( fd_rng_secure( &ctx->seed, sizeof(ctx->seed) ) );
    1763             : 
    1764           0 :   if( FD_UNLIKELY( !strcmp( tile->tower.identity_key, "" ) ) ) FD_LOG_ERR(( "missing [paths.identity_key]" ));
    1765           0 :   ctx->identity_key[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.identity_key, /* pubkey only: */ 1 ) );
    1766             : 
    1767             :   /* The vote key can be specified either directly as a base58 encoded
    1768             :      pubkey, or as a file path.  We first try to decode as a pubkey. */
    1769             : 
    1770           0 :   uchar * vote_key = fd_base58_decode_32( tile->tower.vote_account, ctx->vote_account->uc );
    1771           0 :   if( FD_UNLIKELY( !vote_key ) ) {
    1772           0 :     if( FD_UNLIKELY( !strcmp( tile->tower.vote_account, "" ) ) ) FD_LOG_ERR(( "missing [paths.vote_account]" ));
    1773           0 :     ctx->vote_account[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.vote_account, /* pubkey only: */ 1 ) );
    1774           0 :   }
    1775             : 
    1776           0 :   ulong node_info_obj_id = fd_pod_query_ulong( topo->props, "node_info", ULONG_MAX ); FD_TEST( node_info_obj_id!=ULONG_MAX );
    1777           0 :   fd_node_info_box_t * node_info = fd_node_info_box_join( fd_topo_obj_laddr( topo, node_info_obj_id ) );  FD_TEST( node_info );
    1778           0 :   fd_node_info_write_begin( node_info );
    1779           0 :   node_info->info.vote_account = *ctx->vote_account;
    1780           0 :   fd_node_info_write_end( node_info );
    1781             : 
    1782           0 :   ctx->auth_vtr = auth_vtr_join( auth_vtr_new( auth_vtr ) );
    1783           0 :   for( ulong i=0UL; i<tile->tower.authorized_voter_paths_cnt; i++ ) {
    1784           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 ) );
    1785           0 :     if( FD_UNLIKELY( auth_vtr_query( ctx->auth_vtr, pubkey, NULL ) ) ) {
    1786           0 :       FD_BASE58_ENCODE_32_BYTES( pubkey.uc, pubkey_b58 );
    1787           0 :       FD_LOG_ERR(( "authorized voter key duplicate %s", pubkey_b58 ));
    1788           0 :     }
    1789             : 
    1790           0 :     auth_vtr_t * auth_vtr = auth_vtr_insert( ctx->auth_vtr, pubkey );
    1791           0 :     auth_vtr->paths_idx = i;
    1792           0 :   }
    1793           0 :   ctx->auth_vtr_path_cnt = tile->tower.authorized_voter_paths_cnt;
    1794             : 
    1795             :   /* The tower file is used to checkpt and restore the state of the
    1796             :      local tower. */
    1797             : 
    1798           0 :   char path[ PATH_MAX ];
    1799           0 :   FD_BASE58_ENCODE_32_BYTES( ctx->identity_key->uc, identity_key_b58 );
    1800           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 ) );
    1801           0 :   ctx->checkpt_fd = open( path, O_WRONLY|O_CREAT|O_TRUNC, 0600 );
    1802           0 :   if( FD_UNLIKELY( -1==ctx->checkpt_fd ) ) FD_LOG_ERR(( "open(`%s`) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
    1803             : 
    1804           0 :   FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "%s/tower-1_9-%s.bin", tile->tower.base_path, identity_key_b58 ) );
    1805           0 :   ctx->restore_fd = open( path, O_RDONLY );
    1806           0 :   if( FD_UNLIKELY( -1==ctx->restore_fd && errno!=ENOENT ) ) FD_LOG_ERR(( "open(`%s`) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
    1807           0 : }
    1808             : 
    1809             : static void
    1810             : unprivileged_init( fd_topo_t const *      topo,
    1811           0 :                    fd_topo_tile_t const * tile ) {
    1812           0 :   void *            scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1813           0 :   fd_tower_tile_t * ctx     = init_choreo( scratch, topo, tile );
    1814             : 
    1815           0 :   ctx->wksp               = topo->workspaces[ topo->objs[ tile->tile_obj_id ].wksp_id ].wksp;
    1816           0 :   ctx->identity_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
    1817           0 :   ctx->auth_vtr_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->av_keyswitch_obj_id ) );
    1818             : 
    1819           0 :   FD_TEST( ctx->wksp  );
    1820           0 :   FD_TEST( ctx->identity_keyswitch );
    1821           0 :   FD_TEST( ctx->auth_vtr_keyswitch );
    1822           0 :   FD_TEST( ctx->auth_vtr );
    1823             : 
    1824           0 :   ulong banks_obj_id = fd_pod_query_ulong( topo->props, "banks", ULONG_MAX );
    1825           0 :   FD_TEST( banks_obj_id!=ULONG_MAX );
    1826           0 :   ctx->banks = fd_banks_join( fd_topo_obj_laddr( topo, banks_obj_id ) );
    1827           0 :   FD_TEST( ctx->banks );
    1828             : 
    1829           0 :   FD_TEST( tile->in_cnt<sizeof(ctx->in_kind)/sizeof(ctx->in_kind[0]) );
    1830           0 :   for( ulong i=0UL; i<tile->in_cnt; i++ ) {
    1831           0 :     fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
    1832           0 :     fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
    1833             : 
    1834           0 :     if     ( FD_LIKELY( !strcmp( link->name, "dedup_resolv"  ) ) ) ctx->in_kind[ i ] = IN_KIND_DEDUP;
    1835           0 :     else if( FD_LIKELY( !strcmp( link->name, "replay_epoch"  ) ) ) ctx->in_kind[ i ] = IN_KIND_EPOCH;
    1836           0 :     else if( FD_LIKELY( !strcmp( link->name, "gossip_out"    ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP;
    1837           0 :     else if( FD_LIKELY( !strcmp( link->name, "ipecho_out"    ) ) ) ctx->in_kind[ i ] = IN_KIND_IPECHO;
    1838           0 :     else if( FD_LIKELY( !strcmp( link->name, "replay_out"    ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY;
    1839           0 :     else if( FD_LIKELY( !strcmp( link->name, "shred_out"     ) ) ) ctx->in_kind[ i ] = IN_KIND_SHRED;
    1840           0 :     else FD_LOG_ERR(( "tower tile has unexpected input link %lu %s", i, link->name ));
    1841             : 
    1842           0 :     ctx->in[ i ].mcache_only = !link->mtu;
    1843           0 :     if( FD_LIKELY( !ctx->in[ i ].mcache_only ) ) {
    1844           0 :       ctx->in[ i ].mem    = link_wksp->wksp;
    1845           0 :       ctx->in[ i ].mtu    = link->mtu;
    1846           0 :       ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
    1847           0 :       ctx->in[ i ].wmark  = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
    1848           0 :     }
    1849           0 :   }
    1850             : 
    1851           0 :   ctx->out_mem    = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ 0 ] ].dcache_obj_id ].wksp_id ].wksp;
    1852           0 :   ctx->out_chunk0 = fd_dcache_compact_chunk0( ctx->out_mem, topo->links[ tile->out_link_id[ 0 ] ].dcache );
    1853           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 );
    1854           0 :   ctx->out_chunk  = ctx->out_chunk0;
    1855           0 :   ctx->out_seq    = 0UL;
    1856             : 
    1857           0 :   FD_BASE58_ENCODE_32_BYTES( ctx->vote_account->uc, vote_account_b58 );
    1858           0 :   FD_BASE58_ENCODE_32_BYTES( ctx->identity_key->uc, identity_key_b58 );
    1859           0 :   FD_LOG_INFO(( "my vote account: %s", vote_account_b58 ));
    1860           0 :   FD_LOG_INFO(( "my identity key: %s", identity_key_b58 ));
    1861           0 : }
    1862             : 
    1863             : static ulong
    1864             : populate_allowed_seccomp( fd_topo_t const *      topo,
    1865             :                           fd_topo_tile_t const * tile,
    1866             :                           ulong                  out_cnt,
    1867           0 :                           struct sock_filter *   out ) {
    1868           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1869           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1870           0 :   fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
    1871             : 
    1872           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, FD_ACCDB_FD_RW );
    1873           0 :   return sock_filter_policy_fd_tower_tile_instr_cnt;
    1874           0 : }
    1875             : 
    1876             : static ulong
    1877             : populate_allowed_fds( fd_topo_t const *      topo,
    1878             :                       fd_topo_tile_t const * tile,
    1879             :                       ulong                  out_fds_cnt,
    1880           0 :                       int *                  out_fds ) {
    1881           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1882           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1883           0 :   fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
    1884             : 
    1885           0 :   if( FD_UNLIKELY( out_fds_cnt<5UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
    1886             : 
    1887           0 :   ulong out_cnt = 0UL;
    1888           0 :   out_fds[ out_cnt++ ] = 2; /* stderr */
    1889           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
    1890           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
    1891           0 :   if( FD_LIKELY( ctx->checkpt_fd!=-1 ) ) out_fds[ out_cnt++ ] = ctx->checkpt_fd;
    1892           0 :   if( FD_LIKELY( ctx->restore_fd!=-1 ) ) out_fds[ out_cnt++ ] = ctx->restore_fd;
    1893           0 :   out_fds[ out_cnt++ ] = FD_ACCDB_FD_RW; /* accounts database */
    1894             : 
    1895           0 :   return out_cnt;
    1896           0 : }
    1897             : 
    1898           0 : #define STEM_BURST (2UL)        /* MAX( slot_confirmed, slot_rooted AND (slot_done OR slot_ignored) ) */
    1899           0 : #define STEM_LAZY  (128L*3000L) /* see explanation in fd_pack */
    1900             : 
    1901           0 : #define STEM_CALLBACK_CONTEXT_TYPE        fd_tower_tile_t
    1902           0 : #define STEM_CALLBACK_CONTEXT_ALIGN       alignof(fd_tower_tile_t)
    1903           0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
    1904           0 : #define STEM_CALLBACK_METRICS_WRITE       metrics_write
    1905           0 : #define STEM_CALLBACK_AFTER_CREDIT        after_credit
    1906           0 : #define STEM_CALLBACK_RETURNABLE_FRAG     returnable_frag
    1907             : 
    1908             : #include "../../disco/stem/fd_stem.c"
    1909             : 
    1910             : fd_topo_run_tile_t fd_tile_tower = {
    1911             :   .name                     = "tower",
    1912             :   .max_event_sz             = sizeof(fd_event_slot_confirmed_t),
    1913             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    1914             :   .populate_allowed_fds     = populate_allowed_fds,
    1915             :   .scratch_align            = scratch_align,
    1916             :   .scratch_footprint        = scratch_footprint,
    1917             :   .unprivileged_init        = unprivileged_init,
    1918             :   .privileged_init          = privileged_init,
    1919             :   .run                      = stem_run,
    1920             : };

Generated by: LCOV version 1.14