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

Generated by: LCOV version 1.14