LCOV - code coverage report
Current view: top level - choreo/tower - fd_tower.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 875 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 71 0.0 %

          Line data    Source code
       1             : #include <stdio.h>
       2             : #include <string.h>
       3             : 
       4             : #include "fd_tower.h"
       5             : #include "../../flamenco/txn/fd_txn_generate.h"
       6             : #include "../../flamenco/runtime/fd_system_ids.h"
       7             : #include "../../flamenco/runtime/program/vote/fd_vote_state_versioned.h"
       8             : 
       9             : /* Pool and map_chain for fd_tower_blk_t. */
      10             : 
      11             : #define POOL_NAME blk_pool
      12           0 : #define POOL_T    fd_tower_blk_t
      13             : #define POOL_IDX_T uint
      14             : #include "../../util/tmpl/fd_pool.c"
      15             : 
      16             : #define MAP_NAME                           blk_map
      17           0 : #define MAP_ELE_T                          fd_tower_blk_t
      18             : #define MAP_KEY_T                          ulong
      19           0 : #define MAP_KEY                            slot
      20           0 : #define MAP_PREV                           prev
      21           0 : #define MAP_NEXT                           next
      22           0 : #define MAP_IDX_T                          uint
      23           0 : #define MAP_KEY_EQ(k0,k1)                  (*(k0)==*(k1))
      24           0 : #define MAP_KEY_HASH(key,seed)             ((*(key))^(seed))
      25             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
      26             : #include "../../util/tmpl/fd_map_chain.c"
      27             : 
      28             : /* lockout_interval tracks a map of lockout intervals.
      29             : 
      30             :    We need to track a list of lockout intervals per validator per slot.
      31             :    Intervals are inclusive.  Example:
      32             : 
      33             :    After executing slot 33, validator A votes for slot 32, has a tower
      34             : 
      35             :      vote  | confirmation count | lockout interval
      36             :      ----- | -------------------|------------------
      37             :      32    |  1                 | [32, 33]
      38             :      2     |  3                 | [2,  6]
      39             :      1     |  4                 | [1,  9]
      40             : 
      41             :    The lockout interval is the interval of slots that the validator is
      42             :    locked out from voting for if they want to switch off that vote.  For
      43             :    example if validator A wants to switch off fork 1, they have to wait
      44             :    until slot 9.
      45             : 
      46             :    Agave tracks a similar structure.
      47             : 
      48             :    key: for an interval [vote, vote+lockout] for validator A,
      49             :    it is stored like:
      50             :    vote+lockout -> (vote, validator A) -> (2, validator B) -> (any other vote, any other validator)
      51             : 
      52             :    Since a validator can have up to 31 entries in the tower, and we have
      53             :    a max_vote_accounts, we can pool the interval objects to be
      54             :    31*max_vote_accounts entries PER bank / executed slot.  A compact
      55             :    per-bank list of unique interval ends lets switch checks and pruning
      56             :    enumerate the corresponding map keys.  Keys use the per-bank slot
      57             :    header pool index instead of repeating the full fork slot. */
      58             : 
      59             : struct lockout_interval_key {
      60             :   uint interval_end;
      61             :   uint slot_conf_pubkey;
      62             : };
      63             : typedef struct lockout_interval_key lockout_interval_key_t;
      64             : 
      65             : struct lockout_interval {
      66             :   lockout_interval_key_t key;
      67             :   uint                   next;       /* reserved for fd_map_chain and fd_pool */
      68             :   uint                   end_next;   /* next unique-end interval for this fork slot */
      69             : };
      70             : typedef struct lockout_interval lockout_interval_t;
      71             : 
      72             : FD_STATIC_ASSERT( sizeof(lockout_interval_key_t)==8UL,  lockout_interval_key );
      73             : FD_STATIC_ASSERT( sizeof(lockout_interval_t    )==16UL, lockout_interval     );
      74             : 
      75           0 : #define LOCKOUT_INTERVAL_SLOT_IDX_BITS   (13U)
      76           0 : #define LOCKOUT_INTERVAL_CONF_BITS       (5U)
      77             : #define LOCKOUT_INTERVAL_PUBKEY_IDX_BITS (14U)
      78           0 : #define LOCKOUT_INTERVAL_SLOT_IDX_MASK   ((1U<<LOCKOUT_INTERVAL_SLOT_IDX_BITS)-1U)
      79           0 : #define LOCKOUT_INTERVAL_CONF_MASK       ((1U<<LOCKOUT_INTERVAL_CONF_BITS)-1U)
      80           0 : #define LOCKOUT_INTERVAL_CONF_SHIFT      (LOCKOUT_INTERVAL_SLOT_IDX_BITS)
      81           0 : #define LOCKOUT_INTERVAL_PUBKEY_IDX_SHIFT (LOCKOUT_INTERVAL_CONF_SHIFT+LOCKOUT_INTERVAL_CONF_BITS)
      82             : 
      83             : FD_STATIC_ASSERT( LOCKOUT_INTERVAL_SLOT_IDX_BITS+
      84             :                   LOCKOUT_INTERVAL_CONF_BITS+
      85             :                   LOCKOUT_INTERVAL_PUBKEY_IDX_BITS==32U, lockout_interval_encoding );
      86             : 
      87             : FD_FN_PURE static inline uint
      88           0 : lockout_interval_key_slot_idx( lockout_interval_key_t const * key ) {
      89           0 :   return key->slot_conf_pubkey & LOCKOUT_INTERVAL_SLOT_IDX_MASK;
      90           0 : }
      91             : 
      92             : #define MAP_NAME               lockout_interval_map
      93           0 : #define MAP_ELE_T              lockout_interval_t
      94             : #define MAP_KEY_T              lockout_interval_key_t
      95           0 : #define MAP_KEY_EQ(k0,k1)      ((lockout_interval_key_slot_idx( (k0) )==lockout_interval_key_slot_idx( (k1) )) & ((k0)->interval_end==(k1)->interval_end))
      96           0 : #define MAP_KEY_HASH(key,seed) (fd_ulong_hash( ((((ulong)lockout_interval_key_slot_idx( (key) ))<<32) | (ulong)(key)->interval_end) ^ (seed) ))
      97             : #define MAP_MULTI              1
      98           0 : #define MAP_KEY                key
      99           0 : #define MAP_NEXT               next
     100           0 : #define MAP_IDX_T              uint
     101             : #include "../../util/tmpl/fd_map_chain.c"
     102             : 
     103             : #define POOL_NAME  lockout_interval_pool
     104           0 : #define POOL_T     lockout_interval_t
     105           0 : #define POOL_NEXT  next
     106             : #define POOL_IDX_T uint
     107             : #define POOL_LAZY  1
     108             : #include "../../util/tmpl/fd_pool.c"
     109             : 
     110             : /* lockout_slot owns the list of unique interval ends for one fork slot. */
     111             : 
     112             : struct lockout_slot {
     113             :   uint slot; /* fork slot */
     114             :   uint next; /* map/pool pointer for other lockout_slot_t */
     115             :   uint end_head; /* start of unique interval ends linked list */
     116             : };
     117             : typedef struct lockout_slot lockout_slot_t;
     118             : 
     119             : FD_STATIC_ASSERT( sizeof(lockout_slot_t)==12UL, lockout_slot );
     120             : 
     121             : #define POOL_NAME lockout_slot_pool
     122           0 : #define POOL_T    lockout_slot_t
     123           0 : #define POOL_NEXT next
     124             : #define POOL_IDX_T uint
     125             : #define POOL_LAZY 1
     126             : #include "../../util/tmpl/fd_pool.c"
     127             : 
     128             : #define MAP_NAME               lockout_slot_map
     129             : #define MAP_ELE_T              lockout_slot_t
     130             : #define MAP_KEY_T              uint
     131           0 : #define MAP_KEY                slot
     132           0 : #define MAP_KEY_EQ(k0,k1)      (*(k0)==*(k1))
     133           0 : #define MAP_KEY_HASH(key,seed) (fd_ulong_hash( (ulong)*(key) ^ (seed) ))
     134           0 : #define MAP_NEXT               next
     135           0 : #define MAP_IDX_T              uint
     136             : #include "../../util/tmpl/fd_map_chain.c"
     137             : 
     138             : /* lockout_pubkey_ref dedups pubkeys across lockout intervals.  We know
     139             :    in the worst case there can be 2 * vtr_max pubkeys across all lockout
     140             :    intervals across all live banks since there are vtr_max unique
     141             :    pubkeys per epoch and a running validator can process up to 2 epochs
     142             :    at a time. */
     143             : 
     144             : struct lockout_pubkey_ref {
     145             :   fd_pubkey_t addr;
     146             :   uint        next;    /* reserved for fd_map_chain and fd_pool */
     147             :   uint        ref_cnt; /* number of normal lockout intervals referencing addr */
     148             : };
     149             : typedef struct lockout_pubkey_ref lockout_pubkey_ref_t;
     150             : 
     151             : FD_STATIC_ASSERT( sizeof(lockout_pubkey_ref_t)==40UL, lockout_pubkey_ref );
     152             : 
     153             : #define POOL_NAME  lockout_pubkey_pool
     154           0 : #define POOL_T     lockout_pubkey_ref_t
     155           0 : #define POOL_NEXT  next
     156             : #define POOL_IDX_T uint
     157             : #define POOL_LAZY  1
     158             : #include "../../util/tmpl/fd_pool.c"
     159             : 
     160             : #define MAP_NAME               lockout_pubkey_map
     161             : #define MAP_ELE_T              lockout_pubkey_ref_t
     162             : #define MAP_KEY_T              fd_pubkey_t
     163           0 : #define MAP_KEY                addr
     164           0 : #define MAP_KEY_EQ(k0,k1)      (!memcmp( (k0), (k1), sizeof(fd_pubkey_t) ))
     165           0 : #define MAP_KEY_HASH(key,seed) (fd_ulong_hash( (key)->ul[0] ^ (seed) ))
     166           0 : #define MAP_NEXT               next
     167           0 : #define MAP_IDX_T              uint
     168             : #include "../../util/tmpl/fd_map_chain.c"
     169             : 
     170             : FD_FN_CONST static inline lockout_interval_key_t
     171             : lockout_interval_key( ulong slot_idx,
     172             :                       ulong interval_end,
     173             :                       uint  conf,
     174           0 :                       ulong pubkey_idx ) {
     175           0 :   return (lockout_interval_key_t) {
     176           0 :     .interval_end     = (uint)interval_end,
     177           0 :     .slot_conf_pubkey = (uint)slot_idx |
     178           0 :                         (conf << LOCKOUT_INTERVAL_CONF_SHIFT) |
     179           0 :                         ((uint)pubkey_idx << LOCKOUT_INTERVAL_PUBKEY_IDX_SHIFT),
     180           0 :   };
     181           0 : }
     182             : 
     183             : FD_FN_PURE static inline uint
     184           0 : lockout_interval_key_conf( lockout_interval_key_t const * key ) {
     185           0 :   return (key->slot_conf_pubkey >> LOCKOUT_INTERVAL_CONF_SHIFT) & LOCKOUT_INTERVAL_CONF_MASK;
     186           0 : }
     187             : 
     188             : FD_FN_PURE static inline uint
     189           0 : lockout_interval_key_pubkey_idx( lockout_interval_key_t const * key ) {
     190           0 :   return key->slot_conf_pubkey >> LOCKOUT_INTERVAL_PUBKEY_IDX_SHIFT;
     191           0 : }
     192             : 
     193             : FD_FN_PURE static inline uint
     194           0 : lockout_interval_start( lockout_interval_t const * interval ) {
     195           0 :   return interval->key.interval_end - (1U << lockout_interval_key_conf( &interval->key ));
     196           0 : }
     197             : 
     198           0 : #define THRESHOLD_DEPTH (8)
     199           0 : #define THRESHOLD_RATIO (2.0 / 3.0)
     200             : #define SWITCH_RATIO    (0.38)
     201             : 
     202             : ulong
     203           0 : fd_tower_align( void ) {
     204           0 :   return 128UL;
     205           0 : }
     206             : 
     207             : static int
     208             : fd_tower_max_valid( ulong blk_max,
     209           0 :                     ulong vtr_max ) {
     210             :   /* Lockout intervals use 13-bit slot-header and 14-bit pubkey pool indices. */
     211           0 :   if( FD_UNLIKELY( blk_max>(1UL<<LOCKOUT_INTERVAL_SLOT_IDX_BITS) || vtr_max>(1UL<<(LOCKOUT_INTERVAL_PUBKEY_IDX_BITS-1U)) ) ) return 0;
     212           0 :   if( FD_UNLIKELY( blk_max && vtr_max>UINT_MAX/blk_max ) ) return 0;
     213             : 
     214           0 :   ulong pair_max = blk_max * vtr_max;
     215           0 :   if( FD_UNLIKELY( pair_max>UINT_MAX/FD_TOWER_LOCKOS_MAX ) ) return 0;
     216           0 :   return 1;
     217           0 : }
     218             : 
     219             : ulong
     220             : fd_tower_footprint( ulong blk_max,
     221           0 :                     ulong vtr_max ) {
     222           0 :   if( FD_UNLIKELY( !fd_tower_max_valid( blk_max, vtr_max ) ) ) return 0UL;
     223             : 
     224           0 :   ulong lck_interval_max  = FD_TOWER_LOCKOS_MAX*blk_max*vtr_max;
     225           0 :   ulong lck_map_chain_est = lockout_interval_map_chain_cnt_est( lck_interval_max );
     226           0 :   ulong lck_slot_chains   = lockout_slot_map_chain_cnt_est( blk_max );
     227           0 :   ulong lck_pubkey_max    = 2UL * vtr_max;
     228           0 :   ulong lck_pubkey_chains = lockout_pubkey_map_chain_cnt_est( lck_pubkey_max );
     229             : 
     230           0 :   ulong stk_vtr_chain_cnt = fd_tower_stakes_vtr_map_chain_cnt_est( vtr_max * blk_max );
     231           0 :   int   stk_lg_slot_cnt   = fd_ulong_find_msb( fd_ulong_pow2_up( blk_max ) ) + 1;
     232             : 
     233           0 :   ulong l = FD_LAYOUT_INIT;
     234           0 :   l = FD_LAYOUT_APPEND( l, 128UL,                            sizeof(fd_tower_t)                                          );
     235           0 :   l = FD_LAYOUT_APPEND( l, fd_tower_vote_align(),            fd_tower_vote_footprint()                                   );
     236           0 :   l = FD_LAYOUT_APPEND( l, blk_pool_align(),                 blk_pool_footprint     ( blk_max )                          );
     237           0 :   l = FD_LAYOUT_APPEND( l, blk_map_align(),                  blk_map_footprint      ( blk_map_chain_cnt_est( blk_max ) ) );
     238           0 :   l = FD_LAYOUT_APPEND( l, fd_tower_vtr_align(),             fd_tower_vtr_footprint ( vtr_max )                          );
     239           0 :   for( ulong i = 0; i < vtr_max; i++ ) {
     240           0 :     l = FD_LAYOUT_APPEND( l, fd_tower_vote_align(),          fd_tower_vote_footprint()                                   );
     241           0 :   }
     242             :   /* lockos */
     243           0 :   l = FD_LAYOUT_APPEND( l, lockout_interval_pool_align(),    lockout_interval_pool_footprint( lck_interval_max )         );
     244           0 :   l = FD_LAYOUT_APPEND( l, lockout_interval_map_align(),     lockout_interval_map_footprint ( lck_map_chain_est )        );
     245           0 :   l = FD_LAYOUT_APPEND( l, lockout_slot_pool_align(),        lockout_slot_pool_footprint    ( blk_max )                  );
     246           0 :   l = FD_LAYOUT_APPEND( l, lockout_slot_map_align(),         lockout_slot_map_footprint     ( lck_slot_chains )          );
     247           0 :   l = FD_LAYOUT_APPEND( l, lockout_pubkey_pool_align(),      lockout_pubkey_pool_footprint  ( lck_pubkey_max )           );
     248           0 :   l = FD_LAYOUT_APPEND( l, lockout_pubkey_map_align(),       lockout_pubkey_map_footprint   ( lck_pubkey_chains )        );
     249             :   /* stakes */
     250           0 :   l = FD_LAYOUT_APPEND( l, fd_tower_stakes_vtr_map_align(),  fd_tower_stakes_vtr_map_footprint ( stk_vtr_chain_cnt )     );
     251           0 :   l = FD_LAYOUT_APPEND( l, fd_tower_stakes_vtr_pool_align(), fd_tower_stakes_vtr_pool_footprint( vtr_max * blk_max )     );
     252           0 :   l = FD_LAYOUT_APPEND( l, fd_tower_stakes_slot_align(),     fd_tower_stakes_slot_footprint( stk_lg_slot_cnt )           );
     253           0 :   l = FD_LAYOUT_APPEND( l, fd_used_acc_scratch_align(),      fd_used_acc_scratch_footprint( vtr_max * blk_max )          );
     254           0 :   return FD_LAYOUT_FINI( l, fd_tower_align() );
     255           0 : }
     256             : 
     257             : void *
     258             : fd_tower_new( void * shmem,
     259             :               ulong  blk_max,
     260             :               ulong  vtr_max,
     261           0 :               ulong  seed ) {
     262             : 
     263           0 :   if( FD_UNLIKELY( !shmem ) ) {
     264           0 :     FD_LOG_WARNING(( "NULL mem" ));
     265           0 :     return NULL;
     266           0 :   }
     267             : 
     268           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_tower_align() ) ) ) {
     269           0 :     FD_LOG_WARNING(( "misaligned mem" ));
     270           0 :     return NULL;
     271           0 :   }
     272             : 
     273           0 :   ulong footprint = fd_tower_footprint( blk_max, vtr_max );
     274           0 :   if( FD_UNLIKELY( !footprint ) ) {
     275           0 :     FD_LOG_WARNING(( "bad blk_max (%lu) or vtr_max (%lu)", blk_max, vtr_max ));
     276           0 :     return NULL;
     277           0 :   }
     278             : 
     279           0 :   ulong lck_interval_max  = FD_TOWER_LOCKOS_MAX*blk_max*vtr_max;
     280           0 :   ulong lck_map_chain_est = lockout_interval_map_chain_cnt_est( lck_interval_max );
     281           0 :   ulong lck_slot_chains   = lockout_slot_map_chain_cnt_est( blk_max );
     282           0 :   ulong lck_pubkey_max    = 2UL * vtr_max;
     283           0 :   ulong lck_pubkey_chains = lockout_pubkey_map_chain_cnt_est( lck_pubkey_max );
     284             : 
     285           0 :   ulong stk_vtr_chain_cnt = fd_tower_stakes_vtr_map_chain_cnt_est( vtr_max * blk_max );
     286           0 :   int   stk_lg_slot_cnt   = fd_ulong_find_msb( fd_ulong_pow2_up( blk_max ) ) + 1;
     287             : 
     288           0 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     289           0 :   fd_tower_t * tower          = FD_SCRATCH_ALLOC_APPEND( l, 128UL,                             sizeof(fd_tower_t)                                          );
     290           0 :   void *       votes          = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_vote_align(),             fd_tower_vote_footprint()                                   );
     291           0 :   void *       blk_pool       = FD_SCRATCH_ALLOC_APPEND( l, blk_pool_align(),                  blk_pool_footprint     ( blk_max )                          );
     292           0 :   void *       blk_map        = FD_SCRATCH_ALLOC_APPEND( l, blk_map_align(),                   blk_map_footprint      ( blk_map_chain_cnt_est( blk_max ) ) );
     293           0 :   void *       vtrs           = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_vtr_align(),              fd_tower_vtr_footprint ( vtr_max )                          );
     294           0 :   void *       towers[ vtr_max ];
     295           0 :   for( ulong i = 0; i < vtr_max; i++ ) {
     296           0 :     towers[i] = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_vote_align(), fd_tower_vote_footprint() );
     297           0 :   }
     298           0 :   void *       lck_pool_mem   = FD_SCRATCH_ALLOC_APPEND( l, lockout_interval_pool_align(),    lockout_interval_pool_footprint( lck_interval_max )          );
     299           0 :   void *       lck_map_mem    = FD_SCRATCH_ALLOC_APPEND( l, lockout_interval_map_align(),     lockout_interval_map_footprint ( lck_map_chain_est )         );
     300           0 :   void *       lck_slot_pool  = FD_SCRATCH_ALLOC_APPEND( l, lockout_slot_pool_align(),        lockout_slot_pool_footprint    ( blk_max )                   );
     301           0 :   void *       lck_slot_map   = FD_SCRATCH_ALLOC_APPEND( l, lockout_slot_map_align(),         lockout_slot_map_footprint     ( lck_slot_chains )           );
     302           0 :   void *       lck_pk_pool    = FD_SCRATCH_ALLOC_APPEND( l, lockout_pubkey_pool_align(),      lockout_pubkey_pool_footprint  ( lck_pubkey_max )            );
     303           0 :   void *       lck_pk_map     = FD_SCRATCH_ALLOC_APPEND( l, lockout_pubkey_map_align(),       lockout_pubkey_map_footprint   ( lck_pubkey_chains )         );
     304           0 :   void *       stk_vtr_map    = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_stakes_vtr_map_align(),  fd_tower_stakes_vtr_map_footprint ( stk_vtr_chain_cnt )      );
     305           0 :   void *       stk_vtr_pool   = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_stakes_vtr_pool_align(), fd_tower_stakes_vtr_pool_footprint( vtr_max * blk_max )      );
     306           0 :   void *       stk_slot_map   = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_stakes_slot_align(),     fd_tower_stakes_slot_footprint( stk_lg_slot_cnt )            );
     307           0 :   void *       stk_used_acc   = FD_SCRATCH_ALLOC_APPEND( l, fd_used_acc_scratch_align(),      fd_used_acc_scratch_footprint( vtr_max * blk_max )           );
     308           0 :   FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_tower_align() ) == (ulong)shmem + footprint );
     309             : 
     310           0 :   tower->root     = ULONG_MAX;
     311           0 :   tower->blk_max  = blk_max;
     312           0 :   tower->vtr_max  = vtr_max;
     313           0 :   tower->votes    = fd_tower_vote_new( votes );
     314           0 :   tower->blk_pool = blk_pool_new( blk_pool, blk_max );
     315           0 :   tower->blk_map  = blk_map_new( blk_map, blk_map_chain_cnt_est( blk_max ), seed );
     316           0 :   tower->vtrs     = fd_tower_vtr_new( vtrs, vtr_max );
     317           0 :   for( ulong i = 0; i < vtr_max; i++ ) {
     318           0 :     fd_tower_vtr_join( tower->vtrs )[i].votes = fd_tower_vote_new( towers[i] );
     319           0 :   }
     320             : 
     321           0 :   tower->wait_to_vote_slot = 0UL;
     322             : 
     323           0 :   tower->lck_pool        = lockout_interval_pool_new( lck_pool_mem, lck_interval_max        );
     324           0 :   tower->lck_map         = lockout_interval_map_new ( lck_map_mem,  lck_map_chain_est, seed );
     325           0 :   tower->lck_slot_pool   = lockout_slot_pool_new    ( lck_slot_pool, blk_max                 );
     326           0 :   tower->lck_slot_map    = lockout_slot_map_new     ( lck_slot_map, lck_slot_chains,   seed );
     327           0 :   tower->lck_pubkey_pool = lockout_pubkey_pool_new  ( lck_pk_pool,  lck_pubkey_max          );
     328           0 :   tower->lck_pubkey_map  = lockout_pubkey_map_new   ( lck_pk_map,   lck_pubkey_chains, seed );
     329           0 :   tower->stk_vtr_map  = fd_tower_stakes_vtr_map_new ( stk_vtr_map,  stk_vtr_chain_cnt, seed );
     330           0 :   tower->stk_vtr_pool = fd_tower_stakes_vtr_pool_new( stk_vtr_pool, vtr_max * blk_max       );
     331           0 :   tower->stk_slot_map = fd_tower_stakes_slot_new    ( stk_slot_map, stk_lg_slot_cnt,   seed );
     332           0 :   tower->stk_used_acc = fd_used_acc_scratch_new     ( stk_used_acc, vtr_max * blk_max       );
     333             : 
     334           0 :   return shmem;
     335           0 : }
     336             : 
     337             : fd_tower_t *
     338           0 : fd_tower_join( void * shtower ) {
     339           0 :   fd_tower_t * tower = (fd_tower_t *)shtower;
     340             : 
     341           0 :   if( FD_UNLIKELY( !tower ) ) {
     342           0 :     FD_LOG_WARNING(( "NULL tower" ));
     343           0 :     return NULL;
     344           0 :   }
     345             : 
     346           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)tower, fd_tower_align() ) ) ) {
     347           0 :     FD_LOG_WARNING(( "misaligned tower" ));
     348           0 :     return NULL;
     349           0 :   }
     350             : 
     351           0 :   tower->votes        = fd_tower_vote_join( tower->votes    );
     352           0 :   tower->blk_pool     = blk_pool_join     ( tower->blk_pool );
     353           0 :   tower->blk_map      = blk_map_join      ( tower->blk_map  );
     354           0 :   tower->vtrs         = fd_tower_vtr_join ( tower->vtrs     );
     355           0 :   for( ulong i = 0; i < tower->vtr_max; i++ ) {
     356           0 :     tower->vtrs[i].votes = fd_tower_vote_join( tower->vtrs[i].votes );
     357           0 :   }
     358           0 :   tower->lck_pool        = lockout_interval_pool_join( tower->lck_pool        );
     359           0 :   tower->lck_map         = lockout_interval_map_join ( tower->lck_map         );
     360           0 :   tower->lck_slot_pool   = lockout_slot_pool_join    ( tower->lck_slot_pool   );
     361           0 :   tower->lck_slot_map    = lockout_slot_map_join     ( tower->lck_slot_map    );
     362           0 :   tower->lck_pubkey_pool = lockout_pubkey_pool_join  ( tower->lck_pubkey_pool );
     363           0 :   tower->lck_pubkey_map  = lockout_pubkey_map_join   ( tower->lck_pubkey_map  );
     364           0 :   tower->stk_vtr_map  = fd_tower_stakes_vtr_map_join ( tower->stk_vtr_map  );
     365           0 :   tower->stk_vtr_pool = fd_tower_stakes_vtr_pool_join( tower->stk_vtr_pool );
     366           0 :   tower->stk_slot_map = fd_tower_stakes_slot_join    ( tower->stk_slot_map );
     367           0 :   tower->stk_used_acc = fd_used_acc_scratch_join     ( tower->stk_used_acc );
     368             : 
     369           0 :   return tower;
     370           0 : }
     371             : 
     372             : void *
     373           0 : fd_tower_leave( fd_tower_t const * tower ) {
     374             : 
     375           0 :   if( FD_UNLIKELY( !tower ) ) {
     376           0 :     FD_LOG_WARNING(( "NULL tower" ));
     377           0 :     return NULL;
     378           0 :   }
     379             : 
     380           0 :   return (void *)tower;
     381           0 : }
     382             : 
     383             : void *
     384           0 : fd_tower_delete( void * shtower ) {
     385             : 
     386           0 :   if( FD_UNLIKELY( !shtower ) ) {
     387           0 :     FD_LOG_WARNING(( "NULL tower" ));
     388           0 :     return NULL;
     389           0 :   }
     390             : 
     391           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shtower, fd_tower_align() ) ) ) {
     392           0 :     FD_LOG_WARNING(( "misaligned tower" ));
     393           0 :     return NULL;
     394           0 :   }
     395             : 
     396           0 :   return shtower;
     397           0 : }
     398             : 
     399             : /* expiration calculates the expiration slot of vote given a slot and
     400             :    confirmation count. */
     401             : 
     402             : static inline ulong
     403           0 : expiration_slot( fd_tower_vote_t const * vote ) {
     404           0 :   ulong lockout = 1UL << vote->conf;
     405           0 :   return vote->slot + lockout;
     406           0 : }
     407             : 
     408             : /* simulate_vote simulates voting for slot, popping all votes from the
     409             :    top that would be consecutively expired by voting for slot. */
     410             : 
     411             : static ulong
     412             : simulate_vote( fd_tower_vote_t const * votes,
     413           0 :                ulong                   slot ) {
     414           0 :   ulong cnt = fd_tower_vote_cnt( votes );
     415           0 :   while( cnt ) {
     416           0 :     fd_tower_vote_t const * top_vote = fd_tower_vote_peek_index_const( votes, cnt - 1 );
     417           0 :     if( FD_LIKELY( expiration_slot( top_vote ) >= slot ) ) break; /* expire only if consecutive */
     418           0 :     cnt--;
     419           0 :   }
     420           0 :   return cnt;
     421           0 : }
     422             : 
     423             : /* push_vote pushes a new vote for slot onto the tower.  Pops and
     424             :    returns the new root (bottom of the tower) if it reaches max lockout
     425             :    as a result of the new vote.  Otherwise, returns ULONG_MAX.
     426             : 
     427             :    Max lockout is equivalent to 1 << FD_TOWER_VOTE_MAX + 1 (which
     428             :    implies confirmation count is FD_TOWER_VOTE_MAX + 1).  As a result,
     429             :    fd_tower_vote also maintains the invariant that the tower contains at
     430             :    most FD_TOWER_VOTE_MAX votes, because (in addition to vote expiry)
     431             :    there will always be a pop before reaching FD_TOWER_VOTE_MAX + 1. */
     432             : 
     433             : static ulong
     434             : push_vote( fd_tower_t * tower,
     435           0 :            ulong        slot ) {
     436             : 
     437             :   /* Sanity check: slot should always be greater than previous vote slot in tower. */
     438             : 
     439           0 :   fd_tower_vote_t const * vote = fd_tower_vote_peek_tail_const( tower->votes );
     440           0 :   if( FD_UNLIKELY( vote && slot <= vote->slot ) ) FD_LOG_CRIT(( "[%s] slot %lu <= vote->slot %lu", __func__, slot, vote->slot ));
     441             : 
     442             :   /* Use simulate_vote to determine how many expired votes to pop. */
     443             : 
     444           0 :   ulong cnt = simulate_vote( tower->votes, slot );
     445             : 
     446             :   /* Pop everything that got expired. */
     447             : 
     448           0 :   while( FD_LIKELY( fd_tower_vote_cnt( tower->votes ) > cnt ) ) {
     449           0 :     fd_tower_vote_pop_tail( tower->votes );
     450           0 :   }
     451             : 
     452             :   /* If the tower is still full after expiring, then pop and return the
     453             :      bottom vote slot as the new root because this vote has incremented
     454             :      it to max lockout.  Otherwise this is a no-op and there is no new
     455             :      root (ULONG_MAX). */
     456             : 
     457           0 :   ulong root = ULONG_MAX;
     458           0 :   if( FD_LIKELY( fd_tower_vote_full( tower->votes ) ) ) { /* optimize for full tower */
     459           0 :     root = fd_tower_vote_pop_head( tower->votes ).slot;
     460           0 :   }
     461             : 
     462             :   /* Increment confirmations (double lockouts) for consecutive
     463             :      confirmations in prior votes. */
     464             : 
     465           0 :   ulong prev_conf = 0;
     466           0 :   for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init_rev( tower->votes       );
     467           0 :                                   !fd_tower_vote_iter_done_rev( tower->votes, iter );
     468           0 :                             iter = fd_tower_vote_iter_prev    ( tower->votes, iter ) ) {
     469           0 :     fd_tower_vote_t * vote = fd_tower_vote_iter_ele( tower->votes, iter );
     470           0 :     if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) break;
     471           0 :     vote->conf++;
     472           0 :   }
     473             : 
     474             :   /* Add the new vote to the tower. */
     475             : 
     476           0 :   fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot = slot, .conf = 1 } );
     477             : 
     478             :   /* Return the new root (FD_SLOT_NULL if there is none). */
     479             : 
     480           0 :   return root;
     481           0 : }
     482             : 
     483             : /* lockout_check checks if we are locked out from voting for slot.
     484             :    Returns 1 if we can vote for slot without violating lockout, 0
     485             :    otherwise.
     486             : 
     487             :    After voting for a slot n, we are locked out for 2^k slots, where k
     488             :    is the confirmation count of that vote.  Once locked out, we cannot
     489             :    vote for a different fork until that previously-voted fork expires at
     490             :    slot n+2^k.  This implies the earliest slot in which we can switch
     491             :    from the previously-voted fork is (n+2^k)+1.  We use `ghost` to
     492             :    determine whether `slot` is on the same or different fork as previous
     493             :    vote slots.
     494             : 
     495             :    In the case of the tower, every vote has its own expiration slot
     496             :    depending on confirmations. The confirmation count is the max number
     497             :    of consecutive votes that have been pushed on top of the vote, and
     498             :    not necessarily its current height in the tower.
     499             : 
     500             :    For example, the following is a diagram of a tower pushing and
     501             :    popping with each vote:
     502             : 
     503             : 
     504             :    slot | confirmation count
     505             :    -----|-------------------
     506             :    4    |  1 <- vote
     507             :    3    |  2
     508             :    2    |  3
     509             :    1    |  4
     510             : 
     511             : 
     512             :    slot | confirmation count
     513             :    -----|-------------------
     514             :    9    |  1 <- vote
     515             :    2    |  3
     516             :    1    |  4
     517             : 
     518             : 
     519             :    slot | confirmation count
     520             :    -----|-------------------
     521             :    10   |  1 <- vote
     522             :    9    |  2
     523             :    2    |  3
     524             :    1    |  4
     525             : 
     526             : 
     527             :    slot | confirmation count
     528             :    -----|-------------------
     529             :    11   |  1 <- vote
     530             :    10   |  2
     531             :    9    |  3
     532             :    2    |  4
     533             :    1    |  5
     534             : 
     535             : 
     536             :    slot | confirmation count
     537             :    -----|-------------------
     538             :    18   |  1 <- vote
     539             :    2    |  4
     540             :    1    |  5
     541             : 
     542             : 
     543             :    In the final tower, note the gap in confirmation counts between slot
     544             :    18 and slot 2, even though slot 18 is directly above slot 2. */
     545             : 
     546             : static int
     547             : lockout_check( fd_tower_t * tower,
     548           0 :                ulong        slot ) {
     549             : 
     550           0 :   if( FD_UNLIKELY( slot<tower->wait_to_vote_slot ) ) return 0;
     551             : 
     552             :   /* Mirrors Agave's Tower::is_recent(): reject slot if it is not strictly
     553             :      newer than our last vote (non-empty tower) or our root (empty tower,
     554             :      e.g. snapshot boot).
     555             :      https://github.com/anza-xyz/agave/blob/v4.0.0-alpha.0/core/src/consensus.rs#L825-L836 */
     556           0 :   if( FD_UNLIKELY( fd_tower_vote_empty( tower->votes ) ) )
     557           0 :     return tower->root==ULONG_MAX || slot>tower->root;
     558           0 :   if( FD_UNLIKELY( slot<=fd_tower_vote_peek_tail_const( tower->votes )->slot ) ) return 0;
     559             : 
     560             :   /* Simulate a vote to pop off all the votes that would be expired by
     561             :      voting for slot.  Then check if the newly top-of-tower vote is on
     562             :      the same fork as slot (if so this implies we can vote for it). */
     563             : 
     564           0 :   ulong cnt = simulate_vote( tower->votes, slot ); /* pop off votes that would be expired */
     565           0 :   if( FD_UNLIKELY( !cnt ) ) return 1;              /* tower is empty after popping expired votes */
     566             : 
     567           0 :   fd_tower_vote_t const * vote    = fd_tower_vote_peek_index_const( tower->votes, cnt - 1 );       /* newly top-of-tower */
     568           0 :   int                     lockout = fd_tower_blocks_is_slot_descendant( tower, vote->slot, slot ); /* check if on same fork */
     569           0 :   return lockout;
     570           0 : }
     571             : 
     572             : /* switch_check checks if we can switch to the fork of `slot`.  Returns
     573             :    1 if we can switch, 0 otherwise.  Assumes tower is non-empty.
     574             : 
     575             :    There are two forks of interest: our last vote fork ("vote fork") and
     576             :    the fork we want to switch to ("switch fork").  The switch fork is on
     577             :    the fork of `slot`.
     578             : 
     579             :    In order to switch, SWITCH_RATIO of stake must have voted for
     580             :    a slot that satisfies the following conditions: the
     581             :    GCA(slot, last_vote) is an ancestor of the switch_slot
     582             : 
     583             :    Recall from the lockout check a validator is locked out from voting
     584             :    for our last vote slot when their last vote slot is on a different
     585             :    fork, and that vote's expiration slot > our last vote slot.
     586             : 
     587             :    The following pseudocode describes the algorithm:
     588             : 
     589             :    ```
     590             :    for every fork f in the fork tree, take the most recently executed
     591             :    slot `s` (the leaf of the fork).
     592             : 
     593             :    Take the greatest common ancestor of the `s` and the our last vote
     594             :    slot. If the switch_slot is a descendant of this GCA, then votes for
     595             :    `s` can count towards the switch threshold.
     596             : 
     597             :      query banks(`s`) for vote accounts in `s`
     598             :        for all vote accounts v in `s`
     599             :           if v's  locked out[1] from voting for our latest vote slot
     600             :              add v's stake to switch stake
     601             : 
     602             :    return switch stake >= total_stake * SWITCH_RATIO
     603             :    ```
     604             : 
     605             :    The switch check is used to safeguard optimistic confirmation.
     606             :    Specifically: optimistic confirmation pct + SWITCH_RATIO >= 1. */
     607             : 
     608             : static int
     609             : is_purged( fd_tower_t * tower,
     610           0 :            fd_ghost_blk_t * blk ) {
     611           0 :   fd_tower_blk_t * tower_blk = fd_tower_blocks_query( tower, blk->slot );
     612           0 :   return tower_blk->confirmed && memcmp( &tower_blk->confirmed_block_id, &blk->id, sizeof(fd_hash_t) );
     613           0 : }
     614             : 
     615             : static int
     616             : switch_check( fd_tower_t * tower,
     617             :               fd_ghost_t * ghost,
     618             :               ulong        total_stake,
     619           0 :               ulong        switch_slot ) {
     620             : 
     621           0 :   lockout_interval_map_t * lck_map  = tower->lck_map;
     622           0 :   lockout_interval_t *     lck_pool = tower->lck_pool;
     623           0 :   lockout_slot_map_t const * lck_slot_map = tower->lck_slot_map;
     624           0 :   lockout_slot_t const *     lck_slot_pool = tower->lck_slot_pool;
     625             : 
     626           0 :   ulong switch_stake = 0;
     627           0 :   ulong vote_slot    = fd_tower_vote_peek_tail_const( tower->votes )->slot;
     628           0 :   ulong root_slot    = tower->root;
     629             : 
     630           0 :   ulong            null = fd_ghost_blk_idx_null( ghost );
     631           0 :   fd_ghost_blk_t * head = fd_ghost_blk_map_remove( ghost, fd_ghost_root( ghost ) );
     632           0 :   fd_ghost_blk_t * tail = head;
     633           0 :   head->next = null;
     634             : 
     635           0 :   while( FD_LIKELY( head ) ) {
     636           0 :     fd_ghost_blk_t * blk = head; /* guaranteed to not be purged */
     637             : 
     638             :     /* Because agave has particular behavior where if they replay a
     639             :        equivocating version of a slot and then the correct version, the
     640             :        original version and all of it's children get purged from all
     641             :        structures.  None of the nodes on this subtree can be considered
     642             :        for the switch proof.  Note that this means as we BFS, a node
     643             :        can be considered a "valid leaf" if either it has no children,
     644             :        or if all of it's children are purged/superseded slots.  We
     645             :        detect this by comparing against tower_blocks confirmed. */
     646             : 
     647           0 :     int is_valid_leaf = 1;
     648           0 :     fd_ghost_blk_t * child = fd_ghost_blk_child( ghost, head );
     649           0 :     while( FD_LIKELY( child ) ) {
     650           0 :       if( FD_LIKELY( !is_purged( tower, child ) ) ) {
     651           0 :         fd_ghost_blk_map_remove( ghost, child );
     652           0 :         tail->next    = fd_ghost_blk_idx( ghost, child );
     653           0 :         tail          = child;
     654           0 :         tail->next    = null;
     655           0 :         is_valid_leaf = 0;
     656           0 :       }
     657           0 :       child = fd_ghost_blk_sibling( ghost, child );
     658           0 :     }
     659             : 
     660           0 :     head = fd_ghost_blk_next( ghost, blk );  /* pop queue head */
     661           0 :     fd_ghost_blk_map_insert( ghost, blk );   /* re-insert into map */
     662             : 
     663           0 :     if( FD_UNLIKELY( !is_valid_leaf ) ) continue;  /* not a real candidate */
     664             : 
     665           0 :     ulong candidate_slot = blk->slot;
     666           0 :     ulong lca = fd_tower_blocks_lowest_common_ancestor( tower, candidate_slot, vote_slot );
     667           0 :     if( FD_UNLIKELY( candidate_slot == vote_slot ) ) continue;
     668           0 :     if( FD_UNLIKELY( lca==ULONG_MAX ) ) continue;       /* unlikely but this leaf is an already pruned minority fork */
     669             : 
     670           0 :     if( FD_UNLIKELY( fd_tower_blocks_is_slot_descendant( tower, lca, switch_slot ) ) ) {
     671             : 
     672             :       /* This candidate slot may be considered for the switch proof, if
     673             :          it passes the following conditions:
     674             : 
     675             :          https://github.com/anza-xyz/agave/blob/c7b97bc77addacf03b229c51b47c18650d909576/core/src/consensus.rs#L1117
     676             : 
     677             :          Now for this candidate slot, look at the lockouts that were
     678             :          created at the time that we processed the bank for this
     679             :          candidate slot. */
     680             : 
     681           0 :       uint candidate_slot_key = (uint)candidate_slot;
     682           0 :       lockout_slot_t const * lockout_slot = lockout_slot_map_ele_query_const( lck_slot_map, &candidate_slot_key, NULL, lck_slot_pool );
     683           0 :       if( FD_UNLIKELY( !lockout_slot ) ) continue;
     684           0 :       ulong slot_idx = lockout_slot_pool_idx( lck_slot_pool, lockout_slot );
     685             : 
     686           0 :       for( lockout_interval_t const * lockout = lockout_interval_pool_ele_const( lck_pool, lockout_slot->end_head );
     687           0 :                                     !!lockout;
     688           0 :                                       lockout = lockout_interval_pool_ele_const( lck_pool, lockout->end_next ) ) {
     689           0 :         uint                   interval_end = lockout->key.interval_end;
     690           0 :         lockout_interval_key_t key          = lockout_interval_key( slot_idx, interval_end, 0U, 0UL );
     691             : 
     692             :         /* Intervals are keyed by the end of the interval. If the end of
     693             :            the interval is < the last vote slot, then these vote
     694             :            accounts with this particular lockout are NOT locked out from
     695             :            voting for the last vote slot, which means we can skip this
     696             :            set of intervals. */
     697             : 
     698           0 :         if( FD_LIKELY( interval_end < vote_slot ) ) continue;
     699             : 
     700             :         /* At this point we can actually query for the intervals by
     701             :            end interval to get the vote accounts. */
     702             : 
     703           0 :         for( lockout_interval_t const * interval = lockout_interval_map_ele_query_const( lck_map, &key, NULL, lck_pool );
     704           0 :                                         interval;
     705           0 :                                         interval = lockout_interval_map_ele_next_const( interval, NULL, lck_pool ) ) {
     706           0 :           fd_hash_t const * vote_acc = &lockout_pubkey_pool_ele_const( tower->lck_pubkey_pool, lockout_interval_key_pubkey_idx( &interval->key ) )->addr;
     707             : 
     708           0 :           uint interval_start = lockout_interval_start( interval );
     709           0 :           if( FD_UNLIKELY( !fd_tower_blocks_is_slot_descendant( tower, interval_start, vote_slot ) && interval_start > root_slot ) ) {
     710           0 :             fd_tower_stakes_vtr_xid_t     key         = { .addr = *vote_acc, .slot = switch_slot };
     711           0 :             fd_tower_stakes_vtr_t const * voter_stake = fd_tower_stakes_vtr_map_ele_query_const( tower->stk_vtr_map, &key, NULL, tower->stk_vtr_pool );
     712             : 
     713             :             /* Vote account could have been closed on the switch fork,
     714             :                and therefore not in the tower stakes map.  In this case
     715             :                just count the vote stake as 0 and skip this voter.
     716             :                matches Agave.  */
     717           0 :             if( FD_UNLIKELY( !voter_stake ) ) continue;
     718           0 :             ulong voter_idx = fd_tower_stakes_vtr_pool_idx( tower->stk_vtr_pool, voter_stake );
     719           0 :             if( FD_UNLIKELY( fd_used_acc_scratch_test( tower->stk_used_acc, voter_idx ) ) ) continue; /* exclude already counted voters */
     720           0 :             fd_used_acc_scratch_insert( tower->stk_used_acc, voter_idx );
     721           0 :             switch_stake += voter_stake->stake;
     722           0 :             if( FD_LIKELY( (double)switch_stake / (double)total_stake > SWITCH_RATIO ) ) {
     723           0 :               fd_used_acc_scratch_null( tower->stk_used_acc );
     724           0 :               FD_LOG_DEBUG(( "[%s] vote_slot: %lu. switch_slot: %lu. pct: %.0lf%%", __func__, vote_slot, switch_slot, (double)switch_stake / (double)total_stake * 100.0 ));
     725           0 :               while( FD_LIKELY( head ) ) { /* cleanup: re-insert remaining BFS queue into map */
     726           0 :                 fd_ghost_blk_t * next = fd_ghost_blk_next( ghost, head );
     727           0 :                 fd_ghost_blk_map_insert( ghost, head );
     728           0 :                 head = next;
     729           0 :               }
     730           0 :               return 1;
     731           0 :             }
     732           0 :           }
     733           0 :         }
     734           0 :       }
     735           0 :     }
     736           0 :   }
     737           0 :   fd_used_acc_scratch_null( tower->stk_used_acc );
     738           0 :   FD_LOG_DEBUG(( "[%s] vote_slot: %lu. switch_slot: %lu. pct: %.0lf%%", __func__, vote_slot, switch_slot, (double)switch_stake / (double)total_stake * 100.0 ));
     739           0 :   return 0;
     740           0 : }
     741             : 
     742             : /* threshold_check checks if we pass the threshold required to vote for
     743             :    `slot`.  Returns 1 if we pass the threshold check, 0 otherwise.
     744             : 
     745             :    The following pseudocode describes the algorithm:
     746             : 
     747             :    ```
     748             :    simulate that we have voted for `slot`
     749             : 
     750             :    for all vote accounts in the current epoch
     751             : 
     752             :       simulate that the vote account has voted for `slot`
     753             : 
     754             :       pop all votes expired by that simulated vote
     755             : 
     756             :       if the validator's latest tower vote after expiry >= our threshold
     757             :       slot ie. our vote from THRESHOLD_DEPTH back also after simulating,
     758             :       then add validator's stake to threshold_stake.
     759             : 
     760             :    return threshold_stake >= FD_TOWER_THRESHOLD_RATIO
     761             :    ```
     762             : 
     763             :    The threshold check simulates voting for the current slot to expire
     764             :    stale votes.  This is to prevent validators that haven't voted in a
     765             :    long time from counting towards the threshold stake. */
     766             : 
     767             : static int
     768             : threshold_check( fd_tower_t const *     tower,
     769             :                  fd_tower_vtr_t const * accts,
     770             :                  ulong                  total_stake,
     771           0 :                  ulong                  slot ) {
     772             : 
     773             :   /* First, simulate a vote on our tower, popping off everything that
     774             :      would be expired by voting for slot. */
     775             : 
     776           0 :   ulong cnt = simulate_vote( tower->votes, slot );
     777             : 
     778             :   /* We can always vote if our tower is not at least THRESHOLD_DEPTH
     779             :      deep after simulating. */
     780             : 
     781           0 :   if( FD_UNLIKELY( cnt < THRESHOLD_DEPTH ) ) return 1;
     782             : 
     783             :   /* Get the vote slot from THRESHOLD_DEPTH back. Note THRESHOLD_DEPTH
     784             :      is the 8th index back _including_ the simulated vote at index 0. */
     785             : 
     786           0 :   ulong threshold_slot  = fd_tower_vote_peek_index_const( tower->votes, cnt - THRESHOLD_DEPTH )->slot;
     787           0 :   ulong threshold_stake = 0;
     788           0 :   for( fd_tower_vtr_iter_t iter = fd_tower_vtr_iter_init( accts       );
     789           0 :                                  !fd_tower_vtr_iter_done( accts, iter );
     790           0 :                            iter = fd_tower_vtr_iter_next( accts, iter ) ) {
     791           0 :     fd_tower_vtr_t const * acct = fd_tower_vtr_iter_ele_const( accts, iter );
     792             : 
     793           0 :     ulong cnt = simulate_vote( acct->votes, slot ); /* expire votes */
     794           0 :     if( FD_UNLIKELY( !cnt ) ) continue;              /* no votes left after expiry */
     795             : 
     796             :     /* Count their stake towards the threshold check if their prev vote
     797             :        slot >= our threshold slot.
     798             : 
     799             :        We know their prev vote slot is definitely on the same fork as
     800             :        our threshold slot, because these towers are sourced from vote
     801             :        _accounts_, not vote _transactions_ and the Vote Program
     802             :        validates that all slots in the vote account's tower exist on the
     803             :        current fork.
     804             : 
     805             :        Therefore, if their prev vote slot >= our threshold slot, we know
     806             :        that vote must be for the threshold slot itself or one of
     807             :        threshold slot's descendants. */
     808             : 
     809           0 :     ulong vote_slot = fd_tower_vote_peek_index_const( acct->votes, cnt - 1 )->slot;
     810           0 :     if( FD_LIKELY( vote_slot >= threshold_slot ) ) threshold_stake += acct->stake;
     811           0 :   }
     812             : 
     813           0 :   double threshold_pct = (double)threshold_stake / (double)total_stake;
     814           0 :   int    threshold     = threshold_pct > THRESHOLD_RATIO;
     815           0 :   if( FD_UNLIKELY( !threshold ) ) FD_LOG_DEBUG(( "[%s] vote_slot: %lu. threshold_slot: %lu. pct: %.0lf%%.", __func__, fd_tower_vote_peek_tail_const( tower->votes )->slot, threshold_slot, threshold_pct * 100.0 ));
     816           0 :   return threshold;
     817           0 : }
     818             : 
     819             : static int
     820             : propagated_check( fd_tower_t * tower,
     821           0 :                   ulong        slot ) {
     822             : 
     823           0 :   fd_tower_blk_t * blk = fd_tower_blocks_query( tower, slot );
     824           0 :   FD_TEST( blk );
     825             : 
     826           0 :   if( FD_LIKELY( blk->leader                        ) ) return 1; /* can always vote for slot in which we're leader */
     827           0 :   if( FD_LIKELY( blk->prev_leader_slot==ULONG_MAX   ) ) return 1; /* haven't been leader yet */
     828             : 
     829           0 :   fd_tower_blk_t * prev_leader_blk = fd_tower_blocks_query( tower, blk->prev_leader_slot );
     830           0 :   if( FD_LIKELY( !prev_leader_blk ) ) return 1; /* already pruned / rooted */
     831             : 
     832           0 :   return prev_leader_blk->propagated;
     833           0 : }
     834             : 
     835             : uchar
     836             : fd_tower_vote_and_reset( fd_tower_t * tower,
     837             :                          fd_ghost_t * ghost,
     838             :                          fd_votes_t * votes FD_PARAM_UNUSED,
     839             :                          ulong *      reset_slot,
     840             :                          fd_hash_t *  reset_block_id,
     841             :                          ulong *      reset_bank_seq,
     842             :                          ulong *      vote_slot,
     843             :                          fd_hash_t *  vote_block_id,
     844             :                          fd_hash_t *  vote_bank_hash,
     845             :                          ulong *      root_slot,
     846           0 :                          fd_hash_t *  root_block_id ) {
     847             : 
     848           0 :   uchar                  flags     = 0;
     849           0 :   fd_ghost_blk_t const * best_blk  = fd_ghost_best( ghost, fd_ghost_root( ghost ) );
     850           0 :   fd_ghost_blk_t const * reset_blk = NULL;
     851           0 :   fd_ghost_blk_t const * vote_blk  = NULL;
     852             : 
     853             :   /* Case 0: if we haven't voted yet then there are two subcases where
     854             :      we short-circuit. */
     855             : 
     856             :   /* Case 0a: on boot, tower->root is set to the snapshot slot before
     857             :      any votes are recorded. In this case, lockout_check returns 0 for
     858             :      slot <= root, preventing a vote on the snapshot slot itself. */
     859             : 
     860             :   /* TODO refactor: 0a is a tile-concern not logic-concern */
     861             : 
     862           0 :   if( FD_UNLIKELY( fd_tower_vote_empty( tower->votes ) && !lockout_check( tower, best_blk->slot ) ) ) {
     863           0 :     FD_BASE58_ENCODE_32_BYTES( best_blk->id.uc, best_blk_id );
     864           0 :     FD_LOG_DEBUG(( "[%s] case 0a: not recent (slot %lu <= root %lu). reset_blk: (%lu, %s). vote_blk: (NULL)", __func__, best_blk->slot, tower->root, best_blk->slot, best_blk_id ));
     865           0 :     *reset_slot     = best_blk->slot;
     866           0 :     *reset_block_id = best_blk->id;
     867           0 :     *reset_bank_seq = best_blk->bank_seq;
     868           0 :     *vote_slot      = ULONG_MAX;
     869           0 :     *vote_block_id  = (fd_hash_t){0};
     870           0 :     *root_slot      = ULONG_MAX;
     871           0 :     *root_block_id  = (fd_hash_t){0};
     872           0 :     return flags;
     873           0 :   }
     874             : 
     875             :   /* Case 0b: if we haven't voted yet then we can always vote and reset
     876             :      to ghost_best. */
     877             : 
     878           0 :   if( FD_UNLIKELY( fd_tower_vote_empty( tower->votes ) ) ) {
     879           0 :     FD_BASE58_ENCODE_32_BYTES( best_blk->id.uc, best_blk_id );
     880           0 :     FD_LOG_DEBUG(( "[%s] case 0b: empty tower. reset_blk: (%lu, %s). vote_blk: (%lu, %s)", __func__, best_blk->slot, best_blk_id, best_blk->slot, best_blk_id ));
     881           0 :     fd_tower_blk_t * tower_blk = fd_tower_blocks_query( tower, best_blk->slot );
     882           0 :     tower_blk->voted           = 1;
     883           0 :     tower_blk->voted_block_id  = best_blk->id;
     884           0 :     *reset_slot                = best_blk->slot;
     885           0 :     *reset_block_id            = best_blk->id;
     886           0 :     *reset_bank_seq            = best_blk->bank_seq;
     887           0 :     *vote_slot                 = best_blk->slot;
     888           0 :     *vote_block_id             = best_blk->id;
     889           0 :     *vote_bank_hash            = tower_blk->bank_hash;
     890           0 :     *root_slot                 = push_vote( tower, best_blk->slot );
     891           0 :     *root_block_id             = ( fd_hash_t ){ 0 };
     892           0 :     return flags;
     893           0 :   }
     894             : 
     895           0 :   ulong            prev_vote_slot = fd_tower_vote_peek_tail_const( tower->votes )->slot;
     896           0 :   fd_tower_blk_t * prev_vote_fork = fd_tower_blocks_query( tower, prev_vote_slot ); /* must exist */
     897             : 
     898           0 :   fd_hash_t      * prev_vote_block_id = &prev_vote_fork->voted_block_id;
     899           0 :   fd_ghost_blk_t * prev_vote_blk      = fd_ghost_query( ghost, prev_vote_block_id );
     900             : 
     901             :   /* Case 1: if any ancestor of our prev vote (including prev vote
     902             :      itself) is an unconfirmed duplicate, then our prev vote was on a
     903             :      duplicate fork.
     904             : 
     905             :      There are three subcases to check. */
     906             : 
     907           0 :   int invalid_ancestor = !!fd_ghost_invalid_ancestor( ghost, prev_vote_blk );
     908             : 
     909             :   /* Case 1a: ghost_best is an ancestor of prev vote.  This means
     910             :      ghost_best is rolling back to an ancestor that precedes the
     911             :      duplicate ancestor on the same fork as our prev vote.  In this
     912             :      case, we can't vote on our ancestor, but we do reset to that
     913             :      ancestor.
     914             : 
     915             :      https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1016-L1019 */
     916             : 
     917           0 :   int ancestor_rollback = prev_vote_blk != best_blk && !!fd_ghost_ancestor( ghost, prev_vote_blk, &best_blk->id );
     918             : 
     919             :   /* Case 1b: ghost_best is not an ancestor, but prev_vote is a
     920             :      duplicate and we've confirmed its duplicate sibling.  In this
     921             :      case, we allow switching to ghost_best without a switch proof.
     922             : 
     923             :      Example: slot 5 is a duplicate.  We first receive, replay and
     924             :      vote for block 5, so that is our prev vote.  We later receive
     925             :      block 5' and observe that it is duplicate confirmed.  ghost_best
     926             :      now returns block 5' and we both vote and reset to block 5'
     927             :      regardless of the switch check.
     928             : 
     929             :      https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1021-L1024 */
     930             : 
     931           0 :   int sibling_confirmed = prev_vote_fork->confirmed && 0!=memcmp( &prev_vote_fork->voted_block_id, &prev_vote_fork->confirmed_block_id, sizeof(fd_hash_t) );
     932             : 
     933           0 :   if( FD_UNLIKELY( invalid_ancestor && ancestor_rollback ) ) {
     934           0 :     flags     = fd_uchar_set_bit( flags, FD_TOWER_FLAG_ANCESTOR_ROLLBACK );
     935           0 :     reset_blk = best_blk;
     936           0 :     FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id );
     937           0 :     FD_LOG_DEBUG(( "[%s] case 1a: ancestor rollback. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (NULL)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id ));
     938             : 
     939           0 :   } else if( FD_UNLIKELY( invalid_ancestor && sibling_confirmed ) ) {
     940           0 :     flags     = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SIBLING_CONFIRMED );
     941           0 :     reset_blk = best_blk;
     942           0 :     vote_blk  = best_blk;
     943           0 :     FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id );
     944           0 :     FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc,  vote_blk_id  );
     945           0 :     FD_LOG_DEBUG(( "[%s] case 1b: sibling confirmed. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (%lu, %s)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id, vote_blk->slot, vote_blk_id ));
     946           0 :   }
     947             : 
     948             :   /* Case 2: if our prev vote slot is an ancestor of the best slot, then
     949             :      they are on the same fork and we can both reset to it.  We can also
     950             :      vote for it if we pass the can_vote checks.
     951             : 
     952             :      https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1057 */
     953             : 
     954           0 :   else if( FD_LIKELY( best_blk->slot == prev_vote_slot || fd_tower_blocks_is_slot_ancestor( tower, best_blk->slot, prev_vote_slot ) ) ) {
     955           0 :     flags     = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SAME_FORK );
     956           0 :     reset_blk = best_blk;
     957           0 :     vote_blk  = best_blk;
     958           0 :     FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id );
     959           0 :     FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc,  vote_blk_id  );
     960           0 :     FD_LOG_DEBUG(( "[%s] case 2: same fork. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (%lu, %s)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id, vote_blk->slot, vote_blk_id ));
     961           0 :   }
     962             : 
     963             :   /* Case 3: if our prev vote is not an ancestor of the best block, then
     964             :      it is on a different fork.  If we pass the switch check, we can
     965             :      reset to it.  If we additionally pass the lockout check, we can
     966             :      also vote for it.
     967             : 
     968             :      https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1208-L1215
     969             : 
     970             :      Note also Agave uses the best blk's total stake for checking the
     971             :      threshold.
     972             : 
     973             :      https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/fork_choice.rs#L443-L445 */
     974             : 
     975           0 :   else if( FD_LIKELY( switch_check( tower, ghost, best_blk->total_stake, best_blk->slot ) ) ) {
     976           0 :     flags     = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_PASS );
     977           0 :     reset_blk = best_blk;
     978           0 :     vote_blk  = best_blk;
     979           0 :     FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id );
     980           0 :     FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc,  vote_blk_id  );
     981           0 :     FD_LOG_DEBUG(( "[%s] case 3: switch pass. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (%lu, %s)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id, vote_blk->slot, vote_blk_id ));
     982           0 :   }
     983             : 
     984             :   /* Case 4: same as case 3 but we didn't pass the switch check.  In
     985             :      this case we reset to either ghost_best or ghost_deepest beginning
     986             :      from our prev vote blk.
     987             : 
     988             :      We must reset to a block beginning from our prev vote fork to
     989             :      ensure votes get a chance to propagate.  Because in order for votes
     990             :      to land, someone needs to build a block on that fork.
     991             : 
     992             :      We reset to ghost_best or ghost_deepest depending on whether our
     993             :      prev vote is valid.  When it's invalid we use ghost_deepest instead
     994             :      of ghost_best, because ghost_best won't be able to return a valid
     995             :      block beginning from our prev_vote because by definition the entire
     996             :      subtree will be invalid.
     997             : 
     998             :      When our prev vote fork is not a duplicate, we want to propagate
     999             :      votes that might allow others to switch to our fork.  In addition,
    1000             :      if our prev vote fork is a duplicate, we want to propagate votes
    1001             :      that might "duplicate confirm" that block (reach 52% of stake).
    1002             : 
    1003             :      See top-level documentation in fd_tower.h for more details on vote
    1004             :      propagation. */
    1005             : 
    1006           0 :   else {
    1007             : 
    1008             :     /* Case 4a: failed switch check and last vote slot has an invalid
    1009             :        ancestor.
    1010             : 
    1011             :       https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/heaviest_subtree_fork_choice.rs#L1187 */
    1012             : 
    1013           0 :     if( FD_UNLIKELY( invalid_ancestor ) ) {
    1014           0 :       flags     = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_FAIL );
    1015           0 :       reset_blk = fd_ghost_deepest( ghost, prev_vote_blk );
    1016           0 :       FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id );
    1017           0 :       FD_LOG_DEBUG(( "[%s] case 4a: switch fail, invalid ancestor. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (NULL)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id ));
    1018           0 :     }
    1019             : 
    1020             :     /* Case 4b: failed switch check (no invalid ancestor).
    1021             : 
    1022             :       https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/fork_choice.rs#L200 */
    1023             : 
    1024           0 :     else {
    1025           0 :       flags     = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_FAIL );
    1026           0 :       reset_blk = fd_ghost_best( ghost, prev_vote_blk );
    1027           0 :       FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id );
    1028           0 :       FD_LOG_DEBUG(( "[%s] case 4b: switch fail, no invalid ancestor. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (NULL)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id ));
    1029           0 :     }
    1030           0 :   }
    1031             : 
    1032             :   /* If there is a block to vote for, there are a few additional checks
    1033             :      to make sure we can actually vote for it.
    1034             : 
    1035             :      Specifically, we need to make sure we're not locked out, pass the
    1036             :      threshold check and that our previous leader block has propagated
    1037             :      (reached the prop threshold according to fd_votes).
    1038             : 
    1039             :      https://github.com/firedancer-io/agave/blob/master/core/src/consensus/fork_choice.rs#L382-L385
    1040             : 
    1041             :      Agave uses the total stake on the fork being threshold checked
    1042             :      (vote_blk) for determining whether it meets the stake threshold. */
    1043             : 
    1044           0 :   if( FD_LIKELY( vote_blk ) ) {
    1045           0 :     if     ( FD_UNLIKELY( !lockout_check( tower, vote_blk->slot ) ) ) {
    1046           0 :       FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id );
    1047           0 :       FD_LOG_DEBUG(( "[%s] lockout check failed. prev_vote_slot: %lu. vote_blk: (%lu, %s)", __func__, prev_vote_slot, vote_blk->slot, vote_blk_id ));
    1048           0 :       flags    = fd_uchar_set_bit( flags, FD_TOWER_FLAG_LOCKOUT_FAIL );
    1049           0 :       vote_blk = NULL;
    1050           0 :     }
    1051           0 :     else if( FD_UNLIKELY( !threshold_check( tower, tower->vtrs, vote_blk->total_stake, vote_blk->slot ) ) ) {
    1052           0 :       FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id );
    1053           0 :       FD_LOG_DEBUG(( "[%s] threshold check failed. prev_vote_slot: %lu. vote_blk: (%lu, %s)", __func__, prev_vote_slot, vote_blk->slot, vote_blk_id ));
    1054           0 :       flags    = fd_uchar_set_bit( flags, FD_TOWER_FLAG_THRESHOLD_FAIL );
    1055           0 :       vote_blk = NULL;
    1056           0 :     }
    1057           0 :     else if( FD_UNLIKELY( !propagated_check( tower, vote_blk->slot ) ) ) {
    1058           0 :       FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id );
    1059           0 :       FD_LOG_DEBUG(( "[%s] propagated check failed. prev_vote_slot: %lu. vote_blk: (%lu, %s)", __func__, prev_vote_slot, vote_blk->slot, vote_blk_id ));
    1060           0 :       flags    = fd_uchar_set_bit( flags, FD_TOWER_FLAG_PROPAGATED_FAIL );
    1061           0 :       vote_blk = NULL;
    1062           0 :     }
    1063           0 :   }
    1064             : 
    1065           0 :   FD_TEST( reset_blk ); /* always a reset_blk */
    1066           0 :   *reset_slot     = reset_blk->slot;
    1067           0 :   *reset_block_id = reset_blk->id;
    1068           0 :   *reset_bank_seq = reset_blk->bank_seq;
    1069           0 :   *vote_slot      = ULONG_MAX;
    1070           0 :   *vote_block_id  = (fd_hash_t){0};
    1071           0 :   *vote_bank_hash  = (fd_hash_t){0};
    1072           0 :   *root_slot       = ULONG_MAX;
    1073           0 :   *root_block_id  = (fd_hash_t){0};
    1074             : 
    1075             :   /* Finally, if our vote passed all the checks, we actually push the
    1076             :      vote onto the tower. */
    1077             : 
    1078           0 :   if( FD_LIKELY( vote_blk ) ) {
    1079           0 :     *vote_slot     = vote_blk->slot;
    1080           0 :     *vote_block_id = vote_blk->id;
    1081           0 :     *root_slot     = push_vote( tower, vote_blk->slot );
    1082             : 
    1083             :     /* Query our tower fork for this slot we're voting for.  Note this
    1084             :        can never be NULL because we record tower forks as we replay, and
    1085             :        we should never be voting on something we haven't replayed. */
    1086             : 
    1087           0 :     fd_tower_blk_t * fork = fd_tower_blocks_query( tower, vote_blk->slot );
    1088           0 :     fork->voted           = 1;
    1089           0 :     fork->voted_block_id  = vote_blk->id;
    1090           0 :     *vote_bank_hash       = fork->bank_hash;
    1091             : 
    1092             :     /* Query the root slot's block id from tower forks.  This block id
    1093             :        may not necessarily be confirmed, because confirmation requires
    1094             :        votes on the block itself (vs. block and its descendants).
    1095             : 
    1096             :        So if we have a confirmed block id, we return that.  Otherwise
    1097             :        we return our own vote block id for that slot, which we assume
    1098             :        is the cluster converged on by the time we're rooting it.
    1099             : 
    1100             :        The only way it is possible for us to root the wrong version of
    1101             :        a block (ie. not the one the cluster confirmed) is if there is
    1102             :        mass equivocation (>2/3 of threshold check stake has voted for
    1103             :        two versions of a block).  This exceeds the equivocation safety
    1104             :        threshold and we would eventually detect this via a bank hash
    1105             :        mismatch and error out. */
    1106             : 
    1107           0 :     if( FD_LIKELY( *root_slot!=ULONG_MAX ) ) {
    1108           0 :       fd_tower_blk_t * root_fork = fd_tower_blocks_query( tower, *root_slot );
    1109           0 :       *root_block_id         = *fd_ptr_if( root_fork->confirmed, &root_fork->confirmed_block_id, &root_fork->voted_block_id );
    1110           0 :     }
    1111           0 :   }
    1112             : 
    1113           0 :   FD_BASE58_ENCODE_32_BYTES( reset_block_id->uc, reset_block_id_b58 );
    1114           0 :   FD_BASE58_ENCODE_32_BYTES( vote_block_id->uc,  vote_block_id_b58  );
    1115           0 :   FD_BASE58_ENCODE_32_BYTES( root_block_id->uc,  root_block_id_b58  );
    1116           0 :   FD_LOG_DEBUG(( "[%s] flags: %d. reset_slot: %lu (%s). vote_slot: %lu (%s). root_slot: %lu (%s).", __func__, flags, *reset_slot, reset_block_id_b58, *vote_slot, vote_block_id_b58, *root_slot, root_block_id_b58 ));
    1117           0 :   return flags;
    1118           0 : }
    1119             : 
    1120             : /* fd_tower_reconcile reconciles our local tower with our on-chain tower
    1121             :    (stored inside our vote account).  This function is important in two
    1122             :    contexts:
    1123             : 
    1124             :    ON BOOT
    1125             : 
    1126             :    When Firedancer boots up its local tower contains no votes, only a
    1127             :    root slot set to the snapshot slot.  It needs to restore its "latest"
    1128             :    tower votes and root as of its previous run.  This information is
    1129             :    stored on-chain itself, in a vote account, and Firedancer updates
    1130             :    vote account states during catchup by replaying blocks since the
    1131             :    snapshot.  Firedancer reconciles its local tower with the on-chain
    1132             :    one every time it replays a block, and will by definition have its
    1133             :    "latest" tower once it has caught up.
    1134             : 
    1135             :    Note that it is possible Firedancer had voted for a minority fork in
    1136             :    the previous run.  In this case, its true "latest" tower contains
    1137             :    votes for slots that were pruned by the time of this boot.  In theory
    1138             :    TowerBFT stipulates that lockout can be up to 2^32 slots, but in
    1139             :    practice slots are pruned once they fall out of the slot hash history
    1140             :    limit, because they can no longer be canonically verified on-chain.
    1141             :    Therefore, Firedancer can safely ignore slots that are pruned and
    1142             :    restore its latest tower on the majority fork as of boot time.
    1143             : 
    1144             :    HIGH-AVAILABILITY SETUP
    1145             : 
    1146             :    A typical validator setup involves two nodes, a primary and a backup.
    1147             :    The primary is a valid fee payer, and the one landing votes recording
    1148             :    the latest state of its tower on-chain.  The two nodes' towers will
    1149             :    usually be identical but occasionally diverge when one node votes
    1150             :    for slots that the other one doesn't.  This usually happens when
    1151             :    there are multiple forks.
    1152             : 
    1153             :    This becomes a problem, because the primary's tower may contain votes
    1154             :    the backup doesn't have and/or vice versa.  The primary's tower is
    1155             :    the canonical one, since it's the one recorded on-chain, so reconcile
    1156             :    is a no-op on the primary.
    1157             : 
    1158             :    On the backup, reconcile is more involved.  Because what's on-chain
    1159             :    is the primary's tower, there may be slots the backup never actually
    1160             :    voted for.  When the backup node reads back the on-chain tower, some
    1161             :    metadata, namely `voted` and `voted_block_id`, will be missing from
    1162             :    its fd_tower instance.
    1163             : 
    1164             :    fd_tower_reconcile assumes that if a tower has been recorded on-chain
    1165             :    then it is safe to assume the vote account registered with the
    1166             :    currently running Firedancer has in fact at some point voted for the
    1167             :    slots in that tower.
    1168             : 
    1169             :    In case the instance is the backup, it updates the local tower votes,
    1170             :    root, and metadata structures accordingly with this assumption namely
    1171             :    by inserting voted_block_id for votes that the backup didn't actually
    1172             :    vote for but can safely assume the primary did.
    1173             : 
    1174             :    This affects the Tower voting rules (see fd_tower_vote_and_reset) in
    1175             :    that the voted_block_id is used for certain vote and reset decisions.
    1176             : 
    1177             :    There are some corner cases to consider related to equivocation:
    1178             : 
    1179             :       2
    1180             :      / \
    1181             :     3   3' (confirmed)
    1182             : 
    1183             :    Assume 3 and 3' are alternate blocks for the same slot (3) and have
    1184             :    different block ids.  3' is the block that eventually gets confirmed.
    1185             :    Let's consider a scenario in which the primary votes for "3" and the
    1186             :    backup misses the vote for "3".  fd_tower_reconcile needs to backfill
    1187             :    the voted_block_id for "3" on the backup.  However, it's unclear
    1188             :    whether that vote is for 3 (unconfirmed) or 3' (confirmed), because
    1189             :    all the on-chain tower contains is the slot "3" (with no block_id).
    1190             :    How does the backup figure out the voted_block_id?
    1191             : 
    1192             :    It turns out it doesn't really matter either way, the backup can just
    1193             :    backfill with whichever block_id it happened to replay (we know the
    1194             :    backup has to have replayed either 3 or 3' in order to observe an
    1195             :    on-chain tower containing 3 in the first place):
    1196             : 
    1197             :    If the primary voted for 3 and the backup backfills with 3', we know
    1198             :    the primary will eventually switch to the DC block (3') via repair.
    1199             :    So backfilling with 3' is ok because the primary will converge to it.
    1200             : 
    1201             :    If the primary voted for 3' and the backup backfills with 3, then the
    1202             :    backup will similarly eventually switch to the DC block via repair.
    1203             :    Indeed, it will "freebie" switch in fd_tower_vote_and_reset ie. case
    1204             :    1b: "sibling confirmed".  Thus, the backup will converge to 3'. */
    1205             : 
    1206             : void
    1207             : fd_tower_reconcile( fd_tower_t      * tower,
    1208             :                     fd_tower_vote_t * onchain_votes,
    1209           0 :                     ulong             onchain_root ) {
    1210             : 
    1211           0 :   fd_tower_vote_t * local_votes = tower->votes;
    1212           0 :   ulong             local_root  = tower->root;
    1213             : 
    1214           0 :   ulong local_vote   = fd_tower_vote_empty( local_votes   ) ? ULONG_MAX : fd_tower_vote_peek_tail_const( local_votes   )->slot;
    1215           0 :   ulong onchain_vote = fd_tower_vote_empty( onchain_votes ) ? ULONG_MAX : fd_tower_vote_peek_tail_const( onchain_votes )->slot;
    1216             : 
    1217             :   /* Cases:
    1218             : 
    1219             :      Agave checks Option<onchain_vote> <= Option<local_vote>.  Breakdown of Ord<Option<Slot>>:
    1220             : 
    1221             :      None, None => True
    1222             :      None, Some => True
    1223             :      Some, None => False
    1224             :      Some, Some => onchain_vote <= local_vote */
    1225             : 
    1226           0 :   if( FD_LIKELY( onchain_vote==ULONG_MAX ||                            /* None, None or None, Some */
    1227           0 :                ( local_vote  !=ULONG_MAX && onchain_vote<=local_vote ) /* Some, Some               */ ) ) return;
    1228             : 
    1229             :   /* On-chain tower is newer, so sync our local tower to the on-chain tower. */
    1230             : 
    1231           0 :   FD_LOG_NOTICE(( "[%s] overwriting local tower (last: %lu, root: %lu) with onchain tower (last: %lu, root: %lu)", __func__, local_vote, local_root, onchain_vote, onchain_root ));
    1232             : 
    1233           0 :   FD_TEST( local_root!=ULONG_MAX ); /* local root should always be set before fd_tower_reconcile */
    1234           0 :   if( FD_LIKELY( onchain_root==ULONG_MAX || local_root > onchain_root ) ) {
    1235             : 
    1236             :     /* Local root is larger than on-chain root. Overwrite on-chain root
    1237             :        with local root (this is just a copy, not writing to accdb). */
    1238             : 
    1239           0 :     FD_LOG_DEBUG(( "[%s] local_root %lu > onchain_root %lu", __func__, local_root, onchain_root ));
    1240           0 :     onchain_root = local_root;
    1241             : 
    1242             :     /* Drop on-chain votes <= local root. */
    1243             : 
    1244           0 :     while( FD_LIKELY( !fd_tower_vote_empty( onchain_votes ) ) ) {
    1245           0 :       fd_tower_vote_t const * vote = fd_tower_vote_peek_head_const( onchain_votes );
    1246           0 :       if( FD_LIKELY( vote->slot > local_root ) ) break;
    1247           0 :       FD_LOG_DEBUG(( "[%s] dropping on-chain vote for slot %lu since it's <= local root %lu", __func__, vote->slot, local_root ));
    1248           0 :       fd_tower_vote_pop_head( onchain_votes );
    1249           0 :     }
    1250             : 
    1251             :     /* TODO add sanity-check that onchain_root is an ancestor of the
    1252             :        first vote's ancestor at this point. */
    1253           0 :   }
    1254             : 
    1255           0 :   for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( tower->votes );
    1256           0 :                                   !fd_tower_vote_iter_done( tower->votes, iter );
    1257           0 :                             iter = fd_tower_vote_iter_next( tower->votes, iter ) ) {
    1258           0 :     fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( tower->votes, iter );
    1259           0 :     fd_tower_blk_t * tower_blk = fd_tower_blocks_query( tower, vote->slot );
    1260           0 :     FD_TEST( tower_blk ); /* must exist if it's in our tower */
    1261           0 :     tower_blk->voted = 0;
    1262           0 :   }
    1263             : 
    1264             :   /* Overwrite the root.  No-op if local_root > onchain_root.  The slots
    1265             :      below it stay until the caller publishes the root to ghost. */
    1266             : 
    1267           0 :   tower->root = onchain_root;
    1268             : 
    1269             :   /* Clear out all local_votes. */
    1270             : 
    1271           0 :   fd_tower_vote_remove_all( tower->votes );
    1272             : 
    1273             :   /* Replace them with onchain_votes. */
    1274             : 
    1275           0 :   for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( onchain_votes );
    1276           0 :                                   !fd_tower_vote_iter_done( onchain_votes, iter );
    1277           0 :                             iter = fd_tower_vote_iter_next( onchain_votes, iter ) ) {
    1278           0 :     fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( onchain_votes, iter );
    1279           0 :     fd_tower_vote_push_tail( tower->votes, *vote );
    1280             : 
    1281             :     /* Additionally, backfill voted_block_id for the slots we didn't
    1282             :        actually vote for.  This is intentionally always using the latest
    1283             :        replayed_block_id if we overwrote it with a second replay.  */
    1284             : 
    1285           0 :     fd_tower_blk_t * tower_blk = fd_tower_blocks_query( tower, vote->slot );
    1286           0 :     FD_TEST( tower_blk ); /* must exist because
    1287             :                              1. all on-chain votes >  root slot
    1288             :                              2. all on-chain votes <= replay slot  */
    1289           0 :     if( FD_UNLIKELY( !tower_blk->voted ) ) {
    1290           0 :       tower_blk->voted          = 1;
    1291           0 :       tower_blk->voted_block_id = tower_blk->replayed_block_id;
    1292           0 :     }
    1293           0 :   }
    1294           0 : }
    1295             : 
    1296             : void
    1297             : fd_tower_from_vote_acc( fd_tower_vote_t * votes,
    1298             :                         ulong *           root,
    1299             :                         uchar const *     data,
    1300           0 :                         ulong             data_sz ) {
    1301           0 :   fd_vote_acc_desc_t desc[1];
    1302           0 :   if( FD_UNLIKELY( !fd_vote_acc_desc( desc, data, data_sz ) ) ) {
    1303           0 :     *root = ULONG_MAX;
    1304           0 :     return;
    1305           0 :   }
    1306           0 :   *root = desc->root_slot;
    1307           0 :   for( ulong i=0UL; i<desc->vote_cnt; i++ ) {
    1308           0 :     fd_tower_vote_t vote = {0};
    1309           0 :     switch( desc->kind ) {
    1310           0 :     case FD_VOTE_ACC_V2: {
    1311           0 :       fd_vote_acc_vote_v2_t const * v = fd_vote_acc_desc_vote( desc, data, i );
    1312           0 :       vote.slot = v->slot;
    1313           0 :       vote.conf = v->conf;
    1314           0 :       break;
    1315           0 :     }
    1316           0 :     case FD_VOTE_ACC_V3:
    1317           0 :     case FD_VOTE_ACC_V4: {
    1318           0 :       fd_vote_acc_vote_t const * v = fd_vote_acc_desc_vote( desc, data, i );
    1319           0 :       vote.slot = v->slot;
    1320           0 :       vote.conf = v->conf;
    1321           0 :       break;
    1322           0 :     }
    1323           0 :     }
    1324           0 :     fd_tower_vote_push_tail( votes, vote );
    1325           0 :   }
    1326           0 : }
    1327             : 
    1328             : ulong
    1329             : fd_tower_with_lat_from_vote_acc( fd_vote_acc_vote_t tower[ static FD_TOWER_VOTE_MAX ],
    1330             :                                  uchar const *      data,
    1331           0 :                                  ulong              data_sz ) {
    1332           0 :   fd_vote_acc_desc_t desc[1];
    1333           0 :   if( FD_UNLIKELY( !fd_vote_acc_desc( desc, data, data_sz ) ) ) return 0UL;
    1334           0 :   FD_DCHECK_CRIT( desc->vote_cnt <= FD_TOWER_VOTE_MAX, "invalid vote account" );
    1335           0 :   switch( desc->kind ) {
    1336           0 :   case FD_VOTE_ACC_V2: {
    1337           0 :     for( ulong i=0UL; i<desc->vote_cnt; i++ ) {
    1338           0 :       fd_vote_acc_vote_v2_t const * v = fd_vote_acc_desc_vote( desc, data, i );
    1339           0 :       tower[ i ] = (fd_vote_acc_vote_t){
    1340           0 :         .slot    = v->slot,
    1341           0 :         .conf    = v->conf,
    1342           0 :         .latency = UCHAR_MAX
    1343           0 :       };
    1344           0 :     }
    1345           0 :     break;
    1346           0 :   }
    1347           0 :   case FD_VOTE_ACC_V3:
    1348           0 :   case FD_VOTE_ACC_V4:
    1349           0 :     fd_memcpy( tower, fd_vote_acc_desc_vote( desc, data, 0UL ), desc->vote_cnt * sizeof(fd_vote_acc_vote_t) );
    1350           0 :     break;
    1351           0 :   }
    1352           0 :   return desc->vote_cnt;
    1353           0 : }
    1354             : 
    1355             : void
    1356             : fd_tower_to_vote_txn( fd_tower_t const *    tower,
    1357             :                       fd_hash_t const *     bank_hash,
    1358             :                       fd_hash_t const *     block_id,
    1359             :                       fd_hash_t const *     recent_blockhash,
    1360             :                       fd_pubkey_t const *   validator_identity,
    1361             :                       fd_pubkey_t const *   vote_authority,
    1362             :                       fd_pubkey_t const *   vote_acc,
    1363           0 :                       fd_txn_p_t *          vote_txn ) {
    1364             : 
    1365           0 :   FD_TEST( fd_tower_vote_cnt( tower->votes )<=FD_TOWER_VOTE_MAX );
    1366           0 :   fd_compact_tower_sync_serde_t tower_sync_serde = {
    1367           0 :     .root             = fd_ulong_if( tower->root == ULONG_MAX, 0UL, tower->root ),
    1368           0 :     .lockouts_cnt     = (ushort)fd_tower_vote_cnt( tower->votes ),
    1369             :     /* .lockouts populated below */
    1370           0 :     .hash             = *bank_hash,
    1371           0 :     .timestamp_option = 1,
    1372           0 :     .timestamp        = fd_log_wallclock() / (long)1e9, /* seconds */
    1373           0 :     .block_id         = *block_id
    1374           0 :   };
    1375             : 
    1376           0 :   ulong i = 0UL;
    1377           0 :   ulong prev = tower_sync_serde.root;
    1378           0 :   for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( tower->votes       );
    1379           0 :                              !fd_tower_vote_iter_done( tower->votes, iter );
    1380           0 :                        iter = fd_tower_vote_iter_next( tower->votes, iter ) ) {
    1381           0 :     fd_tower_vote_t const * vote                         = fd_tower_vote_iter_ele_const( tower->votes, iter );
    1382           0 :     tower_sync_serde.lockouts[i].offset             = vote->slot - prev;
    1383           0 :     tower_sync_serde.lockouts[i].confirmation_count = (uchar)vote->conf;
    1384           0 :     prev                                            = vote->slot;
    1385           0 :     i++;
    1386           0 :   }
    1387             : 
    1388           0 :   uchar * txn_out = vote_txn->payload;
    1389           0 :   uchar * txn_meta_out = vote_txn->_;
    1390             : 
    1391           0 :   int same_addr = !memcmp( validator_identity, vote_authority, sizeof(fd_pubkey_t) );
    1392           0 :   if( FD_LIKELY( same_addr ) ) {
    1393             : 
    1394             :     /* 0: validator identity
    1395             :        1: vote account address
    1396             :        2: vote program */
    1397             : 
    1398           0 :     fd_txn_accounts_t votes;
    1399           0 :     votes.signature_cnt         = 1;
    1400           0 :     votes.readonly_signed_cnt   = 0;
    1401           0 :     votes.readonly_unsigned_cnt = 1;
    1402           0 :     votes.acct_cnt              = 3;
    1403           0 :     votes.signers_w             = validator_identity;
    1404           0 :     votes.signers_r             = NULL;
    1405           0 :     votes.non_signers_w         = vote_acc;
    1406           0 :     votes.non_signers_r         = &fd_solana_vote_program_id;
    1407           0 :     FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, votes.signature_cnt, &votes, recent_blockhash->uc ) );
    1408             : 
    1409           0 :   } else {
    1410             : 
    1411             :     /* 0: validator identity
    1412             :        1: vote authority
    1413             :        2: vote account address
    1414             :        3: vote program */
    1415             : 
    1416           0 :     fd_txn_accounts_t votes;
    1417           0 :     votes.signature_cnt         = 2;
    1418           0 :     votes.readonly_signed_cnt   = 1;
    1419           0 :     votes.readonly_unsigned_cnt = 1;
    1420           0 :     votes.acct_cnt              = 4;
    1421           0 :     votes.signers_w             = validator_identity;
    1422           0 :     votes.signers_r             = vote_authority;
    1423           0 :     votes.non_signers_w         = vote_acc;
    1424           0 :     votes.non_signers_r         = &fd_solana_vote_program_id;
    1425           0 :     FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, votes.signature_cnt, &votes, recent_blockhash->uc ) );
    1426           0 :   }
    1427             : 
    1428             :   /* Add the vote instruction to the transaction. */
    1429             : 
    1430           0 :   uchar  vote_ix_buf[FD_TXN_MTU];
    1431           0 :   ulong  vote_ix_sz = 0;
    1432           0 :   FD_STORE( uint, vote_ix_buf, FD_VOTE_IX_KIND_TOWER_SYNC );
    1433           0 :   FD_TEST( 0==fd_compact_tower_sync_ser( &tower_sync_serde, vote_ix_buf + sizeof(uint), FD_TXN_MTU - sizeof(uint), &vote_ix_sz ) ); // cannot fail if fd_tower_vote_cnt( tower->votes ) <= FD_TOWER_VOTE_MAX
    1434           0 :   vote_ix_sz += sizeof(uint);
    1435           0 :   uchar program_id;
    1436           0 :   uchar ix_accs[2];
    1437           0 :   if( FD_LIKELY( same_addr ) ) {
    1438           0 :     ix_accs[0] = 1; /* vote account address */
    1439           0 :     ix_accs[1] = 0; /* vote authority */
    1440           0 :     program_id = 2; /* vote program */
    1441           0 :   } else {
    1442           0 :     ix_accs[0] = 2; /* vote account address */
    1443           0 :     ix_accs[1] = 1; /* vote authority */
    1444           0 :     program_id = 3; /* vote program */
    1445           0 :   }
    1446           0 :   vote_txn->payload_sz = (ushort)fd_txn_add_instr( txn_meta_out, txn_out, program_id, ix_accs, 2, vote_ix_buf, vote_ix_sz );
    1447           0 : }
    1448             : 
    1449             : int
    1450           0 : fd_tower_verify( fd_tower_t const * tower ) {
    1451           0 :   if( FD_UNLIKELY( fd_tower_vote_cnt( tower->votes )>FD_TOWER_VOTE_MAX ) ) {
    1452           0 :     FD_LOG_WARNING(( "[%s] invariant violation: cnt %lu > FD_TOWER_VOTE_MAX %lu", __func__, fd_tower_vote_cnt( tower->votes ), (ulong)FD_TOWER_VOTE_MAX ));
    1453           0 :     return -1;
    1454           0 :   }
    1455             : 
    1456           0 :   fd_tower_vote_t const * prev = NULL;
    1457           0 :   for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( tower->votes       );
    1458           0 :                                    !fd_tower_vote_iter_done( tower->votes, iter );
    1459           0 :                              iter = fd_tower_vote_iter_next( tower->votes, iter ) ) {
    1460           0 :     fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( tower->votes, iter );
    1461           0 :     if( FD_UNLIKELY( prev && ( vote->slot <= prev->slot || vote->conf >= prev->conf ) ) ) {
    1462           0 :       FD_LOG_WARNING(( "[%s] invariant violation: vote (slot:%lu conf:%lu) prev (slot:%lu conf:%lu)", __func__, vote->slot, vote->conf, prev->slot, prev->conf ));
    1463           0 :       return -1;
    1464           0 :     }
    1465           0 :     prev = vote;
    1466           0 :   }
    1467           0 :   return 0;
    1468           0 : }
    1469             : 
    1470             : static void
    1471           0 : to_cstr( fd_tower_t const * tower, char * s, ulong len ) {
    1472           0 :   ulong root = tower->root;
    1473           0 :   ulong off = 0;
    1474           0 :   int   n;
    1475             : 
    1476           0 :   n = snprintf( s + off, len - off, "[Tower]\n\n" );
    1477           0 :   if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
    1478           0 :   off += (ulong)n;
    1479             : 
    1480           0 :   if( FD_UNLIKELY( fd_tower_vote_empty( tower->votes ) ) ) return;
    1481             : 
    1482           0 :   ulong max_slot = 0;
    1483             : 
    1484             :   /* Determine spacing. */
    1485             : 
    1486           0 :   for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init_rev( tower->votes       );
    1487           0 :                              !fd_tower_vote_iter_done_rev( tower->votes, iter );
    1488           0 :                        iter = fd_tower_vote_iter_prev    ( tower->votes, iter ) ) {
    1489           0 :     max_slot = fd_ulong_max( max_slot, fd_tower_vote_iter_ele_const( tower->votes, iter )->slot );
    1490           0 :   }
    1491             : 
    1492             :   /* Calculate the number of digits in the maximum slot value. */
    1493             : 
    1494             : 
    1495           0 :   int digit_cnt = (int)fd_ulong_base10_dig_cnt( max_slot );
    1496             : 
    1497             :   /* Print the column headers. */
    1498             : 
    1499           0 :   if( off < len ) {
    1500           0 :     n = snprintf( s + off, len - off, "slot%*s | %s\n", digit_cnt - (int)strlen("slot"), "", "confirmation count" );
    1501           0 :     if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
    1502           0 :     off += (ulong)n;
    1503           0 :   }
    1504             : 
    1505             :   /* Print the divider line. */
    1506             : 
    1507           0 :   for( int i = 0; i < digit_cnt && off < len; i++ ) {
    1508           0 :     s[off++] = '-';
    1509           0 :   }
    1510           0 :   if( off < len ) {
    1511           0 :     n = snprintf( s + off, len - off, " | " );
    1512           0 :     if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
    1513           0 :     off += (ulong)n;
    1514           0 :   }
    1515           0 :   for( ulong i = 0; i < strlen( "confirmation count" ) && off < len; i++ ) {
    1516           0 :     s[off++] = '-';
    1517           0 :   }
    1518           0 :   if( off < len ) {
    1519           0 :     s[off++] = '\n';
    1520           0 :   }
    1521             : 
    1522             :   /* Print each vote as a table. */
    1523             : 
    1524           0 :   for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init_rev( tower->votes       );
    1525           0 :                              !fd_tower_vote_iter_done_rev( tower->votes, iter );
    1526           0 :                        iter = fd_tower_vote_iter_prev    ( tower->votes, iter ) ) {
    1527           0 :     fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( tower->votes, iter );
    1528           0 :     if( off < len ) {
    1529           0 :       n = snprintf( s + off, len - off, "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf );
    1530           0 :       if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
    1531           0 :       off += (ulong)n;
    1532           0 :     }
    1533           0 :   }
    1534             : 
    1535           0 :   if( FD_UNLIKELY( root == ULONG_MAX ) ) {
    1536           0 :     if( off < len ) {
    1537           0 :       n = snprintf( s + off, len - off, "%*s | root\n", digit_cnt, "NULL" );
    1538           0 :       if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
    1539           0 :       off += (ulong)n;
    1540           0 :     }
    1541           0 :   } else {
    1542           0 :     if( off < len ) {
    1543           0 :       n = snprintf( s + off, len - off, "%*lu | root\n", digit_cnt, root );
    1544           0 :       if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
    1545           0 :       off += (ulong)n;
    1546           0 :     }
    1547           0 :   }
    1548             : 
    1549             :   /* Ensure null termination */
    1550           0 :   if( off < len ) {
    1551           0 :     s[off] = '\0';
    1552           0 :   } else {
    1553           0 :     s[len - 1] = '\0';
    1554           0 :   }
    1555           0 : }
    1556             : 
    1557             : char *
    1558             : fd_tower_to_cstr( fd_tower_t const * tower,
    1559           0 :                   char *             cstr ) {
    1560           0 :   to_cstr( tower, cstr, FD_TOWER_CSTR_MIN );
    1561           0 :   return cstr;
    1562           0 : }
    1563             : 
    1564             : void
    1565             : fd_tower_count_vote( fd_tower_t *        tower,
    1566             :                      fd_pubkey_t const * vote_acc,
    1567             :                      ulong               stake,
    1568             :                      uchar const *       data,
    1569           0 :                      ulong               data_sz ) {
    1570           0 :   fd_tower_vtr_t * vtr = fd_tower_vtr_push_tail_nocopy( tower->vtrs );
    1571           0 :   vtr->vote_acc        = *vote_acc;
    1572           0 :   vtr->stake           = stake;
    1573           0 :   fd_tower_vote_remove_all( vtr->votes );
    1574           0 :   fd_tower_from_vote_acc( vtr->votes, &vtr->root, data, data_sz );
    1575           0 : }
    1576             : 
    1577             : /* Block functions ********************************************************/
    1578             : 
    1579             : static int
    1580             : is_ancestor( fd_tower_t * tower,
    1581             :              ulong        slot,
    1582           0 :              ulong        ancestor_slot ) {
    1583           0 :   fd_tower_blk_t * anc = blk_map_ele_query( tower->blk_map, &slot, NULL, tower->blk_pool );
    1584           0 :   while( FD_LIKELY( anc ) ) {
    1585           0 :     if( FD_LIKELY( anc->parent_slot == ancestor_slot ) ) return 1;
    1586           0 :     anc = anc->parent_slot == ULONG_MAX ? NULL : blk_map_ele_query( tower->blk_map, &anc->parent_slot, NULL, tower->blk_pool );
    1587           0 :   }
    1588           0 :   return 0;
    1589           0 : }
    1590             : 
    1591             : int
    1592             : fd_tower_blocks_is_slot_ancestor( fd_tower_t * tower,
    1593             :                                   ulong        descendant_slot,
    1594           0 :                                   ulong        ancestor_slot ) {
    1595           0 :   return is_ancestor( tower, descendant_slot, ancestor_slot );
    1596           0 : }
    1597             : 
    1598             : int
    1599             : fd_tower_blocks_is_slot_descendant( fd_tower_t * tower,
    1600             :                                     ulong        ancestor_slot,
    1601           0 :                                     ulong        descendant_slot ) {
    1602           0 :   return is_ancestor( tower, descendant_slot, ancestor_slot );
    1603           0 : }
    1604             : 
    1605             : ulong
    1606             : fd_tower_blocks_lowest_common_ancestor( fd_tower_t * tower,
    1607             :                                         ulong        slot1,
    1608           0 :                                         ulong        slot2 ) {
    1609             : 
    1610           0 :   fd_tower_blk_t * fork1 = blk_map_ele_query( tower->blk_map, &slot1, NULL, tower->blk_pool );
    1611           0 :   fd_tower_blk_t * fork2 = blk_map_ele_query( tower->blk_map, &slot2, NULL, tower->blk_pool );
    1612             : 
    1613           0 :   if( FD_UNLIKELY( !fork1 )) FD_LOG_CRIT(( "slot1 %lu not found", slot1 ));
    1614           0 :   if( FD_UNLIKELY( !fork2 )) FD_LOG_CRIT(( "slot2 %lu not found", slot2 ));
    1615             : 
    1616           0 :   while( FD_LIKELY( fork1 && fork2 ) ) {
    1617           0 :     if( FD_UNLIKELY( fork1->slot == fork2->slot ) ) return fork1->slot;
    1618           0 :     if( fork1->slot > fork2->slot                 ) fork1 = blk_map_ele_query( tower->blk_map, &fork1->parent_slot, NULL, tower->blk_pool );
    1619           0 :     else                                            fork2 = blk_map_ele_query( tower->blk_map, &fork2->parent_slot, NULL, tower->blk_pool );
    1620           0 :   }
    1621             : 
    1622           0 :   return ULONG_MAX;
    1623           0 : }
    1624             : 
    1625             : fd_hash_t const *
    1626             : fd_tower_blocks_canonical_block_id( fd_tower_t * tower,
    1627           0 :                                     ulong        slot ) {
    1628           0 :   fd_tower_blk_t * blk = blk_map_ele_query( tower->blk_map, &slot, NULL, tower->blk_pool );
    1629           0 :   if( FD_UNLIKELY( !blk ) ) return NULL;
    1630           0 :   return fd_tower_blk_canonical_block_id( blk );
    1631           0 : }
    1632             : 
    1633             : fd_tower_blk_t *
    1634           0 : fd_tower_blocks_query( fd_tower_t * tower, ulong slot ) {
    1635           0 :   return blk_map_ele_query( tower->blk_map, &slot, NULL, tower->blk_pool );
    1636           0 : }
    1637             : 
    1638             : fd_tower_blk_t *
    1639             : fd_tower_blocks_insert( fd_tower_t * tower,
    1640             :                         ulong        slot,
    1641           0 :                         ulong        parent_slot ) {
    1642           0 :   FD_TEST( blk_pool_free( tower->blk_pool ) );
    1643           0 :   fd_tower_blk_t * blk = blk_pool_ele_acquire( tower->blk_pool );
    1644             : 
    1645           0 :   memset( blk, 0, sizeof(fd_tower_blk_t) );
    1646           0 :   blk->parent_slot      = parent_slot;
    1647           0 :   blk->slot             = slot;
    1648           0 :   blk->prev_leader_slot = ULONG_MAX;
    1649           0 :   blk_map_ele_insert( tower->blk_map, blk, tower->blk_pool );
    1650           0 :   return blk;
    1651           0 : }
    1652             : 
    1653             : void
    1654             : fd_tower_blocks_remove( fd_tower_t * tower,
    1655           0 :                         ulong        slot ) {
    1656           0 :   fd_tower_blk_t * blk = blk_map_ele_query( tower->blk_map, &slot, NULL, tower->blk_pool );
    1657           0 :   if( FD_LIKELY( blk ) ) {
    1658           0 :     blk_map_ele_remove_fast( tower->blk_map, blk, tower->blk_pool );
    1659           0 :     blk_pool_ele_release( tower->blk_pool, blk );
    1660           0 :   }
    1661           0 : }
    1662             : 
    1663             : /* Lockos implementation */
    1664             : 
    1665             : void
    1666             : fd_tower_lockos_insert( fd_tower_t *      tower,
    1667             :                         ulong             slot,
    1668             :                         fd_hash_t const * addr,
    1669           0 :                         fd_tower_vote_t * votes ) {
    1670             : 
    1671           0 :   lockout_interval_map_t * lck_map       = tower->lck_map;
    1672           0 :   lockout_interval_t *     lck_pool      = tower->lck_pool;
    1673           0 :   lockout_slot_map_t *     lck_slot_map  = tower->lck_slot_map;
    1674           0 :   lockout_slot_t *         lck_slot_pool = tower->lck_slot_pool;
    1675             : 
    1676           0 :   ulong            vote_cnt     = fd_tower_vote_cnt( votes );
    1677           0 :   uint             pubkey_idx   = UINT_MAX;
    1678           0 :   lockout_slot_t * lockout_slot = NULL;
    1679           0 :   if( FD_LIKELY( vote_cnt ) ) {
    1680           0 :     FD_CHECK_CRIT( vote_cnt<=UINT_MAX, "tower lockout vote count overflow" );
    1681             : 
    1682           0 :     uint slot_key = (uint)slot;
    1683           0 :     lockout_slot = lockout_slot_map_ele_query( lck_slot_map, &slot_key, NULL, lck_slot_pool );
    1684           0 :     if( FD_UNLIKELY( !lockout_slot ) ) {
    1685           0 :       FD_CHECK_CRIT( lockout_slot_pool_free( lck_slot_pool ), "no free entries in tower lockout slot pool" );
    1686           0 :       lockout_slot           = lockout_slot_pool_ele_acquire( lck_slot_pool );
    1687           0 :       lockout_slot->slot     = slot_key;
    1688           0 :       lockout_slot->end_head = (uint)lockout_interval_pool_idx_null( lck_pool );
    1689           0 :       FD_CHECK_CRIT( lockout_slot_map_ele_insert( lck_slot_map, lockout_slot, lck_slot_pool ), "unable to insert into tower lockout slot map" );
    1690           0 :     }
    1691             : 
    1692           0 :     lockout_pubkey_ref_t * ref = lockout_pubkey_map_ele_query( tower->lck_pubkey_map, addr, NULL, tower->lck_pubkey_pool );
    1693           0 :     if( FD_UNLIKELY( !ref ) ) {
    1694           0 :       FD_CHECK_CRIT( lockout_pubkey_pool_free( tower->lck_pubkey_pool ), "no free entries in tower lockout pubkey pool" );
    1695           0 :       ref          = lockout_pubkey_pool_ele_acquire( tower->lck_pubkey_pool );
    1696           0 :       ref->addr    = *addr;
    1697           0 :       ref->ref_cnt = 0U;
    1698           0 :       FD_CHECK_CRIT( lockout_pubkey_map_ele_insert( tower->lck_pubkey_map, ref, tower->lck_pubkey_pool ), "unable to insert into tower lockout pubkey map" );
    1699           0 :     }
    1700             : 
    1701           0 :     ref->ref_cnt += (uint)vote_cnt;
    1702           0 :     pubkey_idx    = (uint)lockout_pubkey_pool_idx( tower->lck_pubkey_pool, ref );
    1703           0 :     FD_TEST( pubkey_idx<(1U<<LOCKOUT_INTERVAL_PUBKEY_IDX_BITS) );
    1704           0 :   }
    1705             : 
    1706           0 :   ulong slot_idx = vote_cnt ? lockout_slot_pool_idx( lck_slot_pool, lockout_slot ) : ULONG_MAX;
    1707           0 :   for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( votes );
    1708           0 :                                   !fd_tower_vote_iter_done( votes, iter );
    1709           0 :                             iter = fd_tower_vote_iter_next( votes, iter ) ) {
    1710           0 :     fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( votes, iter );
    1711           0 :     FD_TEST( vote->conf<=31U );
    1712           0 :     uint                   interval_end   = (uint)(vote->slot + (1UL << vote->conf));
    1713           0 :     lockout_interval_key_t key            = lockout_interval_key( slot_idx, interval_end, (uint)vote->conf, pubkey_idx );
    1714             : 
    1715           0 :     int is_unique_end = !lockout_interval_map_ele_query( lck_map, &key, NULL, lck_pool );
    1716           0 :     FD_TEST( lockout_interval_pool_free( lck_pool ) );
    1717           0 :     lockout_interval_t * interval = lockout_interval_pool_ele_acquire( lck_pool );
    1718           0 :     interval->key      = key;
    1719           0 :     interval->end_next = (uint)lockout_interval_pool_idx_null( lck_pool );
    1720           0 :     FD_TEST( lockout_interval_map_ele_insert( lck_map, interval, lck_pool ) );
    1721           0 :     if( is_unique_end ) {
    1722           0 :       interval->end_next     = lockout_slot->end_head;
    1723           0 :       lockout_slot->end_head = (uint)lockout_interval_pool_idx( lck_pool, interval );
    1724           0 :     }
    1725           0 :   }
    1726           0 : }
    1727             : 
    1728             : void
    1729             : fd_tower_lockos_remove( fd_tower_t * tower,
    1730           0 :                         ulong        slot ) {
    1731             : 
    1732           0 :   lockout_interval_map_t * lck_map       = tower->lck_map;
    1733           0 :   lockout_interval_t *     lck_pool      = tower->lck_pool;
    1734           0 :   lockout_slot_map_t *     lck_slot_map  = tower->lck_slot_map;
    1735           0 :   lockout_slot_t *         lck_slot_pool = tower->lck_slot_pool;
    1736             : 
    1737             :   /* First remove the lockout slot header from the fork slot map */
    1738           0 :   uint             slot_key     = (uint)slot;
    1739           0 :   lockout_slot_t * lockout_slot = lockout_slot_map_ele_remove( lck_slot_map, &slot_key, NULL, lck_slot_pool );
    1740           0 :   if( FD_UNLIKELY( !lockout_slot ) ) return;
    1741             : 
    1742             :   /* Iterate through unique end interval and for each of those, release
    1743             :      every interval sharing that same end slot. */
    1744           0 :   uint  end_idx  = lockout_slot->end_head;
    1745           0 :   ulong slot_idx = lockout_slot_pool_idx( lck_slot_pool, lockout_slot );
    1746           0 :   while( end_idx!=lockout_interval_pool_idx_null( lck_pool ) ) {
    1747           0 :     lockout_interval_t * unique_end   = lockout_interval_pool_ele( lck_pool, end_idx );
    1748           0 :     uint                 next_end_idx = unique_end->end_next;
    1749           0 :     uint                 interval_end = unique_end->key.interval_end;
    1750             : 
    1751           0 :     lockout_interval_key_t key = lockout_interval_key( slot_idx, interval_end, 0U, 0UL );
    1752           0 :     for( lockout_interval_t * itrvl = lockout_interval_map_ele_remove( lck_map, &key, NULL, lck_pool );
    1753           0 :                             !!itrvl;
    1754           0 :                               itrvl = lockout_interval_map_ele_remove( lck_map, &key, NULL, lck_pool ) ) {
    1755           0 :       lockout_pubkey_ref_t * ref = lockout_pubkey_pool_ele( tower->lck_pubkey_pool, lockout_interval_key_pubkey_idx( &itrvl->key ) );
    1756           0 :       if( FD_LIKELY( !--ref->ref_cnt ) ) {
    1757           0 :         FD_CHECK_CRIT( lockout_pubkey_map_ele_remove( tower->lck_pubkey_map, &ref->addr, NULL, tower->lck_pubkey_pool ), "unable to remove tower lockout pubkey" );
    1758           0 :         lockout_pubkey_pool_ele_release( tower->lck_pubkey_pool, ref );
    1759           0 :       }
    1760           0 :       lockout_interval_pool_ele_release( lck_pool, itrvl );
    1761           0 :     }
    1762           0 :     end_idx = next_end_idx;
    1763           0 :   }
    1764           0 :   lockout_slot_pool_ele_release( lck_slot_pool, lockout_slot );
    1765           0 : }

Generated by: LCOV version 1.14