LCOV - code coverage report
Current view: top level - choreo/tower - fd_tower.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 139 431 32.3 %
Date: 2026-02-13 06:06:24 Functions: 6 17 35.3 %

          Line data    Source code
       1             : #include <limits.h>
       2             : #include <unistd.h>
       3             : #include <fcntl.h>
       4             : #include <sys/stat.h>
       5             : 
       6             : #include "fd_tower.h"
       7             : #include "../voter/fd_voter.h"
       8             : #include "../voter/fd_voter_private.h"
       9             : #include "fd_tower_forks.h"
      10             : #include "fd_tower_serde.h"
      11             : #include "../../flamenco/txn/fd_txn_generate.h"
      12             : #include "../../flamenco/runtime/fd_system_ids.h"
      13             : 
      14             : #define LOGGING 0
      15             : 
      16           0 : #define THRESHOLD_DEPTH (8)
      17           0 : #define THRESHOLD_RATIO (2.0 / 3.0)
      18             : #define SWITCH_RATIO    (0.38)
      19             : 
      20             : /* expiration calculates the expiration slot of vote given a slot and
      21             :    confirmation count. */
      22             : 
      23             : static inline ulong
      24         132 : expiration_slot( fd_tower_t const * vote ) {
      25         132 :   ulong lockout = 1UL << vote->conf;
      26         132 :   return vote->slot + lockout;
      27         132 : }
      28             : 
      29             : /* simulate_vote simulates voting for slot, popping all votes from the
      30             :    top that would be consecutively expired by voting for slot. */
      31             : 
      32             : ulong
      33             : simulate_vote( fd_tower_t const * tower,
      34         141 :                ulong              slot ) {
      35         141 :   ulong cnt = fd_tower_cnt( tower );
      36         147 :   while( cnt ) {
      37         132 :     fd_tower_t const * top_vote = fd_tower_peek_index_const( tower, cnt - 1 );
      38         132 :     if( FD_LIKELY( expiration_slot( top_vote ) >= slot ) ) break; /* expire only if consecutive */
      39           6 :     cnt--;
      40           6 :   }
      41         141 :   return cnt;
      42         141 : }
      43             : 
      44             : /* push_vote pushes a new vote for slot onto the tower.  Pops and
      45             :    returns the new root (bottom of the tower) if it reaches max lockout
      46             :    as a result of the new vote.  Otherwise, returns ULONG_MAX.
      47             : 
      48             :    Max lockout is equivalent to 1 << FD_TOWER_VOTE_MAX + 1 (which
      49             :    implies confirmation count is FD_TOWER_VOTE_MAX + 1).  As a result,
      50             :    fd_tower_vote also maintains the invariant that the tower contains at
      51             :    most FD_TOWER_VOTE_MAX votes, because (in addition to vote expiry)
      52             :    there will always be a pop before reaching FD_TOWER_VOTE_MAX + 1. */
      53             : 
      54             : ulong
      55             : push_vote( fd_tower_t * tower,
      56         141 :            ulong        slot ) {
      57             : 
      58         141 : # if FD_TOWER_PARANOID
      59         141 :   fd_tower_t const * vote = fd_tower_peek_tail_const( tower );
      60         141 :   if( FD_UNLIKELY( vote && slot <= vote->slot ) ) FD_LOG_ERR(( "[%s] slot %lu <= vote->slot %lu", __func__, slot, vote->slot )); /* caller error*/
      61         141 : # endif
      62             : 
      63             :   /* Use simulate_vote to determine how many expired votes to pop. */
      64             : 
      65         141 :   ulong cnt = simulate_vote( tower, slot );
      66             : 
      67             :   /* Pop everything that got expired. */
      68             : 
      69         147 :   while( FD_LIKELY( fd_tower_cnt( tower ) > cnt ) ) {
      70           6 :     fd_tower_pop_tail( tower );
      71           6 :   }
      72             : 
      73             :   /* If the tower is still full after expiring, then pop and return the
      74             :      bottom vote slot as the new root because this vote has incremented
      75             :      it to max lockout.  Otherwise this is a no-op and there is no new
      76             :      root (FD_SLOT_NULL). */
      77             : 
      78         141 :   ulong root = FD_SLOT_NULL;
      79         141 :   if( FD_LIKELY( fd_tower_full( tower ) ) ) { /* optimize for full tower */
      80           0 :     root = fd_tower_pop_head( tower ).slot;
      81           0 :   }
      82             : 
      83             :   /* Increment confirmations (double lockouts) for consecutive
      84             :      confirmations in prior votes. */
      85             : 
      86         141 :   ulong prev_conf = 0;
      87         141 :   for( fd_tower_iter_t iter = fd_tower_iter_init_rev( tower       );
      88        1635 :                              !fd_tower_iter_done_rev( tower, iter );
      89        1494 :                        iter = fd_tower_iter_prev    ( tower, iter ) ) {
      90        1494 :     fd_tower_t * vote = fd_tower_iter_ele( tower, iter );
      91        1494 :     if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) break;
      92        1494 :     vote->conf++;
      93        1494 :   }
      94             : 
      95             :   /* Add the new vote to the tower. */
      96             : 
      97         141 :   fd_tower_push_tail( tower, (fd_tower_t){ .slot = slot, .conf = 1 } );
      98             : 
      99             :   /* Return the new root (FD_SLOT_NULL if there is none). */
     100             : 
     101         141 :   return root;
     102         141 : }
     103             : 
     104             : /* lockout_check checks if we are locked out from voting for slot.
     105             :    Returns 1 if we can vote for slot without violating lockout, 0
     106             :    otherwise.
     107             : 
     108             :    After voting for a slot n, we are locked out for 2^k slots, where k
     109             :    is the confirmation count of that vote.  Once locked out, we cannot
     110             :    vote for a different fork until that previously-voted fork expires at
     111             :    slot n+2^k.  This implies the earliest slot in which we can switch
     112             :    from the previously-voted fork is (n+2^k)+1.  We use `ghost` to
     113             :    determine whether `slot` is on the same or different fork as previous
     114             :    vote slots.
     115             : 
     116             :    In the case of the tower, every vote has its own expiration slot
     117             :    depending on confirmations. The confirmation count is the max number
     118             :    of consecutive votes that have been pushed on top of the vote, and
     119             :    not necessarily its current height in the tower.
     120             : 
     121             :    For example, the following is a diagram of a tower pushing and
     122             :    popping with each vote:
     123             : 
     124             : 
     125             :    slot | confirmation count
     126             :    -----|-------------------
     127             :    4    |  1 <- vote
     128             :    3    |  2
     129             :    2    |  3
     130             :    1    |  4
     131             : 
     132             : 
     133             :    slot | confirmation count
     134             :    -----|-------------------
     135             :    9    |  1 <- vote
     136             :    2    |  3
     137             :    1    |  4
     138             : 
     139             : 
     140             :    slot | confirmation count
     141             :    -----|-------------------
     142             :    10   |  1 <- vote
     143             :    9    |  2
     144             :    2    |  3
     145             :    1    |  4
     146             : 
     147             : 
     148             :    slot | confirmation count
     149             :    -----|-------------------
     150             :    11   |  1 <- vote
     151             :    10   |  2
     152             :    9    |  3
     153             :    2    |  4
     154             :    1    |  5
     155             : 
     156             : 
     157             :    slot | confirmation count
     158             :    -----|-------------------
     159             :    18   |  1 <- vote
     160             :    2    |  4
     161             :    1    |  5
     162             : 
     163             : 
     164             :    In the final tower, note the gap in confirmation counts between slot
     165             :    18 and slot 2, even though slot 18 is directly above slot 2. */
     166             : 
     167             : int
     168             : lockout_check( fd_tower_t const * tower,
     169             :                fd_forks_t       * forks,
     170           0 :                ulong              slot ) {
     171             : 
     172           0 :   if( FD_UNLIKELY( fd_tower_empty( tower )                         ) ) return 1; /* always not locked out if we haven't voted. */
     173           0 :   if( FD_UNLIKELY( slot <= fd_tower_peek_tail_const( tower )->slot ) ) return 0; /* always locked out from voting for slot <= last vote slot */
     174             : 
     175             :   /* Simulate a vote to pop off all the votes that would be expired by
     176             :      voting for slot.  Then check if the newly top-of-tower vote is on
     177             :      the same fork as slot (if so this implies we can vote for it). */
     178             : 
     179           0 :   ulong cnt = simulate_vote( tower, slot ); /* pop off votes that would be expired */
     180           0 :   if( FD_UNLIKELY( !cnt ) ) return 1;       /* tower is empty after popping expired votes */
     181             : 
     182           0 :   fd_tower_t const * vote    = fd_tower_peek_index_const( tower, cnt - 1 );            /* newly top-of-tower */
     183           0 :   int                lockout = fd_forks_is_slot_descendant( forks, vote->slot, slot ); /* check if on same fork */
     184           0 :   FD_LOG_INFO(( "[%s] lockout? %d. last_vote_slot: %lu. slot: %lu", __func__, lockout, vote->slot, slot ));
     185           0 :   return lockout;
     186           0 : }
     187             : 
     188             : /* switch_check checks if we can switch to the fork of `slot`.  Returns
     189             :    1 if we can switch, 0 otherwise.  Assumes tower is non-empty.
     190             : 
     191             :    There are two forks of interest: our last vote fork ("vote fork") and
     192             :    the fork we want to switch to ("switch fork").  The switch fork is on
     193             :    the fork of `slot`.
     194             : 
     195             :    In order to switch, FD_TOWER_SWITCH_PCT of stake must have voted for
     196             :    a slot that satisfies the following conditions: the
     197             :    GCA(slot, last_vote) is an ancestor of the switch_slot
     198             : 
     199             :    Recall from the lockout check a validator is locked out from voting
     200             :    for our last vote slot when their last vote slot is on a different
     201             :    fork, and that vote's expiration slot > our last vote slot.
     202             : 
     203             :    The following pseudocode describes the algorithm:
     204             : 
     205             :    ```
     206             :    for every fork f in the fork tree, take the most recently executed
     207             :    slot `s` (the leaf of the fork).
     208             : 
     209             :    Take the greatest common ancestor of the `s` and the our last vote
     210             :    slot. If the switch_slot is a descendant of this GCA, then votes for
     211             :    `s` can count towards the switch threshold.
     212             : 
     213             :      query banks(`s`) for vote accounts in `s`
     214             :        for all vote accounts v in `s`
     215             :           if v's  locked out[1] from voting for our latest vote slot
     216             :              add v's stake to switch stake
     217             : 
     218             :    return switch stake >= FD_TOWER_SWITCH_PCT
     219             :    ```
     220             : 
     221             :    The switch check is used to safeguard optimistic confirmation.
     222             :    Specifically: FD_TOWER_OPT_CONF_PCT + FD_TOWER_SWITCH_PCT >= 1. */
     223             : 
     224             : int
     225             : switch_check( fd_tower_t const  * tower,
     226             :               fd_forks_t        * forks,
     227             :               fd_epoch_stakes_t * epoch_stakes,
     228             :               ulong               total_stake,
     229          33 :               ulong               switch_slot ) {
     230          33 :   ulong switch_stake   = 0;
     231          33 :   ulong last_vote_slot = fd_tower_peek_tail_const( tower )->slot;
     232          33 :   ulong root_slot      = fd_tower_peek_head_const( tower )->slot;
     233          33 :   for ( fd_tower_leaves_dlist_iter_t iter = fd_tower_leaves_dlist_iter_fwd_init( forks->tower_leaves_dlist, forks->tower_leaves_pool );
     234         138 :                                            !fd_tower_leaves_dlist_iter_done( iter, forks->tower_leaves_dlist, forks->tower_leaves_pool );
     235         120 :                                      iter = fd_tower_leaves_dlist_iter_fwd_next( iter, forks->tower_leaves_dlist, forks->tower_leaves_pool ) ) {
     236             : 
     237             :     /* Iterate over all the leaves of all forks */
     238         120 :     fd_tower_leaf_t  * leaf = fd_tower_leaves_dlist_iter_ele( iter, forks->tower_leaves_dlist, forks->tower_leaves_pool );
     239         120 :     ulong candidate_slot = leaf->slot;
     240         120 :     ulong lca = fd_forks_lowest_common_ancestor( forks, candidate_slot, last_vote_slot );
     241             : 
     242         120 :     if( lca != ULONG_MAX && fd_forks_is_slot_descendant( forks, lca, switch_slot ) ) {
     243             : 
     244             :       /* This candidate slot may be considered for the switch proof, if
     245             :          it passes the following conditions:
     246             : 
     247             :          https://github.com/anza-xyz/agave/blob/c7b97bc77addacf03b229c51b47c18650d909576/core/src/consensus.rs#L1117
     248             : 
     249             :          Now for this candidate slot, look at the lockouts that were created at
     250             :          the time that we processed the bank for this candidate slot. */
     251             : 
     252          78 :       for( fd_lockout_slots_t const * slot = fd_lockout_slots_map_ele_query_const( forks->lockout_slots_map, &candidate_slot, NULL, forks->lockout_slots_pool );
     253          84 :                                       slot;
     254          78 :                                       slot = fd_lockout_slots_map_ele_next_const ( slot, NULL, forks->lockout_slots_pool ) ) {
     255          21 :         ulong interval_end = slot->interval_end;
     256          21 :         ulong key = fd_lockout_interval_key( candidate_slot, interval_end );
     257             : 
     258             :         /* Intervals are keyed by the end of the interval. If the end of
     259             :            the interval is < the last vote slot, then these vote
     260             :            accounts with this particular lockout are NOT locked out from
     261             :            voting for the last vote slot, which means we can skip this
     262             :            set of intervals. */
     263             : 
     264          21 :         if( interval_end < last_vote_slot ) continue;
     265             : 
     266             :         /* At this point we can actually query for the intervals by
     267             :            end interval to get the vote accounts. */
     268             : 
     269          15 :         for( fd_lockout_intervals_t const * interval = fd_lockout_intervals_map_ele_query_const( forks->lockout_intervals_map, &key, NULL, forks->lockout_intervals_pool );
     270          24 :                                             interval;
     271          24 :                                             interval = fd_lockout_intervals_map_ele_next_const( interval, NULL, forks->lockout_intervals_pool ) ) {
     272          24 :           ulong vote_slot            =  interval->interval_start;
     273          24 :           fd_hash_t const * vote_acc = &interval->vote_account_pubkey;
     274             : 
     275          24 :           if( FD_UNLIKELY( !fd_forks_is_slot_descendant( forks, vote_slot, last_vote_slot ) &&
     276          24 :                             vote_slot > root_slot ) ) {
     277          24 :             fd_voter_stake_key_t key = { .vote_account = *vote_acc, .slot = switch_slot };
     278          24 :             fd_voter_stake_t const * voter_stake = fd_voter_stake_map_ele_query_const( epoch_stakes->voter_stake_map, &key, NULL, epoch_stakes->voter_stake_pool );
     279          24 :             if( FD_UNLIKELY( !voter_stake ) ) {
     280           0 :               FD_BASE58_ENCODE_32_BYTES( vote_acc->key, vote_acc_b58 );
     281           0 :               FD_LOG_CRIT(( "missing voter stake for vote account %s on slot %lu. Is this an error?", vote_acc_b58, switch_slot ));
     282           0 :             }
     283          24 :             ulong voter_idx = fd_voter_stake_pool_idx( epoch_stakes->voter_stake_pool, voter_stake );
     284             : 
     285             :             /* Don't count this vote account towards the switch cqheck if it has already been used. */
     286          24 :             if( FD_UNLIKELY( fd_used_acc_scratch_test( epoch_stakes->used_acc_scratch, voter_idx ) ) ) continue;
     287             : 
     288          24 :             fd_used_acc_scratch_insert( epoch_stakes->used_acc_scratch, voter_idx );
     289          24 :             switch_stake += voter_stake->stake;
     290          24 :             if( FD_LIKELY( (double)switch_stake >= (double)total_stake * SWITCH_RATIO ) ) {
     291          15 :               fd_used_acc_scratch_null( epoch_stakes->used_acc_scratch );
     292          15 :               FD_LOG_INFO(( "[%s] switch? 1. last_vote_slot: %lu. switch_slot: %lu. pct: %.0lf%%", __func__, last_vote_slot, switch_slot, (double)switch_stake / (double)total_stake * 100.0 ));
     293          15 :               return 1;
     294          15 :             }
     295          24 :           }
     296          24 :         }
     297          15 :       }
     298          78 :     }
     299         120 :   }
     300          18 :   fd_used_acc_scratch_null( epoch_stakes->used_acc_scratch );
     301          18 :   FD_LOG_INFO(( "[%s] switch? 0. last_vote_slot: %lu. switch_slot: %lu. pct: %.0lf%%", __func__, last_vote_slot, switch_slot, (double)switch_stake / (double)total_stake * 100.0 ));
     302          18 :   return 0;
     303          33 : }
     304             : 
     305             : /* threshold_check checks if we pass the threshold required to vote for
     306             :    `slot`.  Returns 1 if we pass the threshold check, 0 otherwise.
     307             : 
     308             :    The following psuedocode describes the algorithm:
     309             : 
     310             :    ```
     311             :    simulate that we have voted for `slot`
     312             : 
     313             :    for all vote accounts in the current epoch
     314             : 
     315             :       simulate that the vote account has voted for `slot`
     316             : 
     317             :       pop all votes expired by that simulated vote
     318             : 
     319             :       if the validator's latest tower vote after expiry >= our threshold
     320             :       slot ie. our vote from THRESHOLD_DEPTH back also after simulating,
     321             :       then add validator's stake to threshold_stake.
     322             : 
     323             :    return threshold_stake >= FD_TOWER_THRESHOLD_RATIO
     324             :    ```
     325             : 
     326             :    The threshold check simulates voting for the current slot to expire
     327             :    stale votes.  This is to prevent validators that haven't voted in a
     328             :    long time from counting towards the threshold stake. */
     329             : 
     330             : int
     331             : threshold_check( fd_tower_t       const * tower,
     332             :                  fd_tower_accts_t const * accts,
     333             :                  ulong                    total_stake,
     334           0 :                  ulong                    slot ) {
     335             : 
     336           0 :   uchar __attribute__((aligned(FD_TOWER_ALIGN))) scratch[ FD_TOWER_FOOTPRINT ];
     337           0 :   fd_tower_t * scratch_tower = fd_tower_join( fd_tower_new( scratch ) );
     338             : 
     339             :   /* First, simulate a vote on our tower, popping off everything that
     340             :      would be expired by voting for slot. */
     341             : 
     342           0 :   ulong cnt = simulate_vote( tower, slot );
     343             : 
     344             :   /* We can always vote if our tower is not at least THRESHOLD_DEPTH
     345             :      deep after simulating. */
     346             : 
     347           0 :   if( FD_UNLIKELY( cnt < THRESHOLD_DEPTH ) ) return 1;
     348             : 
     349             :   /* Get the vote slot from THRESHOLD_DEPTH back. Note THRESHOLD_DEPTH
     350             :      is the 8th index back _including_ the simulated vote at index 0. */
     351             : 
     352           0 :   ulong threshold_slot  = fd_tower_peek_index_const( tower, cnt - THRESHOLD_DEPTH )->slot;
     353           0 :   ulong threshold_stake = 0;
     354           0 :   for( fd_tower_accts_iter_t iter = fd_tower_accts_iter_init( accts       );
     355           0 :                                    !fd_tower_accts_iter_done( accts, iter );
     356           0 :                              iter = fd_tower_accts_iter_next( accts, iter ) ) {
     357           0 :     fd_tower_accts_t const * acct = fd_tower_accts_iter_ele_const( accts, iter );
     358           0 :     fd_tower_remove_all( scratch_tower );
     359           0 :     fd_tower_from_vote_acc( scratch_tower, acct->data );
     360             : 
     361           0 :     ulong cnt = simulate_vote( scratch_tower, slot ); /* expire votes */
     362           0 :     if( FD_UNLIKELY( !cnt ) ) continue;               /* no votes left after expiry */
     363             : 
     364             :     /* Count their stake towards the threshold check if their last vote
     365             :        slot >= our threshold slot.
     366             : 
     367             :        We know these votes are for our own fork because towers are
     368             :        sourced from vote _accounts_, not vote _transactions_.
     369             : 
     370             :        Because we are iterating vote accounts on the same fork that we
     371             :        we want to vote for, we know these slots must all occur along the
     372             :        same fork ancestry.
     373             : 
     374             :        Therefore, if their latest vote slot >= our threshold slot, we
     375             :        know that vote must be for the threshold slot itself or one of
     376             :        threshold slot's descendants. */
     377             : 
     378           0 :     ulong last_vote = fd_tower_peek_index_const( scratch_tower, cnt - 1 )->slot;
     379           0 :     if( FD_LIKELY( last_vote >= threshold_slot ) ) threshold_stake += acct->stake;
     380           0 :   }
     381             : 
     382           0 :   double threshold_pct = (double)threshold_stake / (double)total_stake;
     383           0 :   int    threshold     = threshold_pct > THRESHOLD_RATIO;
     384           0 :   FD_LOG_INFO(( "[%s] threshold? %d. top: %lu. threshold: %lu. pct: %.0lf%%.", __func__, threshold, fd_tower_peek_tail_const( tower )->slot, threshold_slot, threshold_pct * 100.0 ));
     385           0 :   return threshold;
     386           0 : }
     387             : 
     388             : int
     389             : propagated_check( fd_notar_t * notar,
     390           0 :                   ulong        slot ) {
     391             : 
     392           0 :   fd_notar_slot_t * notar_slot = fd_notar_slot_query( notar->slot_map, slot, NULL );
     393           0 :   if( FD_UNLIKELY( !notar_slot ) ) return 1;
     394             : 
     395           0 :   if( FD_LIKELY( notar_slot->is_leader                   ) ) return 1; /* can always vote for slot in which we're leader */
     396           0 :   if( FD_LIKELY( notar_slot->prev_leader_slot==ULONG_MAX ) ) return 1; /* haven't been leader yet */
     397             : 
     398           0 :   fd_notar_slot_t * prev_leader_notar_slot = fd_notar_slot_query( notar->slot_map, notar_slot->prev_leader_slot, NULL );
     399           0 :   if( FD_LIKELY( !prev_leader_notar_slot ) ) return 1; /* already pruned rooted */
     400             : 
     401           0 :   return prev_leader_notar_slot->is_propagated;
     402           0 : }
     403             : 
     404             : fd_tower_out_t
     405             : fd_tower_vote_and_reset( fd_tower_t        * tower,
     406             :                          fd_tower_accts_t  * accts,
     407             :                          fd_epoch_stakes_t * epoch_stakes,
     408             :                          fd_forks_t        * forks,
     409             :                          fd_ghost_t        * ghost,
     410           0 :                          fd_notar_t        * notar ) {
     411             : 
     412           0 :   uchar                  flags     = 0;
     413           0 :   fd_ghost_blk_t const * best_blk  = fd_ghost_best( ghost, fd_ghost_root( ghost ) );
     414           0 :   fd_ghost_blk_t const * reset_blk = NULL;
     415           0 :   fd_ghost_blk_t const * vote_blk  = NULL;
     416             : 
     417             :   /* Case 0: if we haven't voted yet then we can always vote and reset
     418             :      to ghost_best.
     419             : 
     420             :      https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L933-L935 */
     421             : 
     422           0 :   if( FD_UNLIKELY( fd_tower_empty( tower ) ) ) {
     423           0 :     fd_tower_forks_t * fork = fd_forks_query( forks, best_blk->slot );
     424           0 :     fork->voted             = 1;
     425           0 :     fork->voted_block_id    = best_blk->id;
     426           0 :     return (fd_tower_out_t){
     427           0 :       .flags          = flags,
     428           0 :       .reset_slot     = best_blk->slot,
     429           0 :       .reset_block_id = best_blk->id,
     430           0 :       .vote_slot      = best_blk->slot,
     431           0 :       .vote_block_id  = best_blk->id,
     432           0 :       .root_slot      = push_vote( tower, best_blk->slot )
     433           0 :     };
     434           0 :   }
     435             : 
     436           0 :   ulong              prev_vote_slot     = fd_tower_peek_tail_const( tower )->slot;
     437           0 :   fd_tower_forks_t * prev_vote_fork     = fd_forks_query( forks, prev_vote_slot );
     438             : 
     439             : 
     440           0 :   if( FD_UNLIKELY( !prev_vote_fork->voted ) ) {
     441             : 
     442             :     /* It's possible prev_vote_fork->voted is not set even though we
     443             :        popped it from the top of our tower.  This can happen when there
     444             :        are multiple nodes operating with the same vote account.
     445             : 
     446             :        In a typical setup involving a primary staked node and backup
     447             :        unstaked node, the two nodes' towers will usually be identical
     448             :        but occassionally diverge when one node observes a minority fork
     449             :        the other doesn't.  As a result, one node might be locked out
     450             :        from voting for a fork that the other node is not.
     451             : 
     452             :        This becomes a problem in our primary-backup setup when the
     453             :        unstaked node is locked out but the staked node is not.  The
     454             :        staked node ultimately lands the vote into the on-chain vote
     455             :        account, so it's possible when the unstaked node reads back their
     456             :        on-chain vote account to diff against their local tower, there
     457             :        are votes in there they themselves did not vote for due to
     458             :        lockout (fd_tower_reconcile).
     459             : 
     460             :        As a result, `voted_block_id` will not be set for slots in their
     461             :        tower, which normally would be an invariant violation because the
     462             :        node must have set this value when they voted for the slot (and
     463             :        pushed it to their tower).
     464             : 
     465             :        So here we manually set the voted_block_id to replayed_block_id
     466             :        if not already set. We know we must have replayed it, because to
     467             :        observe the on-chain tower you must have replayed all the slots
     468             :        in the tower. */
     469             : 
     470             :     /* FIXME this needs to be thought through more carefully for set-identity. */
     471             : 
     472           0 :     prev_vote_fork->voted          = 1;
     473           0 :     prev_vote_fork->voted_block_id = prev_vote_fork->replayed_block_id;
     474           0 :   }
     475             : 
     476           0 :   fd_hash_t        * prev_vote_block_id = &prev_vote_fork->voted_block_id;
     477           0 :   fd_ghost_blk_t   * prev_vote_blk      = fd_ghost_query( ghost, prev_vote_block_id );
     478             : 
     479             :   /* Case 1: if an ancestor of our prev vote (including prev vote
     480             :      itself) is an unconfirmed duplicate, then our prev vote was on a
     481             :      duplicate fork.
     482             : 
     483             :      There are two subcases to check. */
     484             : 
     485           0 :   int invalid_ancestor = !!fd_ghost_invalid_ancestor( ghost, prev_vote_blk );
     486           0 :   if( FD_UNLIKELY( invalid_ancestor ) ) { /* do we have an invalid ancestor? */
     487             : 
     488             :     /* Case 1a: ghost_best is an ancestor of prev vote.  This means
     489             :        ghost_best is rolling back to an ancestor that precedes the
     490             :        duplicate ancestor on the same fork as our prev vote.  In this
     491             :        case, we can't vote on our ancestor, but we do reset to that
     492             :        ancestor.
     493             : 
     494             :        https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1016-L1019 */
     495             : 
     496           0 :     int ancestor_rollback = prev_vote_blk != best_blk && !!fd_ghost_ancestor( ghost, prev_vote_blk, &best_blk->id );
     497           0 :     if( FD_LIKELY( ancestor_rollback ) ) {
     498           0 :       flags     = fd_uchar_set_bit( flags, FD_TOWER_FLAG_ANCESTOR_ROLLBACK );
     499           0 :       reset_blk = best_blk;
     500           0 :     }
     501             : 
     502             :     /* Case 1b: ghost_best is not an ancestor, but prev_vote is a
     503             :        duplicate and we've confirmed its duplicate sibling.  In this
     504             :        case, we allow switching to ghost_best without a switch proof.
     505             : 
     506             :        Example: slot 5 is a duplicate.  We first receive, replay and
     507             :        vote for block 5, so that is our prev vote.  We later receive
     508             :        block 5' and observe that it is duplicate confirmed.  ghost_best
     509             :        now returns block 5' and we both vote and reset to block 5'
     510             :        regardless of the switch check.
     511             : 
     512             :        https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1021-L1024 */
     513             : 
     514           0 :     int sibling_confirmed = 0!=memcmp( &prev_vote_fork->voted_block_id, &prev_vote_fork->confirmed_block_id, sizeof(fd_hash_t) );
     515           0 :     if( FD_LIKELY( sibling_confirmed ) ) {
     516           0 :       flags     = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SIBLING_CONFIRMED );
     517           0 :       reset_blk = best_blk;
     518           0 :       vote_blk  = best_blk;
     519           0 :     }
     520             : 
     521             :     /* At this point our prev vote was on a duplicate fork but didn't
     522             :        match either of the above subcases.
     523             : 
     524             :        In this case, we have to pass the switch check to reset to a
     525             :        different fork from prev vote (same as non-duplicate case). */
     526           0 :   }
     527             : 
     528             :   /* Case 2: if our prev vote slot is an ancestor of the best slot, then
     529             :      they are on the same fork and we can both reset to it.  We can also
     530             :      vote for it if we pass the can_vote checks.
     531             : 
     532             :      https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1057 */
     533             : 
     534           0 :   else if( FD_LIKELY( best_blk->slot == prev_vote_slot || fd_forks_is_slot_ancestor( forks, best_blk->slot, prev_vote_slot ) ) ) {
     535           0 :     flags     = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SAME_FORK );
     536           0 :     reset_blk = best_blk;
     537           0 :     vote_blk  = best_blk;
     538           0 :   }
     539             : 
     540             :   /* Case 3: if our prev vote is not an ancestor of the best block, then
     541             :      it is on a different fork.  If we pass the switch check, we can
     542             :      reset to it.  If we additionally pass the lockout check, we can
     543             :      also vote for it.
     544             : 
     545             :      https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1208-L1215
     546             : 
     547             :      Note also Agave uses the best blk's total stake for checking the
     548             :      threshold.
     549             : 
     550             :      https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/fork_choice.rs#L443-L445 */
     551             : 
     552           0 :   else if( FD_LIKELY( switch_check( tower, forks, epoch_stakes, best_blk->total_stake, best_blk->slot ) ) ) {
     553           0 :     flags     = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_PASS );
     554           0 :     reset_blk = best_blk;
     555           0 :     vote_blk  = best_blk;
     556           0 :   }
     557             : 
     558             :   /* Case 4: same as case 3 but we didn't pass the switch check.  In
     559             :      this case we reset to either ghost_best or ghost_deepest beginning
     560             :      from our prev vote blk.
     561             : 
     562             :      We must reset to a block beginning from our prev vote fork to
     563             :      ensure votes get a chance to propagate.  Because in order for votes
     564             :      to land, someone needs to build a block on that fork.
     565             : 
     566             :      We reset to ghost_best or ghost_deepest depending on whether our
     567             :      prev vote is valid.  When it's invalid we use ghost_deepest instead
     568             :      of ghost_best, because ghost_best won't be able to return a valid
     569             :      block beginning from our prev_vote because by definition the entire
     570             :      subtree will be invalid.
     571             : 
     572             :      When our prev vote fork is not a duplicate, we want to propagate
     573             :      votes that might allow others to switch to our fork.  In addition,
     574             :      if our prev vote fork is a duplicate, we want to propagate votes
     575             :      that might "duplicate confirm" that block (reach 52% of stake).
     576             : 
     577             :      See top-level documentation in fd_tower.h for more details on vote
     578             :      propagation. */
     579             : 
     580           0 :   else {
     581             : 
     582             :     /* Case 4a: failed switch check and last vote's fork is invalid.
     583             : 
     584             :       https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/heaviest_subtree_fork_choice.rs#L1187 */
     585             : 
     586           0 :     if( FD_UNLIKELY( invalid_ancestor ) ) {
     587           0 :       flags     = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_FAIL );
     588           0 :       reset_blk = fd_ghost_deepest( ghost, prev_vote_blk );
     589           0 :     }
     590             : 
     591             :     /* Case 4b: failed switch check and last vote's fork is valid.
     592             : 
     593             :       https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/fork_choice.rs#L200 */
     594             : 
     595           0 :     else {
     596           0 :       flags     = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_FAIL );
     597           0 :       reset_blk = fd_ghost_best( ghost, prev_vote_blk );
     598           0 :     }
     599           0 :   }
     600             : 
     601             :   /* If there is a block to vote for, there are a few additional checks
     602             :      to make sure we can actually vote for it.
     603             : 
     604             :      Specifically, we need to make sure we're not locked out, pass the
     605             :      threshold check and that our previous leader block has propagated
     606             :      (reached the prop threshold according to fd_notar).
     607             : 
     608             :      https://github.com/firedancer-io/agave/blob/master/core/src/consensus/fork_choice.rs#L382-L385
     609             : 
     610             :      Agave uses the total stake on the fork being threshold checked
     611             :      (vote_blk) for determining whether it meets the stake threshold. */
     612             : 
     613           0 :   if( FD_LIKELY( vote_blk ) ) {
     614           0 :     if     ( FD_UNLIKELY( !lockout_check( tower, forks, vote_blk->slot ) ) ) {
     615           0 :       flags    = fd_uchar_set_bit( flags, FD_TOWER_FLAG_LOCKOUT_FAIL );
     616           0 :       vote_blk = NULL;
     617           0 :     }
     618           0 :     else if( FD_UNLIKELY( !threshold_check( tower, accts, vote_blk->total_stake, vote_blk->slot ) ) ) {
     619           0 :       flags    = fd_uchar_set_bit( flags, FD_TOWER_FLAG_THRESHOLD_FAIL );
     620           0 :       vote_blk = NULL;
     621           0 :     }
     622           0 :     else if( FD_UNLIKELY( !propagated_check( notar, vote_blk->slot ) ) ) {
     623           0 :       flags    = fd_uchar_set_bit( flags, FD_TOWER_FLAG_PROPAGATED_FAIL );
     624           0 :       vote_blk = NULL;
     625           0 :     }
     626           0 :   }
     627             : 
     628           0 :   FD_TEST( reset_blk ); /* always a reset_blk */
     629           0 :   fd_tower_out_t out = {
     630           0 :     .flags          = flags,
     631           0 :     .reset_slot     = reset_blk->slot,
     632           0 :     .reset_block_id = reset_blk->id,
     633           0 :     .vote_slot      = ULONG_MAX,
     634           0 :     .root_slot      = ULONG_MAX
     635           0 :   };
     636             : 
     637             :   /* Finally, if our vote passed all the checks, we actually push the
     638             :      vote onto the tower. */
     639             : 
     640           0 :   if( FD_LIKELY( vote_blk ) ) {
     641           0 :     out.vote_slot     = vote_blk->slot;
     642           0 :     out.vote_block_id = vote_blk->id;
     643           0 :     out.root_slot     = push_vote( tower, vote_blk->slot );
     644             : 
     645             :     /* Query our tower fork for this slot we're voting for.  Note this
     646             :        can never be NULL because we record tower forks as we replay, and
     647             :        we should never be voting on something we haven't replayed. */
     648             : 
     649           0 :     fd_tower_forks_t * fork = fd_forks_query( forks, vote_blk->slot );
     650           0 :     fork->voted             = 1;
     651           0 :     fork->voted_block_id    = vote_blk->id;
     652             : 
     653             :     /* Query the root slot's block id from tower forks.  This block id
     654             :        may not necessarily be confirmed, because confirmation requires
     655             :        votes on the block itself (vs. block and its descendants).
     656             : 
     657             :        So if we have a confirmed block id, we return that.  Otherwise
     658             :        we return our own vote block id for that slot, which we assume
     659             :        is the cluster converged on by the time we're rooting it.
     660             : 
     661             :        The only way it is possible for us to root the wrong version of
     662             :        a block (ie. not the one the cluster confirmed) is if there is
     663             :        mass equivocation (>2/3 of threshold check stake has voted for
     664             :        two versions of a block).  This exceeds the equivocation safety
     665             :        threshold and we would eventually detect this via a bank hash
     666             :        mismatch and error out. */
     667             : 
     668           0 :     if( FD_LIKELY( out.root_slot!=ULONG_MAX ) ) {
     669           0 :       fd_tower_forks_t * root_fork = fd_forks_query( forks, out.root_slot );
     670           0 :       out.root_block_id            = *fd_ptr_if( root_fork->confirmed, &root_fork->confirmed_block_id, &root_fork->voted_block_id );
     671           0 :     }
     672           0 :   }
     673             : 
     674           0 :   FD_BASE58_ENCODE_32_BYTES( out.reset_block_id.uc, reset_block_id );
     675           0 :   FD_BASE58_ENCODE_32_BYTES( out.vote_block_id.uc,  vote_block_id  );
     676           0 :   FD_BASE58_ENCODE_32_BYTES( out.root_block_id.uc,  root_block_id  );
     677           0 :   FD_LOG_INFO(( "[%s] flags: %d. reset_slot: %lu (%s). vote_slot: %lu (%s). root_slot: %lu (%s).", __func__, out.flags, out.reset_slot, reset_block_id, out.vote_slot, vote_block_id, out.root_slot, root_block_id ));
     678           0 :   return out;
     679           0 : }
     680             : 
     681             : void
     682             : fd_tower_reconcile( fd_tower_t  * tower,
     683             :                     ulong         root,
     684           0 :                     uchar const * vote_account_data ) {
     685           0 :   ulong on_chain_vote = fd_voter_vote_slot( vote_account_data );
     686           0 :   ulong on_chain_root = fd_voter_root_slot( vote_account_data );
     687             : 
     688           0 :   fd_tower_vote_t const * last_vote      = fd_tower_peek_tail_const( tower );
     689           0 :   ulong                   last_vote_slot = last_vote ? last_vote->slot : ULONG_MAX;
     690             : 
     691           0 :   if( FD_UNLIKELY( ( on_chain_vote==ULONG_MAX && last_vote_slot==ULONG_MAX ) ) ) return;
     692           0 :   if( FD_LIKELY  ( ( on_chain_vote!=ULONG_MAX && last_vote_slot!=ULONG_MAX
     693           0 :                      && on_chain_vote <= last_vote_slot                    ) ) ) return;
     694             : 
     695             :   /* At this point our local tower is too old, and we need to replace it
     696             :      with our on-chain tower.  However, it's possible our local root is
     697             :      newer than the on-chain root (even though the tower is older).  The
     698             :      most likely reason this happens is because we just booted from a
     699             :      snapshot and the snapshot slot > on-chain root.
     700             : 
     701             :      So we need to filter out the stale votes < snapshot slot.  This
     702             :      mirrors the Agave logic:
     703             :      https://github.com/firedancer-io/agave/blob/master/core/src/replay_stage.rs#L3690-L3719 */
     704             : 
     705           0 :   if( FD_LIKELY( on_chain_root == ULONG_MAX || root > on_chain_root ) ) {
     706           0 :     fd_tower_remove_all( tower );
     707           0 :     fd_voter_t const * voter = (fd_voter_t const *)fd_type_pun_const( vote_account_data );
     708           0 :     uint               kind  = fd_uint_load_4_fast( vote_account_data ); /* skip node_pubkey */
     709           0 :     for( ulong i=0; i<fd_voter_votes_cnt( vote_account_data ); i++ ) {
     710           0 :       switch( kind ) {
     711           0 :       case FD_VOTER_V4: fd_tower_push_tail( tower, (fd_tower_vote_t){ .slot = v4_off( voter )[i].slot, .conf = v4_off( voter )[i].conf } ); break;
     712           0 :       case FD_VOTER_V3: fd_tower_push_tail( tower, (fd_tower_vote_t){ .slot = voter->v3.votes[i].slot, .conf = voter->v3.votes[i].conf } ); break;
     713           0 :       case FD_VOTER_V2: fd_tower_push_tail( tower, (fd_tower_vote_t){ .slot = voter->v2.votes[i].slot, .conf = voter->v2.votes[i].conf } ); break;
     714           0 :       default:          FD_LOG_ERR(( "unsupported voter account version: %u", kind ));
     715           0 :       }
     716           0 :     }
     717             : 
     718             :     /* Fast forward our tower to tower_root by retaining only votes >
     719             :        local tower root. */
     720             : 
     721           0 :     while( FD_LIKELY( !fd_tower_empty( tower ) ) ) {
     722           0 :       fd_tower_t const * vote = fd_tower_peek_head_const( tower );
     723           0 :       if( FD_LIKELY( vote->slot > root ) ) break;
     724           0 :       fd_tower_pop_head( tower );
     725           0 :     }
     726           0 :   }
     727           0 : }
     728             : 
     729             : void
     730             : fd_tower_from_vote_acc( fd_tower_t   * tower,
     731         132 :                         uchar  const * vote_acc ) {
     732         132 :   fd_voter_t const * voter = (fd_voter_t const *)fd_type_pun_const( vote_acc );
     733         132 :   uint               kind  = fd_uint_load_4_fast( vote_acc ); /* skip node_pubkey */
     734         264 :   for( ulong i=0; i<fd_voter_votes_cnt( vote_acc ); i++ ) {
     735         132 :     switch( kind ) {
     736           0 :     case FD_VOTER_V4: fd_tower_push_tail( tower, (fd_tower_vote_t){ .slot = v4_off( voter )[i].slot, .conf = v4_off( voter )[i].conf } ); break;
     737         132 :     case FD_VOTER_V3: fd_tower_push_tail( tower, (fd_tower_vote_t){ .slot = voter->v3.votes[i].slot, .conf = voter->v3.votes[i].conf } ); break;
     738           0 :     case FD_VOTER_V2: fd_tower_push_tail( tower, (fd_tower_vote_t){ .slot = voter->v2.votes[i].slot, .conf = voter->v2.votes[i].conf } ); break;
     739           0 :     default:          FD_LOG_ERR(( "unsupported voter account version: %u", kind ));
     740         132 :     }
     741         132 :   }
     742         132 : }
     743             : 
     744             : ulong
     745             : fd_tower_with_lat_from_vote_acc( fd_voter_vote_t tower[ static FD_TOWER_VOTE_MAX ],
     746           0 :                                  uchar const *      vote_acc ) {
     747           0 :   fd_voter_t const * voter = (fd_voter_t const *)fd_type_pun_const( vote_acc );
     748           0 :   uint               kind  = fd_uint_load_4_fast( vote_acc ); /* skip node_pubkey */
     749           0 :   for( ulong i=0; i<fd_voter_votes_cnt( vote_acc ); i++ ) {
     750           0 :     switch( kind ) {
     751           0 :     case FD_VOTER_V4: tower[ i ] = (fd_voter_vote_t){ .latency = v4_off( voter )[i].latency, .slot = v4_off( voter )[i].slot, .conf = v4_off( voter )[i].conf }; break;
     752           0 :     case FD_VOTER_V3: tower[ i ] = (fd_voter_vote_t){ .latency = voter->v3.votes[i].latency, .slot = voter->v3.votes[i].slot, .conf = voter->v3.votes[i].conf }; break;
     753           0 :     case FD_VOTER_V2: tower[ i ] = (fd_voter_vote_t){ .latency = UCHAR_MAX,                  .slot = voter->v2.votes[i].slot, .conf = voter->v2.votes[i].conf }; break;
     754           0 :     default:          FD_LOG_ERR(( "unsupported voter account version: %u", kind ));
     755           0 :     }
     756           0 :   }
     757             : 
     758           0 :   return fd_voter_votes_cnt( vote_acc );
     759           0 : }
     760             : 
     761             : void
     762             : fd_tower_to_vote_txn( fd_tower_t const *    tower,
     763             :                       ulong                 root,
     764             :                       fd_hash_t const *     bank_hash,
     765             :                       fd_hash_t const *     block_id,
     766             :                       fd_hash_t const *     recent_blockhash,
     767             :                       fd_pubkey_t const *   validator_identity,
     768             :                       fd_pubkey_t const *   vote_authority,
     769             :                       fd_pubkey_t const *   vote_acc,
     770           3 :                       fd_txn_p_t *          vote_txn ) {
     771             : 
     772           3 :   FD_TEST( fd_tower_cnt( tower )<=FD_TOWER_VOTE_MAX );
     773           3 :   fd_compact_tower_sync_serde_t tower_sync_serde = {
     774           3 :     .root             = fd_ulong_if( root == ULONG_MAX, 0UL, root ),
     775           3 :     .lockouts_cnt     = (ushort)fd_tower_cnt( tower ),
     776             :     /* .lockouts populated below */
     777           3 :     .hash             = *bank_hash,
     778           3 :     .timestamp_option = 1,
     779           3 :     .timestamp        = fd_log_wallclock() / (long)1e9, /* seconds */
     780           3 :     .block_id         = *block_id
     781           3 :   };
     782             : 
     783           3 :   ulong i = 0UL;
     784           3 :   ulong prev = tower_sync_serde.root;
     785           3 :   for( fd_tower_iter_t iter = fd_tower_iter_init( tower       );
     786          96 :                              !fd_tower_iter_done( tower, iter );
     787          93 :                        iter = fd_tower_iter_next( tower, iter ) ) {
     788          93 :     fd_tower_t const * vote                         = fd_tower_iter_ele_const( tower, iter );
     789          93 :     tower_sync_serde.lockouts[i].offset             = vote->slot - prev;
     790          93 :     tower_sync_serde.lockouts[i].confirmation_count = (uchar)vote->conf;
     791          93 :     prev                                            = vote->slot;
     792          93 :     i++;
     793          93 :   }
     794             : 
     795           3 :   uchar * txn_out = vote_txn->payload;
     796           3 :   uchar * txn_meta_out = vote_txn->_;
     797             : 
     798           3 :   int same_addr = !memcmp( validator_identity, vote_authority, sizeof(fd_pubkey_t) );
     799           3 :   if( FD_LIKELY( same_addr ) ) {
     800             : 
     801             :     /* 0: validator identity
     802             :        1: vote account address
     803             :        2: vote program */
     804             : 
     805           3 :     fd_txn_accounts_t votes;
     806           3 :     votes.signature_cnt         = 1;
     807           3 :     votes.readonly_signed_cnt   = 0;
     808           3 :     votes.readonly_unsigned_cnt = 1;
     809           3 :     votes.acct_cnt              = 3;
     810           3 :     votes.signers_w             = validator_identity;
     811           3 :     votes.signers_r             = NULL;
     812           3 :     votes.non_signers_w         = vote_acc;
     813           3 :     votes.non_signers_r         = &fd_solana_vote_program_id;
     814           3 :     FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, votes.signature_cnt, &votes, recent_blockhash->uc ) );
     815             : 
     816           3 :   } else {
     817             : 
     818             :     /* 0: validator identity
     819             :        1: vote authority
     820             :        2: vote account address
     821             :        3: vote program */
     822             : 
     823           0 :     fd_txn_accounts_t votes;
     824           0 :     votes.signature_cnt         = 2;
     825           0 :     votes.readonly_signed_cnt   = 1;
     826           0 :     votes.readonly_unsigned_cnt = 1;
     827           0 :     votes.acct_cnt              = 4;
     828           0 :     votes.signers_w             = validator_identity;
     829           0 :     votes.signers_r             = vote_authority;
     830           0 :     votes.non_signers_w         = vote_acc;
     831           0 :     votes.non_signers_r         = &fd_solana_vote_program_id;
     832           0 :     FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, votes.signature_cnt, &votes, recent_blockhash->uc ) );
     833           0 :   }
     834             : 
     835             :   /* Add the vote instruction to the transaction. */
     836             : 
     837           3 :   uchar  vote_ix_buf[FD_TXN_MTU];
     838           3 :   ulong  vote_ix_sz = 0;
     839           3 :   FD_STORE( uint, vote_ix_buf, FD_VOTE_IX_KIND_TOWER_SYNC );
     840           3 :   FD_TEST( 0==fd_compact_tower_sync_serialize( &tower_sync_serde, vote_ix_buf + sizeof(uint), FD_TXN_MTU - sizeof(uint), &vote_ix_sz ) ); // cannot fail if fd_tower_cnt( tower ) <= FD_TOWER_VOTE_MAX
     841           3 :   vote_ix_sz += sizeof(uint);
     842           3 :   uchar program_id;
     843           3 :   uchar ix_accs[2];
     844           3 :   if( FD_LIKELY( same_addr ) ) {
     845           3 :     ix_accs[0] = 1; /* vote account address */
     846           3 :     ix_accs[1] = 0; /* vote authority */
     847           3 :     program_id = 2; /* vote program */
     848           3 :   } else {
     849           0 :     ix_accs[0] = 2; /* vote account address */
     850           0 :     ix_accs[1] = 1; /* vote authority */
     851           0 :     program_id = 3; /* vote program */
     852           0 :   }
     853           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 );
     854           3 : }
     855             : 
     856             : int
     857           0 : fd_tower_verify( fd_tower_t const * tower ) {
     858           0 :   if( FD_UNLIKELY( fd_tower_cnt( tower )>=FD_TOWER_VOTE_MAX ) ) {
     859           0 :     FD_LOG_WARNING(( "[%s] invariant violation: cnt %lu >= FD_TOWER_VOTE_MAX %lu", __func__, fd_tower_cnt( tower ), (ulong)FD_TOWER_VOTE_MAX ));
     860           0 :     return -1;
     861           0 :   }
     862             : 
     863           0 :   fd_tower_t const * prev = NULL;
     864           0 :   for( fd_tower_iter_t iter = fd_tower_iter_init( tower       );
     865           0 :                                    !fd_tower_iter_done( tower, iter );
     866           0 :                              iter = fd_tower_iter_next( tower, iter ) ) {
     867           0 :     fd_tower_t const * vote = fd_tower_iter_ele_const( tower, iter );
     868           0 :     if( FD_UNLIKELY( prev && ( vote->slot < prev->slot || vote->conf < prev->conf ) ) ) {
     869           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 ));
     870           0 :       return -1;
     871           0 :     }
     872           0 :     prev = vote;
     873           0 :   }
     874           0 :   return 0;
     875           0 : }
     876             : 
     877             : #include <stdio.h>
     878             : #include <string.h>
     879             : 
     880             : static void
     881           0 : print( fd_tower_t const * tower, ulong root, char * s, ulong len ) {
     882           0 :   ulong off = 0;
     883           0 :   int   n;
     884             : 
     885           0 :   n = snprintf( s + off, len - off, "[Tower]\n\n" );
     886           0 :   if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     887           0 :   off += (ulong)n;
     888             : 
     889           0 :   if( FD_UNLIKELY( fd_tower_empty( tower ) ) ) return;
     890             : 
     891           0 :   ulong max_slot = 0;
     892             : 
     893             :   /* Determine spacing. */
     894             : 
     895           0 :   for( fd_tower_iter_t iter = fd_tower_iter_init_rev( tower       );
     896           0 :                              !fd_tower_iter_done_rev( tower, iter );
     897           0 :                        iter = fd_tower_iter_prev    ( tower, iter ) ) {
     898           0 :     max_slot = fd_ulong_max( max_slot, fd_tower_iter_ele_const( tower, iter )->slot );
     899           0 :   }
     900             : 
     901             :   /* Calculate the number of digits in the maximum slot value. */
     902             : 
     903             : 
     904           0 :   int digit_cnt = (int)fd_ulong_base10_dig_cnt( max_slot );
     905             : 
     906             :   /* Print the column headers. */
     907             : 
     908           0 :   if( off < len ) {
     909           0 :     n = snprintf( s + off, len - off, "slot%*s | %s\n", digit_cnt - (int)strlen("slot"), "", "confirmation count" );
     910           0 :     if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     911           0 :     off += (ulong)n;
     912           0 :   }
     913             : 
     914             :   /* Print the divider line. */
     915             : 
     916           0 :   for( int i = 0; i < digit_cnt && off < len; i++ ) {
     917           0 :     s[off++] = '-';
     918           0 :   }
     919           0 :   if( off < len ) {
     920           0 :     n = snprintf( s + off, len - off, " | " );
     921           0 :     if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     922           0 :     off += (ulong)n;
     923           0 :   }
     924           0 :   for( ulong i = 0; i < strlen( "confirmation count" ) && off < len; i++ ) {
     925           0 :     s[off++] = '-';
     926           0 :   }
     927           0 :   if( off < len ) {
     928           0 :     s[off++] = '\n';
     929           0 :   }
     930             : 
     931             :   /* Print each vote as a table. */
     932             : 
     933           0 :   for( fd_tower_iter_t iter = fd_tower_iter_init_rev( tower       );
     934           0 :                              !fd_tower_iter_done_rev( tower, iter );
     935           0 :                        iter = fd_tower_iter_prev    ( tower, iter ) ) {
     936           0 :     fd_tower_t const * vote = fd_tower_iter_ele_const( tower, iter );
     937           0 :     if( off < len ) {
     938           0 :       n = snprintf( s + off, len - off, "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf );
     939           0 :       if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     940           0 :       off += (ulong)n;
     941           0 :     }
     942           0 :   }
     943             : 
     944           0 :   if( FD_UNLIKELY( root == ULONG_MAX ) ) {
     945           0 :     if( off < len ) {
     946           0 :       n = snprintf( s + off, len - off, "%*s | root\n", digit_cnt, "NULL" );
     947           0 :       if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     948           0 :       off += (ulong)n;
     949           0 :     }
     950           0 :   } else {
     951           0 :     if( off < len ) {
     952           0 :       n = snprintf( s + off, len - off, "%*lu | root\n", digit_cnt, root );
     953           0 :       if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     954           0 :       off += (ulong)n;
     955           0 :     }
     956           0 :   }
     957             : 
     958             :   /* Ensure null termination */
     959           0 :   if( off < len ) {
     960           0 :     s[off] = '\0';
     961           0 :   } else {
     962           0 :     s[len - 1] = '\0';
     963           0 :   }
     964           0 : }
     965             : 
     966             : void
     967           0 : fd_tower_print( fd_tower_t const * tower, ulong root ) {
     968           0 :   char buf[4096];
     969           0 :   print( tower, root, buf, sizeof(buf) );
     970           0 :   FD_LOG_NOTICE(( "\n\n%s", buf ));
     971           0 : }

Generated by: LCOV version 1.14