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

Generated by: LCOV version 1.14