LCOV - code coverage report
Current view: top level - choreo/tower - fd_tower.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 165 648 25.5 %
Date: 2025-09-18 04:41:32 Functions: 8 26 30.8 %

          Line data    Source code
       1             : #include <unistd.h>
       2             : #include <fcntl.h>
       3             : #include <sys/stat.h>
       4             : 
       5             : #include "fd_tower.h"
       6             : #include "../../ballet/ed25519/fd_ed25519.h"
       7             : #include "../../flamenco/txn/fd_txn_generate.h"
       8             : #include "../../flamenco/runtime/fd_system_ids.h"
       9             : 
      10           0 : #define THRESHOLD_DEPTH         (8)
      11           0 : #define THRESHOLD_RATIO         (2.0 / 3.0)
      12             : #define SHALLOW_THRESHOLD_DEPTH (4)
      13             : #define SHALLOW_THRESHOLD_RATIO (0.38)
      14           0 : #define SWITCH_PCT              (0.38)
      15           0 : #define SERDE_KIND              (1)
      16           0 : #define SERDE_LAST_VOTE_KIND    (3)
      17             : 
      18             : void *
      19           3 : fd_tower_new( void * shmem ) {
      20           3 :   if( FD_UNLIKELY( !shmem ) ) {
      21           0 :     FD_LOG_WARNING(( "NULL mem" ));
      22           0 :     return NULL;
      23           0 :   }
      24             : 
      25           3 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_tower_align() ) ) ) {
      26           0 :     FD_LOG_WARNING(( "misaligned mem" ));
      27           0 :     return NULL;
      28           0 :   }
      29             : 
      30           3 :   return fd_tower_votes_new( shmem );
      31           3 : }
      32             : 
      33             : fd_tower_t *
      34           3 : fd_tower_join( void * shtower ) {
      35             : 
      36           3 :   if( FD_UNLIKELY( !shtower ) ) {
      37           0 :     FD_LOG_WARNING(( "NULL tower" ));
      38           0 :     return NULL;
      39           0 :   }
      40             : 
      41           3 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shtower, fd_tower_align() ) ) ) {
      42           0 :     FD_LOG_WARNING(( "misaligned tower" ));
      43           0 :     return NULL;
      44           0 :   }
      45             : 
      46           3 :   return fd_tower_votes_join( shtower );
      47           3 : }
      48             : 
      49             : void *
      50           0 : fd_tower_leave( fd_tower_t * tower ) {
      51             : 
      52           0 :   if( FD_UNLIKELY( !tower ) ) {
      53           0 :     FD_LOG_WARNING(( "NULL tower" ));
      54           0 :     return NULL;
      55           0 :   }
      56             : 
      57           0 :   return fd_tower_votes_leave( tower );
      58           0 : }
      59             : 
      60             : void *
      61           0 : fd_tower_delete( void * tower ) {
      62             : 
      63           0 :   if( FD_UNLIKELY( !tower ) ) {
      64           0 :     FD_LOG_WARNING(( "NULL tower" ));
      65           0 :     return NULL;
      66           0 :   }
      67             : 
      68           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)tower, fd_tower_align() ) ) ) {
      69           0 :     FD_LOG_WARNING(( "misaligned tower" ));
      70           0 :     return NULL;
      71           0 :   }
      72             : 
      73           0 :   return fd_tower_votes_delete( tower );
      74           0 : }
      75             : 
      76             : static inline ulong
      77           0 : expiration( fd_tower_vote_t const * vote ) {
      78           0 :   ulong lockout = 1UL << vote->conf;
      79           0 :   return vote->slot + lockout;
      80           0 : }
      81             : 
      82             : static inline ulong
      83           0 : simulate_vote( fd_tower_t const * tower, ulong slot ) {
      84           0 :   ulong cnt = fd_tower_votes_cnt( tower );
      85           0 :   while( cnt ) {
      86             : 
      87             :     /* Return early if we can't pop the top tower vote, even if votes
      88             :        below it are expired. */
      89             : 
      90           0 :     if( FD_LIKELY( expiration( fd_tower_votes_peek_index_const( tower, cnt - 1 ) ) >= slot ) ) {
      91           0 :       break;
      92           0 :     }
      93           0 :     cnt--;
      94           0 :   }
      95           0 :   return cnt;
      96           0 : }
      97             : 
      98             : int
      99             : fd_tower_lockout_check( fd_tower_t const * tower,
     100             :                         fd_ghost_t const * ghost,
     101             :                         ulong              slot,
     102           0 :                         fd_hash_t const  * block_id ) {
     103           0 : # if FD_TOWER_PARANOID
     104           0 :   FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
     105           0 : # endif
     106             : 
     107             :   /* Simulate a vote to pop off all the votes that have been expired at
     108             :      the top of the tower. */
     109             : 
     110           0 :   ulong cnt = simulate_vote( tower, slot );
     111             : 
     112             :   /* By definition, all votes in the tower must be for the same fork, so
     113             :      check if the previous vote (ie. the last vote in the tower) is on
     114             :      the same fork as the fork we want to vote for. We do this using
     115             :      ghost by checking if the previous vote slot is an ancestor of the
     116             :      `slot`. If the previous vote slot is too old (ie. older than
     117             :      ghost->root), then we don't have ancestry information anymore and
     118             :      we just assume it is on the same fork.
     119             : 
     120             :      FIXME discuss if it is safe to assume that? */
     121             : 
     122           0 :   fd_tower_vote_t const * vote = fd_tower_votes_peek_index_const( tower, cnt - 1 );
     123           0 :   fd_ghost_ele_t const *  root = fd_ghost_root_const( ghost );
     124             : 
     125           0 :   int lockout_check = (slot > vote->slot) && (vote->slot < root->slot || fd_ghost_is_ancestor( ghost, fd_ghost_hash( ghost, vote->slot ), block_id ));
     126             : # if LOGGING
     127             :   FD_LOG_NOTICE(( "[%s] %d. top: (slot: %lu, conf: %lu). switch: %lu.", __func__, lockout_check, vote->slot, vote->conf, slot ));
     128             : # endif
     129           0 :   return lockout_check;
     130           0 : }
     131             : 
     132             : int
     133             : fd_tower_switch_check( fd_tower_t const * tower,
     134             :                        fd_epoch_t const * epoch,
     135             :                        fd_ghost_t const * ghost,
     136             :                        ulong              slot,
     137           0 :                        fd_hash_t const *  block_id ) {
     138           0 :   #if FD_TOWER_PARANOID
     139           0 :   FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
     140           0 :   #endif
     141             : 
     142           0 :   fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
     143           0 :   fd_ghost_ele_t const *  root = fd_ghost_root_const( ghost );
     144             : 
     145           0 :   if( FD_UNLIKELY( vote->slot < root->slot ) ) {
     146             : 
     147             :     /* It is possible our last vote slot precedes our ghost root. This
     148             :        can happen, for example, when we restart from a snapshot and set
     149             :        the ghost root to the snapshot slot (we won't have an ancestry
     150             :        before the snapshot slot.)
     151             : 
     152             :        If this is the case, we assume it's ok to switch. */
     153             : 
     154           0 :     return 1;
     155           0 :   }
     156             : 
     157             :   /* fd_tower_switch_check is only called if latest_vote->slot and
     158             :      fork->slot are on different forks (determined by is_descendant), so
     159             :      they must not fall on the same ancestry path back to the gca.
     160             : 
     161             :      INVALID:
     162             : 
     163             :        0
     164             :         \
     165             :          1    <- a
     166             :           \
     167             :            2  <- b
     168             : 
     169             :      VALID:
     170             : 
     171             :        0
     172             :       / \
     173             :      1   2
     174             :      ^   ^
     175             :      a   b
     176             : 
     177             :   */
     178             : 
     179           0 : # if FD_TOWER_PARANOID
     180           0 :   FD_TEST( !fd_ghost_is_ancestor( ghost, fd_ghost_hash( ghost, vote->slot ), block_id ) );
     181           0 : # endif
     182           0 :   fd_hash_t     const * vote_block_id = fd_ghost_hash( ghost, vote->slot );
     183           0 :   fd_ghost_hash_map_t const * maph    = fd_ghost_hash_map_const( ghost );
     184           0 :   fd_ghost_ele_t      const * pool    = fd_ghost_pool_const( ghost );
     185           0 :   fd_ghost_ele_t      const * gca     = fd_ghost_gca( ghost, vote_block_id, block_id );
     186           0 :   ulong                       gca_idx = fd_ghost_hash_map_idx_query_const( maph, &gca->key, ULONG_MAX, pool );
     187             : 
     188             :   /* gca_child is our latest_vote slot's ancestor that is also a direct
     189             :      child of GCA.  So we do not count it towards the stake of the
     190             :      different forks. */
     191             : 
     192           0 :   fd_ghost_ele_t const * gca_child = fd_ghost_query_const( ghost, vote_block_id );
     193           0 :   while( FD_LIKELY( gca_child->parent != gca_idx ) ) {
     194           0 :     gca_child = fd_ghost_pool_ele_const( pool, gca_child->parent );
     195           0 :   }
     196             : 
     197           0 :   ulong switch_stake = 0;
     198           0 :   fd_ghost_ele_t const * child = fd_ghost_child_const( ghost, gca );
     199           0 :   while( FD_LIKELY( child ) ) {
     200           0 :     if( FD_LIKELY( child != gca_child ) ) {
     201           0 :       switch_stake += child->weight;
     202           0 :     }
     203           0 :     child = fd_ghost_pool_ele_const( pool, child->sibling );
     204           0 :   }
     205             : 
     206           0 :   double switch_pct = (double)switch_stake / (double)epoch->total_stake;
     207           0 :   FD_LOG_DEBUG(( "[%s] ok? %d. top: %lu. switch: %lu. switch stake: %.0lf%%.", __func__, switch_pct > SWITCH_PCT, fd_tower_votes_peek_tail_const( tower )->slot, slot, switch_pct * 100.0 ));
     208           0 :   return switch_pct > SWITCH_PCT;
     209           0 : }
     210             : 
     211             : int
     212             : fd_tower_threshold_check( fd_tower_t const *   tower,
     213             :                           fd_epoch_t *         epoch,
     214             :                           fd_pubkey_t *        vote_keys,
     215             :                           fd_tower_t * const * vote_towers,
     216             :                           ulong                vote_cnt,
     217           0 :                           ulong                slot ) {
     218             : 
     219             :   /* First, simulate a vote, popping off everything that would be
     220             :      expired by voting for the current slot. */
     221             : 
     222           0 :   ulong cnt = simulate_vote( tower, slot );
     223             : 
     224             :   /* Return early if our tower is not at least THRESHOLD_DEPTH deep
     225             :      after simulating. */
     226             : 
     227           0 :   if( FD_UNLIKELY( cnt < THRESHOLD_DEPTH ) ) return 1;
     228             : 
     229             :   /* Get the vote slot from THRESHOLD_DEPTH back. Note THRESHOLD_DEPTH
     230             :      is the 8th index back _including_ the simulated vote at index 0,
     231             :      which is not accounted for by `cnt`, so subtracting THRESHOLD_DEPTH
     232             :      will conveniently index the threshold vote. */
     233             : 
     234           0 :   ulong threshold_slot = fd_tower_votes_peek_index_const( tower, cnt - THRESHOLD_DEPTH )->slot;
     235             : 
     236             :   /* Track the amount of stake that has vote slot >= threshold_slot. */
     237             : 
     238           0 :   ulong threshold_stake = 0;
     239             : 
     240             :   /* Iterate all the vote accounts. */
     241             : 
     242           0 :   for (ulong i = 0; i < vote_cnt; i++ ) {
     243           0 :     fd_tower_t const * vote_tower = vote_towers[i];
     244             : 
     245             :     /* If this voter has not voted, continue. */
     246             : 
     247           0 :     if( FD_UNLIKELY( fd_tower_votes_empty( vote_tower ) ) ) continue;
     248             : 
     249           0 :     ulong cnt = simulate_vote( vote_tower, slot );
     250             : 
     251             :     /* Continue if their tower is empty after simulating. */
     252             : 
     253           0 :     if( FD_UNLIKELY( !cnt ) ) continue;
     254             : 
     255             :     /* Get their latest vote. */
     256             : 
     257           0 :     fd_tower_vote_t const * vote = fd_tower_votes_peek_index_const( vote_tower, cnt - 1 );
     258             : 
     259             :     /* Count their stake towards the threshold check if their latest
     260             :         vote slot >= our threshold slot.
     261             : 
     262             :         Because we are iterating vote accounts on the same fork that we
     263             :         we want to vote for, we know these slots must all occur along
     264             :         the same fork ancestry.
     265             : 
     266             :         Therefore, if their latest vote slot >= our threshold slot, we
     267             :         know that vote must be for the threshold slot itself or one of
     268             :         threshold slot's descendants. */
     269             : 
     270           0 :     if( FD_LIKELY( vote->slot >= threshold_slot ) ) {
     271           0 :       fd_voter_t * epoch_voters = fd_epoch_voters( epoch );
     272           0 :       fd_voter_t * voter        = fd_epoch_voters_query( epoch_voters, vote_keys[i], NULL );
     273           0 :       if( FD_UNLIKELY( !voter ) ) {
     274             :         /* This means that the cached list of epoch voters is not in sync with the list passed
     275             :            through from replay. This likely means that we have crossed an epoch boundary and the
     276             :            epoch_voter list has not been updated.
     277             : 
     278             :            TODO: update the set of account in epoch_voter's to match the list received from replay,
     279             :                  so that epoch_voters is correct across epoch boundaries. */
     280           0 :         FD_LOG_CRIT(( "[%s] voter %s was not in epoch voters", __func__,
     281           0 :           FD_BASE58_ENC_32_ALLOCA(&vote_keys[i]) ));
     282           0 :         continue;
     283           0 :       }
     284           0 :       threshold_stake += voter->stake;
     285           0 :     }
     286           0 :   }
     287             : 
     288           0 :   double threshold_pct = (double)threshold_stake / (double)epoch->total_stake;
     289             : # if LOGGING
     290             :   FD_LOG_NOTICE(( "[%s] ok? %d. top: %lu. threshold: %lu. stake: %.0lf%%.", __func__, threshold_pct > THRESHOLD_RATIO, fd_tower_votes_peek_tail_const( tower )->slot, threshold_slot, threshold_pct * 100.0 ));
     291             : # endif
     292           0 :   return threshold_pct > THRESHOLD_RATIO;
     293           0 : }
     294             : 
     295             : ulong
     296             : fd_tower_reset_slot( fd_tower_t const * tower,
     297             :                      fd_epoch_t const * epoch,
     298           0 :                      fd_ghost_t const * ghost ) {
     299             : 
     300           0 :   fd_tower_vote_t const * last = fd_tower_votes_peek_tail_const( tower );
     301           0 :   fd_ghost_ele_t const *  vote = last ? fd_ghost_query_const( ghost, fd_ghost_hash( ghost, last->slot ) ) : NULL;
     302           0 :   fd_ghost_ele_t const *  root = fd_ghost_root_const( ghost );
     303           0 :   fd_ghost_ele_t const *  head = fd_ghost_head( ghost, root );
     304             : 
     305           0 : # if FD_TOWER_PARANOID
     306           0 :   if( FD_UNLIKELY( !vote ) ) FD_LOG_CRIT(( "[%s] missing vote %lu", __func__, last->slot  ));
     307           0 :   if( FD_UNLIKELY( !root ) ) FD_LOG_CRIT(( "[%s] missing root",     __func__              ));
     308           0 :   if( FD_UNLIKELY( !head ) ) FD_LOG_CRIT(( "[%s] missing head",     __func__              ));
     309           0 : # endif
     310             : 
     311             :   /* Case 0: reset to the ghost head (ie. heaviest leaf slot of any
     312             :      fork) if any of the following is true:
     313             : 
     314             :      a. haven't voted
     315             :      b. last vote slot < ghost root slot
     316             :      c. ghost root is not an ancestor of last vote
     317             : 
     318             :      TODO can c. happen in non-exceptional conditions? error out? */
     319             : 
     320           0 :   if( FD_UNLIKELY( !vote || vote->slot < root->slot || !fd_ghost_is_ancestor( ghost, &root->key, &vote->key ) ) )
     321           0 :     return head->slot;
     322             : 
     323             :   /* Case 1: last vote on same fork as heaviest leaf (ie. last vote slot
     324             :      is an ancestor of heaviest leaf ). This is the common case. */
     325             : 
     326           0 :   else if( FD_LIKELY( fd_ghost_is_ancestor( ghost, &vote->key, &head->key ) ) )
     327           0 :     return head->slot;
     328             : 
     329             :   /* Case 2: last vote is on different fork from heaviest leaf (ie. last
     330             :      vote slot is _not_ an ancestor of heaviest leaf), but we have a
     331             :      valid switch proof for the heaviest leaf. */
     332             : 
     333           0 :   else if( FD_LIKELY( fd_tower_switch_check( tower, epoch, ghost, head->slot, &head->key ) ) )
     334           0 :     return head->slot;
     335             : 
     336             :   /* Case 3: same as case 2 except we don't have a valid switch proof,
     337             :      but we detect last vote is now on an "invalid" fork (ie. any
     338             :      ancestor of our last vote slot equivocates AND has not reached 52%
     339             :      of stake). If we do find such an ancestor, we reset to the heaviest
     340             :      leaf anyways, despite it being on a different fork and not having a
     341             :      valid switch proof. */
     342             : 
     343           0 :   else if( FD_LIKELY( fd_ghost_invalid( ghost, vote ) ) )
     344           0 :     return head->slot;
     345             : 
     346             :   /* Case 4: same as case 3 except last vote's fork is not invalid. In
     347             :      this case we reset to the heaviest leaf starting from the subtree
     348             :      rooted at our last vote slot, instead of the overall heaviest leaf.
     349             :      This is done to ensure votes propagate (see top-level documentation
     350             :      in fd_tower.h for details) */
     351             : 
     352           0 :   else
     353           0 :     return fd_ghost_head( ghost, vote )->slot;
     354           0 : }
     355             : 
     356             : ulong
     357             : fd_tower_vote_slot( fd_tower_t const *   tower,
     358             :                     fd_epoch_t *         epoch,
     359             :                     fd_pubkey_t *        vote_keys,
     360             :                     fd_tower_t * const * vote_towers,
     361             :                     ulong                vote_cnt,
     362           0 :                     fd_ghost_t const *   ghost ) {
     363             : 
     364           0 :   fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
     365           0 :   fd_ghost_ele_t const *  root = fd_ghost_root_const( ghost );
     366           0 :   fd_ghost_ele_t const *  head = fd_ghost_head( ghost, root );
     367             : 
     368             :   /* Vote for the ghost head if any of the following is true:
     369             : 
     370             :      1. haven't voted
     371             :      2. last vote < ghost root
     372             :      3. ghost root is not an ancestory of last vote
     373             : 
     374             :      FIXME need to ensure lockout safety for case 2 and 3 */
     375             : 
     376           0 :   if( FD_UNLIKELY( !vote || vote->slot < root->slot ) ) {
     377           0 :     return head->slot;
     378           0 :   }
     379           0 :   fd_hash_t const * vote_block_id = fd_ghost_hash( ghost, vote->slot );
     380           0 :   if( FD_UNLIKELY( !fd_ghost_is_ancestor( ghost, &root->key, vote_block_id ) ) ) {
     381           0 :     return head->slot;
     382           0 :   }
     383             : 
     384             :   /* Optimize for when there is just one fork or that we already
     385             :      previously voted for the best fork. */
     386             : 
     387           0 :   if( FD_LIKELY( fd_ghost_is_ancestor( ghost, vote_block_id, &head->key ) ) ) {
     388             : 
     389             :     /* The ghost head is on the same fork as our last vote slot, so we
     390             :        can vote fork it as long as we pass the threshold check. */
     391             : 
     392           0 :     if( FD_LIKELY( head->slot > vote->slot && fd_tower_threshold_check( tower, epoch, vote_keys, vote_towers, vote_cnt, head->slot ) ) ) {
     393           0 :       FD_LOG_DEBUG(( "[%s] success (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
     394           0 :       return head->slot;
     395           0 :     }
     396           0 :     FD_LOG_DEBUG(( "[%s] failure (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
     397           0 :     return FD_SLOT_NULL; /* can't vote. need to wait for threshold check. */
     398           0 :   }
     399             : 
     400             :   /* The ghost head is on a different fork from our last vote slot, so
     401             :       try to switch if we pass lockout and switch threshold. */
     402             : 
     403           0 :   if( FD_UNLIKELY( fd_tower_lockout_check( tower, ghost, head->slot, &head->key ) &&
     404           0 :                    fd_tower_switch_check( tower, epoch, ghost, head->slot, &head->key ) ) ) {
     405           0 :     FD_LOG_DEBUG(( "[%s] success (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
     406           0 :     return head->slot;
     407           0 :   }
     408           0 :   FD_LOG_DEBUG(( "[%s] failure (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
     409           0 :   return FD_SLOT_NULL;
     410           0 : }
     411             : 
     412             : ulong
     413           0 : fd_tower_vote( fd_tower_t * tower, ulong slot ) {
     414           0 :   FD_LOG_DEBUG(( "[%s] voting for slot %lu", __func__, slot ));
     415             : 
     416           0 :   #if FD_TOWER_PARANOID
     417           0 :   fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
     418           0 :   if( FD_UNLIKELY( vote && slot < vote->slot ) ) FD_LOG_ERR(( "[%s] slot %lu < vote->slot %lu", __func__, slot, vote->slot )); /* caller error*/
     419           0 :   #endif
     420             : 
     421             :   /* Use simulate_vote to determine how many expired votes to pop. */
     422             : 
     423           0 :   ulong cnt = simulate_vote( tower, slot );
     424             : 
     425             :   /* Pop everything that got expired. */
     426             : 
     427           0 :   while( FD_LIKELY( fd_tower_votes_cnt( tower ) > cnt ) ) {
     428           0 :     fd_tower_votes_pop_tail( tower );
     429           0 :   }
     430             : 
     431             :   /* If the tower is still full after expiring, then pop and return the
     432             :      bottom vote slot as the new root because this vote has incremented
     433             :      it to max lockout.  Otherwise this is a no-op and there is no new
     434             :      root (FD_SLOT_NULL). */
     435             : 
     436           0 :   ulong root = FD_SLOT_NULL;
     437           0 :   if( FD_LIKELY( fd_tower_votes_full( tower ) ) ) { /* optimize for full tower */
     438           0 :     root = fd_tower_votes_pop_head( tower ).slot;
     439           0 :   }
     440             : 
     441             :   /* Increment confirmations (double lockouts) for consecutive
     442             :      confirmations in prior votes. */
     443             : 
     444           0 :   ulong prev_conf = 0;
     445           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower       );
     446           0 :                                    !fd_tower_votes_iter_done_rev( tower, iter );
     447           0 :                              iter = fd_tower_votes_iter_prev    ( tower, iter ) ) {
     448           0 :     fd_tower_vote_t * vote = fd_tower_votes_iter_ele( tower, iter );
     449           0 :     if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) break;
     450           0 :     vote->conf++;
     451           0 :   }
     452             : 
     453             :   /* Add the new vote to the tower. */
     454             : 
     455           0 :   fd_tower_votes_push_tail( tower, (fd_tower_vote_t){ .slot = slot, .conf = 1 } );
     456             : 
     457             :   /* Return the new root (FD_SLOT_NULL if there is none). */
     458             : 
     459           0 :   return root;
     460           0 : }
     461             : 
     462             : ulong
     463           0 : fd_tower_simulate_vote( fd_tower_t const * tower, ulong slot ) {
     464           0 : # if FD_TOWER_PARANOID
     465           0 :   FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
     466           0 : # endif
     467             : 
     468           0 :   return simulate_vote( tower, slot );
     469           0 : }
     470             : 
     471             : static const uchar option_some = 1; /* this is a hack to lift the lifetime of a uchar outside fd_tower_sync_serde */
     472             : 
     473             : fd_tower_sync_serde_t *
     474           0 : fd_tower_to_tower_sync( fd_tower_t const * tower, ulong root, fd_hash_t * bank_hash, fd_hash_t * block_id, long ts, fd_tower_sync_serde_t * ser ) {
     475           0 :   ser->root         = &root;
     476           0 :   ser->lockouts_cnt = (ushort)fd_tower_votes_cnt( tower );
     477           0 :   ushort i          = 0;
     478           0 :   ulong  prev       = root;
     479           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
     480           0 :                                    !fd_tower_votes_iter_done( tower, iter );
     481           0 :                              iter = fd_tower_votes_iter_next( tower, iter ) ) {
     482           0 :     fd_tower_vote_t const * vote        = fd_tower_votes_iter_ele_const( tower, iter );
     483           0 :     ser->lockouts[i].offset             = vote->slot - prev;
     484           0 :     ser->lockouts[i].confirmation_count = (uchar const *)fd_type_pun_const( &vote->conf );
     485           0 :     i++;
     486           0 :   }
     487           0 :   ser->hash              = bank_hash;
     488           0 :   ser->timestamp_option  = &option_some;
     489           0 :   ser->timestamp         = &ts;
     490           0 :   ser->block_id          = block_id;
     491           0 :   return ser;
     492           0 : }
     493             : 
     494             : int
     495             : fd_tower_checkpt( fd_tower_t const *      tower,
     496             :                   ulong                   root,
     497             :                   fd_tower_sync_serde_t * last_vote,
     498             :                   uchar const             pubkey[static 32],
     499             :                   fd_tower_sign_fn *      sign_fn,
     500             :                   int                     fd,
     501             :                   uchar *                 buf,
     502           0 :                   ulong                   buf_max ) {
     503             : 
     504             :   /* TODO check no invalid ptrs */
     505             : 
     506           0 :   fd_tower_serde_t ser = { 0 };
     507             : 
     508           0 :   uint   kind            = SERDE_KIND;
     509           0 :   ulong  threshold_depth = THRESHOLD_DEPTH;
     510           0 :   double threshold_size  = THRESHOLD_RATIO;
     511             : 
     512           0 :   ser.kind            = &kind;
     513           0 :   ser.threshold_depth = &threshold_depth;
     514           0 :   ser.threshold_size  = &threshold_size;
     515             : 
     516           0 :   fd_voter_v2_serde_t * voter_v2_ser = &ser.vote_state;
     517             : 
     518             :   /* Agave defaults all fields except the actual tower votes and root
     519             :      https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/tower_vote_state.rs#L118-L128 */
     520             : 
     521           0 :   fd_pubkey_t pubkey_null            = { 0 };
     522           0 :   voter_v2_ser->node_pubkey           = &pubkey_null;
     523           0 :   voter_v2_ser->authorized_withdrawer = &pubkey_null;
     524           0 :   uchar commission                   = 0;
     525           0 :   voter_v2_ser->commission            = &commission;
     526             : 
     527           0 :   ulong votes_cnt        = fd_tower_votes_cnt( tower );
     528           0 :   voter_v2_ser->votes_cnt = &votes_cnt;
     529             : 
     530           0 :   ulong i = 0;
     531           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower       );
     532           0 :                                    !fd_tower_votes_iter_done( tower, iter );
     533           0 :                              iter = fd_tower_votes_iter_next( tower, iter ) ) {
     534           0 :     fd_tower_vote_t const * vote              = fd_tower_votes_iter_ele_const( tower, iter );
     535           0 :     voter_v2_ser->votes[i].slot               = &vote->slot;
     536           0 :     voter_v2_ser->votes[i].confirmation_count = (uint const *)fd_type_pun_const( &vote->conf );
     537           0 :     i++;
     538           0 :   }
     539             : 
     540           0 :   uchar root_slot_option        = root == ULONG_MAX;
     541           0 :   voter_v2_ser->root_slot_option = &root_slot_option;
     542           0 :   voter_v2_ser->root_slot        = root_slot_option ? NULL : &root;
     543             : 
     544           0 :   ulong authorized_voters_cnt        = 0;
     545           0 :   voter_v2_ser->authorized_voters_cnt = &authorized_voters_cnt;
     546             : 
     547           0 :   ulong start_epoch = 0;
     548           0 :   ulong end_epoch   = 0;
     549           0 :   for( ulong i = 0; i < 32; i++ ) {
     550           0 :     voter_v2_ser->prior_voters.buf[i].pubkey      = &pubkey_null;
     551           0 :     voter_v2_ser->prior_voters.buf[i].start_epoch = &start_epoch;
     552           0 :     voter_v2_ser->prior_voters.buf[i].end_epoch   = &end_epoch;
     553           0 :   }
     554           0 :   ulong idx                           = 31;
     555           0 :   voter_v2_ser->prior_voters.idx      = &idx;
     556           0 :   uchar is_empty                      = 0;
     557           0 :   voter_v2_ser->prior_voters.is_empty = &is_empty;
     558             : 
     559           0 :   ulong epoch_credits_cnt         = 0;
     560           0 :   voter_v2_ser->epoch_credits_cnt = &epoch_credits_cnt;
     561             : 
     562           0 :   ulong slot                             = 0;
     563           0 :   long  timestamp                        = 0;
     564           0 :   voter_v2_ser->last_timestamp.slot      = &slot;
     565           0 :   voter_v2_ser->last_timestamp.timestamp = &timestamp;
     566             : 
     567             :   /* Copy the last vote (reused from the actual ) into the Tower */
     568             : 
     569           0 :   uint last_vote_kind = SERDE_LAST_VOTE_KIND;
     570           0 :   ser.last_vote_kind  = &last_vote_kind;
     571           0 :   ser.last_vote       = *last_vote;
     572             : 
     573           0 :   ulong last_timestamp_slot      = fd_tower_votes_peek_tail_const( tower )->slot;
     574           0 :   long  last_timestamp_timestamp = fd_log_wallclock() / (long)1e9;
     575           0 :   ser.last_timestamp.slot        = &last_timestamp_slot;
     576           0 :   ser.last_timestamp.timestamp   = &last_timestamp_timestamp;
     577             : 
     578           0 :   int err;
     579             : 
     580           0 :   ulong buf_sz; err = fd_tower_serialize( &ser, buf, buf_max, &buf_sz );
     581           0 :   if( FD_UNLIKELY( err ) ) { FD_LOG_WARNING(( "fd_tower_serialize failed" )); return -1; }
     582             : 
     583           0 :   ulong   off    = sizeof(uint) /* kind */ + FD_ED25519_SIG_SZ /* signature */ + sizeof(ulong) /* data_sz */;
     584           0 :   uchar * sig    = buf + sizeof(uint);
     585           0 :   uchar * msg    = buf + off;
     586           0 :   ulong   msg_sz = buf_sz - off;
     587             : 
     588           0 :   sign_fn( pubkey, sig, msg, msg_sz );
     589             : 
     590           0 :   ser.signature = (fd_ed25519_sig_t const *)fd_type_pun_const( &buf );
     591           0 :   ser.data_sz   = &msg_sz;
     592             : 
     593           0 :   ulong wsz; err = fd_io_write( fd, buf, buf_sz, buf_sz, &wsz );
     594           0 :   if( FD_UNLIKELY( err ) ) { FD_LOG_WARNING(( "fd_io_write failed: %s", strerror( err ) )); return -1; }
     595             : 
     596           0 :   fsync( fd );
     597             : 
     598           0 :   return 0;
     599           0 : }
     600             : 
     601             : int
     602             : fd_tower_restore( fd_tower_t * tower,
     603             :                   ulong *      root,
     604             :                   long *       ts,
     605             :                   uchar const  pubkey[static 32],
     606             :                   int          fd,
     607             :                   uchar *      buf,
     608             :                   ulong        buf_max,
     609           0 :                   ulong *      buf_sz ) {
     610           0 :   int err = fd_io_sz( fd, buf_sz );
     611           0 :   if( FD_UNLIKELY( err             ) ) { FD_LOG_WARNING(( "%s: %s", __func__, fd_io_strerror( err )                  )); return -1; }
     612           0 :   if( FD_UNLIKELY( buf_max<*buf_sz ) ) { FD_LOG_WARNING(( "%s: buf_max %lu < buf_sz %lu", __func__, buf_max, *buf_sz )); return -1; }
     613             : 
     614           0 :   ulong rsz; err = fd_io_read( fd, buf, *buf_sz, *buf_sz, &rsz );
     615           0 :   if( FD_UNLIKELY( err<0        ) ) { FD_LOG_WARNING(( "%s: unexpected EOF", __func__                             )); return -1; }
     616           0 :   if( FD_UNLIKELY( *buf_sz!=rsz ) ) { FD_LOG_WARNING(( "%s: read %lu bytes, expected %lu", __func__, rsz, *buf_sz )); return -1; }
     617           0 :   if( FD_UNLIKELY( err>0        ) ) { FD_LOG_WARNING(( "%s: %s", __func__, fd_io_strerror( err )                  )); return -1; }
     618             : 
     619           0 :   fd_tower_serde_t de = { 0 };
     620           0 :   fd_tower_deserialize( buf, *buf_sz, &de );
     621             : 
     622           0 :   uchar *       msg    = (uchar *)de.node_pubkey; /* signed data region begins at this field */
     623           0 :   ulong         msg_sz = *de.data_sz;
     624           0 :   uchar const * sig    = *de.signature;
     625           0 :   fd_sha512_t sha[1];
     626           0 :   err = fd_ed25519_verify( msg, msg_sz, sig, pubkey, sha );
     627           0 :   if( FD_UNLIKELY( err!=FD_ED25519_SUCCESS                      ) ) { FD_LOG_WARNING(( "serialized tower failed sigverify: %s", fd_ed25519_strerror( err )             )); return -1; }
     628           0 :   if( FD_UNLIKELY( 0!=memcmp( de.node_pubkey->uc, pubkey, 32 ) ) ) { FD_LOG_WARNING(( "node_pubkey does not match pubkey"                                             )); return -1; }
     629           0 :   if( FD_UNLIKELY( *de.kind!=SERDE_KIND                        ) ) { FD_LOG_WARNING(( "serialized tower generated by too old agave version (required >= 2.3.7)"       )); return -1; }
     630           0 :   if( FD_UNLIKELY( *de.threshold_depth!=THRESHOLD_DEPTH        ) ) { FD_LOG_WARNING(( "threshold_depth does not match THRESHOLD_DEPTH"                                )); return -1; }
     631           0 :   if( FD_UNLIKELY( *de.threshold_size !=THRESHOLD_RATIO        ) ) { FD_LOG_WARNING(( "threshold_size does not match THRESHOLD_RATIO"                                 )); return -1; }
     632           0 :   if( FD_UNLIKELY( *de.vote_state.votes_cnt > 31               ) ) { FD_LOG_WARNING(( "invalid votes_cnt %lu > 31", *de.vote_state.votes_cnt                         )); return -1; }
     633           0 :   if( FD_UNLIKELY( *de.vote_state.authorized_voters_cnt > 31   ) ) { FD_LOG_WARNING(( "invalid authorized_voters_cnt %lu > 31", *de.vote_state.authorized_voters_cnt )); return -1; }
     634           0 :   if( FD_UNLIKELY(  de.last_vote.lockouts_cnt > 31             ) ) { FD_LOG_WARNING(( "invalid lockouts_cnt %u > 31", de.last_vote.lockouts_cnt                      )); return -1; }
     635             : 
     636           0 :   for( ulong i = 0; i < *de.vote_state.votes_cnt; i++ ) {
     637           0 :     fd_tower_votes_push_tail( tower, (fd_tower_vote_t){ .slot = *de.vote_state.votes[i].slot, .conf = *de.vote_state.votes[i].confirmation_count } );
     638           0 :   }
     639           0 :   *root = *de.vote_state.root_slot_option ? *de.vote_state.root_slot : ULONG_MAX;
     640           0 :   *ts   = *de.last_timestamp.timestamp;
     641             : 
     642           0 :   return 0;
     643           0 : }
     644             : 
     645             : static ulong
     646           3 : ser_short_vec_cnt( uchar * dst, ushort src ) {
     647           3 :   if     ( FD_LIKELY( src < 0x80UL   ) ) { *dst   = (uchar)  src;                                                                                          return 1; }
     648           0 :   else if( FD_LIKELY( src < 0x4000UL ) ) { *dst++ = (uchar)((src&0x7FUL)|0x80UL); *dst++ = (uchar)(  src>>7);                                              return 2; }
     649           0 :   else                                   { *dst++ = (uchar)((src&0x7FUL)|0x80UL); *dst++ = (uchar)(((src>>7)&0x7FUL)|0x80UL); *dst++ = (uchar)(src>>14UL); return 3; }
     650           3 : }
     651             : 
     652             : static ulong
     653          93 : ser_varint( uchar * dst, ulong src ) {
     654          93 :   ulong off = 0;
     655          93 :   while( FD_LIKELY( 1 ) ) {
     656          93 :     if( FD_LIKELY( src < 0x80UL ) ) {
     657          93 :       *(dst) = (uchar)src;
     658          93 :       off   += 1;
     659          93 :       return off;
     660          93 :     }
     661           0 :     *(dst+off) = (uchar)((src&0x7FUL)|0x80UL);
     662           0 :     off       += 1;
     663           0 :     src      >>= 7;
     664           0 :   }
     665          93 : }
     666             : 
     667             : int
     668             : fd_tower_serialize( fd_tower_serde_t * ser,
     669             :                     uchar *            buf,
     670             :                     ulong              buf_max,
     671           3 :                     ulong *            buf_sz ) {
     672             : 
     673           3 :   if( FD_UNLIKELY( *ser->threshold_depth!=THRESHOLD_DEPTH      ) ) { FD_LOG_WARNING(( "threshold_depth does not match THRESHOLD_DEPTH"                                 )); return -1; }
     674           3 :   if( FD_UNLIKELY( *ser->threshold_size !=THRESHOLD_RATIO      ) ) { FD_LOG_WARNING(( "threshold_size does not match THRESHOLD_RATIO"                                  )); return -1; }
     675           3 :   if( FD_UNLIKELY( *ser->vote_state.votes_cnt > 31             ) ) { FD_LOG_WARNING(( "invalid votes_cnt %lu > 31", *ser->vote_state.votes_cnt                         )); return -1; }
     676           3 :   if( FD_UNLIKELY( *ser->vote_state.authorized_voters_cnt > 31 ) ) { FD_LOG_WARNING(( "invalid authorized_voters_cnt %lu > 31", *ser->vote_state.authorized_voters_cnt )); return -1; }
     677           3 :   if( FD_UNLIKELY(  ser->last_vote.lockouts_cnt > 31           ) ) { FD_LOG_WARNING(( "invalid lockouts_cnt %u > 31", ser->last_vote.lockouts_cnt                      )); return -1; }
     678             : 
     679         639 :   #define SER( T, name ) do {                                                                 \
     680         639 :       if( FD_UNLIKELY( off+sizeof(T)>buf_max ) ) {                                            \
     681           0 :         FD_LOG_WARNING(( "ser %s: overflow (off %lu > buf_max: %lu)", #name, off, buf_max )); \
     682           0 :         return -1;                                                                            \
     683           0 :       }                                                                                       \
     684         639 :       if( FD_LIKELY( ser->name ) ) {                                                          \
     685         639 :         FD_STORE( T, buf+off, *ser->name );                                                   \
     686         639 :         ser->name = (T const *)fd_type_pun_const( buf+off );                                  \
     687         639 :       }                                                                                       \
     688         639 :       off += sizeof(T);                                                                       \
     689         639 :   } while(0)
     690             : 
     691           6 :   #define OFF( T, name ) do {                                                                 \
     692           6 :       if( FD_UNLIKELY( off+sizeof(T)>buf_max ) ) {                                            \
     693           0 :         FD_LOG_WARNING(( "ser %s: overflow (off %lu > buf_max: %lu)", #name, off, buf_max )); \
     694           0 :         return -1;                                                                            \
     695           0 :       }                                                                                       \
     696           6 :       ser->name = (T const *)fd_type_pun_const( buf+off );                                    \
     697           6 :       off += sizeof(T);                                                                       \
     698           6 :   } while(0)
     699             : 
     700           3 :   ulong off = 0;
     701             : 
     702             :   /* SavedTower::Current */
     703             : 
     704           3 :   SER( uint,             kind                                       );
     705           3 :   OFF( fd_ed25519_sig_t, signature                                  );
     706           3 :   OFF( ulong,            data_sz                                    );
     707           3 :   SER( fd_pubkey_t,      node_pubkey                                );
     708           3 :   SER( ulong,            threshold_depth                            );
     709           3 :   SER( double,           threshold_size                             );
     710             : 
     711             :   /* VoteState1_14_11 */
     712             : 
     713           3 :   SER( fd_pubkey_t,      vote_state.node_pubkey                     );
     714           3 :   SER( fd_pubkey_t,      vote_state.authorized_withdrawer           );
     715           3 :   SER( uchar,            vote_state.commission                      );
     716           3 :   SER( ulong,            vote_state.votes_cnt                       );
     717          96 :   for( ulong i=0; i < fd_ulong_min( *ser->vote_state.votes_cnt, 31 ); i++ ) {
     718          93 :     SER( ulong,          vote_state.votes[i].slot                   );
     719          93 :     SER( uint,           vote_state.votes[i].confirmation_count     );
     720          93 :   }
     721           3 :   SER( uchar,            vote_state.root_slot_option                );
     722           3 :   if( FD_LIKELY( *ser->vote_state.root_slot_option ) ) {
     723           3 :     SER( ulong,          vote_state.root_slot                       );
     724           3 :   }
     725           3 :   SER( ulong,            vote_state.authorized_voters_cnt           );
     726           3 :   for( ulong i = 0; i < fd_ulong_min( *ser->vote_state.authorized_voters_cnt, 32 ); i++ ) {
     727           0 :     SER( ulong,          vote_state.authorized_voters[i].epoch      );
     728           0 :     SER( fd_pubkey_t,    vote_state.authorized_voters[i].pubkey     );
     729           0 :   }
     730          99 :   for( ulong i = 0; i < 32; i++ ) {
     731          96 :     SER( fd_pubkey_t,    vote_state.prior_voters.buf[i].pubkey      );
     732          96 :     SER( ulong,          vote_state.prior_voters.buf[i].start_epoch );
     733          96 :     SER( ulong,          vote_state.prior_voters.buf[i].end_epoch   );
     734          96 :   }
     735           3 :   SER( ulong,            vote_state.prior_voters.idx                );
     736           3 :   SER( uchar,            vote_state.prior_voters.is_empty           );
     737           3 :   SER( ulong,            vote_state.epoch_credits_cnt               );
     738           3 :   for( ulong i = 0; i < fd_ulong_min( *ser->vote_state.epoch_credits_cnt, 32 ); i++ ) {
     739           0 :     SER( ulong,          vote_state.epoch_credits[i].epoch          );
     740           0 :     SER( ulong,          vote_state.epoch_credits[i].credits        );
     741           0 :     SER( ulong,          vote_state.epoch_credits[i].prev_credits   );
     742           0 :   }
     743           3 :   SER( ulong,            vote_state.last_timestamp.slot             );
     744           3 :   SER( long,             vote_state.last_timestamp.timestamp        );
     745             : 
     746             :   /* VoteTransaction::TowerSync */
     747             : 
     748           3 :   SER( uint,             last_vote_kind                             );
     749           3 :   SER( ulong,            last_vote.root                             );
     750           3 :   off += ser_short_vec_cnt( buf+off, ser->last_vote.lockouts_cnt );
     751          96 :   for( ulong i = 0; i < fd_ulong_min( ser->last_vote.lockouts_cnt, 31 ); i++ ) {
     752          93 :     off += ser_varint( buf+off, ser->last_vote.lockouts[i].offset );
     753          93 :     SER( uchar,          last_vote.lockouts[i].confirmation_count   );
     754          93 :   }
     755           3 :   SER( fd_hash_t,        last_vote.hash                             );
     756           3 :   SER( uchar,            last_vote.timestamp_option                 );
     757           3 :   if( FD_LIKELY( *ser->last_vote.timestamp_option ) ) {
     758           3 :     SER( long,           last_vote.timestamp                        );
     759           3 :   }
     760           3 :   SER( fd_hash_t,        last_vote.block_id                         );
     761             : 
     762             :   /* BlockTimestamp */
     763             : 
     764           3 :   SER( ulong,            last_timestamp.slot                        );
     765           3 :   SER( long,             last_timestamp.timestamp                   );
     766             : 
     767           3 :   #undef SER
     768           3 :   #undef OFF
     769             : 
     770           3 :   *buf_sz = off;
     771             : 
     772           3 :   return 0;
     773           3 : }
     774             : 
     775             : static ulong
     776           3 : de_short_vec_cnt( ushort * dst, uchar * src ) {
     777           3 :   if     ( FD_LIKELY( !(0x80U & src[0]) ) ) { *dst = (ushort)src[0];                                                                           return 1; }
     778           0 :   else if( FD_LIKELY( !(0x80U & src[1]) ) ) { *dst = (ushort)((ulong)(src[0]&0x7FUL) + (((ulong)src[1])<<7));                                  return 2; }
     779           0 :   else                                      { *dst = (ushort)((ulong)(src[0]&0x7FUL) + (((ulong)(src[1]&0x7FUL))<<7) + (((ulong)src[2])<<14)); return 3; }
     780           3 : }
     781             : 
     782             : static ulong
     783          93 : de_varint( ulong * dst, uchar * src ) {
     784          93 :   *dst = 0;
     785          93 :   ulong off = 0;
     786          93 :   ulong bit = 0;
     787          93 :   while( FD_LIKELY( bit < 64 ) ) {
     788          93 :     uchar byte = *(uchar const *)(src+off);
     789          93 :     off       += 1;
     790          93 :     *dst      |= (byte & 0x7FUL) << bit;
     791          93 :     if( FD_LIKELY( (byte & 0x80U) == 0U ) ) {
     792          93 :       if( FD_UNLIKELY( (*dst>>bit) != byte                ) ) FD_LOG_CRIT(( "de_varint" ));
     793          93 :       if( FD_UNLIKELY( byte==0U && (bit!=0U || *dst!=0UL) ) ) FD_LOG_CRIT(( "de_varint" ));
     794          93 :       return off;
     795          93 :     }
     796           0 :     bit += 7;
     797           0 :   }
     798           0 :   FD_LOG_CRIT(( "de_varint" ));
     799           0 : }
     800             : 
     801             : int
     802             : fd_tower_deserialize( uchar *            buf,
     803             :                       ulong              buf_sz,
     804           3 :                       fd_tower_serde_t * de ) {
     805             : 
     806         645 :   #define DE( T, name ) do {                                                               \
     807         645 :       if( FD_UNLIKELY( off+sizeof(T)>buf_sz ) ) {                                          \
     808           0 :         FD_LOG_WARNING(( "de %s: overflow (off %lu > buf_sz: %lu)", #name, off, buf_sz )); \
     809           0 :         return -1;                                                                         \
     810           0 :       }                                                                                    \
     811         645 :       de->name = (T const *)fd_type_pun_const( buf+off );                                  \
     812         645 :       off += sizeof(T);                                                                    \
     813         645 :   } while(0)
     814             : 
     815           3 :   ulong off = 0;
     816             : 
     817             :   /* SavedTower::Current */
     818             : 
     819           3 :   DE( uint,             kind                                       );
     820           3 :   DE( fd_ed25519_sig_t, signature                                  );
     821           3 :   DE( ulong,            data_sz                                    );
     822           3 :   DE( fd_pubkey_t,      node_pubkey                                );
     823           3 :   DE( ulong,            threshold_depth                            );
     824           3 :   DE( double,           threshold_size                             );
     825             : 
     826             :   /* VoteState1_14_11 */
     827             : 
     828           3 :   DE( fd_pubkey_t,      vote_state.node_pubkey                     );
     829           3 :   DE( fd_pubkey_t,      vote_state.authorized_withdrawer           );
     830           3 :   DE( uchar,            vote_state.commission                      );
     831           3 :   DE( ulong,            vote_state.votes_cnt                       );
     832          96 :   for( ulong i=0; i < fd_ulong_min( *de->vote_state.votes_cnt, 31 ); i++ ) {
     833          93 :     DE( ulong,          vote_state.votes[i].slot                   );
     834          93 :     DE( uint,           vote_state.votes[i].confirmation_count     );
     835          93 :   }
     836           3 :   DE( uchar,            vote_state.root_slot_option                );
     837           3 :   if( FD_LIKELY( *de->vote_state.root_slot_option ) ) {
     838           3 :     DE( ulong,          vote_state.root_slot                       );
     839           3 :   }
     840           3 :   DE( ulong,            vote_state.authorized_voters_cnt           );
     841           3 :   for( ulong i = 0; i < fd_ulong_min( *de->vote_state.authorized_voters_cnt, 32 ); i++ ) {
     842           0 :     DE( ulong,          vote_state.authorized_voters[i].epoch      );
     843           0 :     DE( fd_pubkey_t,    vote_state.authorized_voters[i].pubkey     );
     844           0 :   }
     845          99 :   for( ulong i = 0; i < 32; i++ ) {
     846          96 :     DE( fd_pubkey_t,    vote_state.prior_voters.buf[i].pubkey      );
     847          96 :     DE( ulong,          vote_state.prior_voters.buf[i].start_epoch );
     848          96 :     DE( ulong,          vote_state.prior_voters.buf[i].end_epoch   );
     849          96 :   }
     850           3 :   DE( ulong,            vote_state.prior_voters.idx                );
     851           3 :   DE( uchar,            vote_state.prior_voters.is_empty           );
     852           3 :   DE( ulong,            vote_state.epoch_credits_cnt               );
     853           3 :   for( ulong i = 0; i < fd_ulong_min( *de->vote_state.epoch_credits_cnt, 32 ); i++ ) {
     854           0 :     DE( ulong,          vote_state.epoch_credits[i].epoch          );
     855           0 :     DE( ulong,          vote_state.epoch_credits[i].credits        );
     856           0 :     DE( ulong,          vote_state.epoch_credits[i].prev_credits   );
     857           0 :   }
     858           3 :   DE( ulong,            vote_state.last_timestamp.slot             );
     859           3 :   DE( long,             vote_state.last_timestamp.timestamp        );
     860             : 
     861             :   /* VoteTransaction::TowerSync */
     862             : 
     863           3 :   DE( uint,             last_vote_kind                             );
     864           3 :   DE( ulong,            last_vote.root                             );
     865           3 :   off += de_short_vec_cnt( &de->last_vote.lockouts_cnt, buf+off );
     866          96 :   for( ulong i = 0; i < fd_ulong_min( de->last_vote.lockouts_cnt, 31 ); i++ ) {
     867          93 :     off += de_varint( &de->last_vote.lockouts[i].offset, buf+off );
     868          93 :     DE( uchar,          last_vote.lockouts[i].confirmation_count   );
     869          93 :   }
     870           3 :   DE( fd_hash_t,        last_vote.hash                             );
     871           3 :   DE( uchar,            last_vote.timestamp_option                 );
     872           3 :   if( FD_LIKELY( *de->last_vote.timestamp_option ) ) {
     873           3 :     DE( long,           last_vote.timestamp                        );
     874           3 :   }
     875           3 :   DE( fd_hash_t,        last_vote.block_id                         );
     876             : 
     877             :   /* BlockTimestamp */
     878             : 
     879           3 :   DE( ulong,            last_timestamp.slot                        );
     880           3 :   DE( long,             last_timestamp.timestamp                   );
     881             : 
     882           3 :   #undef DE
     883             : 
     884           3 :   return 0;
     885           3 : }
     886             : 
     887             : void
     888             : fd_tower_to_vote_txn( fd_tower_t const *    tower,
     889             :                       ulong                 root,
     890             :                       fd_lockout_offset_t * lockouts_scratch,
     891             :                       fd_hash_t const *     bank_hash,
     892             :                       fd_hash_t const *     recent_blockhash,
     893             :                       fd_pubkey_t const *   validator_identity,
     894             :                       fd_pubkey_t const *   vote_authority,
     895             :                       fd_pubkey_t const *   vote_acc,
     896           0 :                       fd_txn_p_t *          vote_txn ) {
     897             : 
     898           0 :   fd_compact_vote_state_update_t tower_sync;
     899           0 :   tower_sync.root          = fd_ulong_if( root == ULONG_MAX, 0UL, root );
     900           0 :   tower_sync.lockouts_len  = (ushort)fd_tower_votes_cnt( tower );
     901           0 :   tower_sync.lockouts      = lockouts_scratch;
     902           0 :   tower_sync.timestamp     = fd_log_wallclock() / (long)1e9; /* seconds */
     903           0 :   tower_sync.has_timestamp = 1;
     904             : 
     905           0 :   ulong prev = tower_sync.root;
     906           0 :   ulong i    = 0UL;
     907           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower       );
     908           0 :                                    !fd_tower_votes_iter_done( tower, iter );
     909           0 :                              iter = fd_tower_votes_iter_next( tower, iter ) ) {
     910           0 :     fd_tower_vote_t const * vote              = fd_tower_votes_iter_ele_const( tower, iter );
     911           0 :     tower_sync.lockouts[i].offset             = vote->slot - prev;
     912           0 :     tower_sync.lockouts[i].confirmation_count = (uchar)vote->conf;
     913           0 :     prev                                      = vote->slot;
     914           0 :     i++;
     915           0 :   }
     916           0 :   memcpy( tower_sync.hash.uc, bank_hash, sizeof(fd_hash_t) );
     917             : 
     918           0 :   uchar * txn_out = vote_txn->payload;
     919           0 :   uchar * txn_meta_out = vote_txn->_;
     920             : 
     921           0 :   int same_addr = !memcmp( validator_identity, vote_authority, sizeof(fd_pubkey_t) );
     922           0 :   if( FD_LIKELY( same_addr ) ) {
     923             : 
     924             :     /* 0: validator identity
     925             :        1: vote account address
     926             :        2: vote program */
     927             : 
     928           0 :     fd_txn_accounts_t accts;
     929           0 :     accts.signature_cnt         = 1;
     930           0 :     accts.readonly_signed_cnt   = 0;
     931           0 :     accts.readonly_unsigned_cnt = 1;
     932           0 :     accts.acct_cnt              = 3;
     933           0 :     accts.signers_w             = validator_identity;
     934           0 :     accts.signers_r             = NULL;
     935           0 :     accts.non_signers_w         = vote_acc;
     936           0 :     accts.non_signers_r         = &fd_solana_vote_program_id;
     937           0 :     FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, accts.signature_cnt, &accts, recent_blockhash->uc ) );
     938           0 :   } else {
     939             : 
     940             :     /* 0: validator identity
     941             :        1: vote authority
     942             :        2: vote account address
     943             :        3: vote program */
     944             : 
     945           0 :     fd_txn_accounts_t accts;
     946           0 :     accts.signature_cnt         = 2;
     947           0 :     accts.readonly_signed_cnt   = 1;
     948           0 :     accts.readonly_unsigned_cnt = 1;
     949           0 :     accts.acct_cnt              = 4;
     950           0 :     accts.signers_w             = validator_identity;
     951           0 :     accts.signers_r             = vote_authority;
     952           0 :     accts.non_signers_w         = vote_acc;
     953           0 :     accts.non_signers_r         = &fd_solana_vote_program_id;
     954           0 :     FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, accts.signature_cnt, &accts, recent_blockhash->uc ) );
     955           0 :   }
     956             : 
     957             :   /* Add the vote instruction to the transaction. */
     958             : 
     959           0 :   fd_vote_instruction_t vote_ix;
     960           0 :   uchar                 vote_ix_buf[FD_TXN_MTU];
     961           0 :   vote_ix.discriminant                    = fd_vote_instruction_enum_compact_update_vote_state;
     962           0 :   vote_ix.inner.compact_update_vote_state = tower_sync;
     963           0 :   fd_bincode_encode_ctx_t encode = { .data = vote_ix_buf, .dataend = ( vote_ix_buf + FD_TXN_MTU ) };
     964           0 :   fd_vote_instruction_encode( &vote_ix, &encode );
     965           0 :   uchar program_id;
     966           0 :   uchar ix_accs[2];
     967           0 :   if( FD_LIKELY( same_addr ) ) {
     968           0 :     ix_accs[0] = 1; /* vote account address */
     969           0 :     ix_accs[1] = 0; /* vote authority */
     970           0 :     program_id = 2; /* vote program */
     971           0 :   } else {
     972           0 :     ix_accs[0] = 2; /* vote account address */
     973           0 :     ix_accs[1] = 1; /* vote authority */
     974           0 :     program_id = 3; /* vote program */
     975           0 :   }
     976           0 :   ushort vote_ix_sz = (ushort)fd_vote_instruction_size( &vote_ix );
     977           0 :   vote_txn->payload_sz = fd_txn_add_instr( txn_meta_out, txn_out, program_id, ix_accs, 2, vote_ix_buf, vote_ix_sz );
     978           0 : }
     979             : 
     980             : int
     981           0 : fd_tower_verify( fd_tower_t const * tower ) {
     982           0 :   fd_tower_vote_t const * prev = NULL;
     983           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower       );
     984           0 :                                    !fd_tower_votes_iter_done( tower, iter );
     985           0 :                              iter = fd_tower_votes_iter_next( tower, iter ) ) {
     986           0 :     fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
     987           0 :     if( FD_LIKELY( prev && !( vote->slot < prev->slot && vote->conf < prev->conf ) ) ) {
     988           0 :       FD_LOG_WARNING(( "[%s] invariant violation: vote %lu %lu. prev %lu %lu", __func__, vote->slot, vote->conf, prev->slot, prev->conf ));
     989           0 :       return -1;
     990           0 :     }
     991           0 :     prev = vote;
     992           0 :   }
     993           0 :   return 0;
     994           0 : }
     995             : 
     996             : #include <stdio.h>
     997             : 
     998             : void
     999           0 : fd_tower_print( fd_tower_t const * tower, ulong root ) {
    1000           0 :   FD_LOG_NOTICE( ( "\n\n[Tower]" ) );
    1001           0 :   ulong max_slot = 0;
    1002             : 
    1003             :   /* Determine spacing. */
    1004             : 
    1005           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower       );
    1006           0 :                                    !fd_tower_votes_iter_done_rev( tower, iter );
    1007           0 :                              iter = fd_tower_votes_iter_prev    ( tower, iter ) ) {
    1008             : 
    1009           0 :     max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
    1010           0 :   }
    1011             : 
    1012             :   /* Calculate the number of digits in the maximum slot value. */
    1013             : 
    1014           0 :   int           digit_cnt = 0;
    1015           0 :   unsigned long rem       = max_slot;
    1016           0 :   do {
    1017           0 :     rem /= 10;
    1018           0 :     ++digit_cnt;
    1019           0 :   } while( rem > 0 );
    1020             : 
    1021             :   /* Print the table header */
    1022             : 
    1023           0 :   printf( "slot%*s | %s\n", digit_cnt - (int)strlen("slot"), "", "confirmation count" );
    1024             : 
    1025             :   /* Print the divider line */
    1026             : 
    1027           0 :   for( int i = 0; i < digit_cnt; i++ ) {
    1028           0 :     printf( "-" );
    1029           0 :   }
    1030           0 :   printf( " | " );
    1031           0 :   for( ulong i = 0; i < strlen( "confirmation count" ); i++ ) {
    1032           0 :     printf( "-" );
    1033           0 :   }
    1034           0 :   printf( "\n" );
    1035             : 
    1036             :   /* Print each record in the table */
    1037             : 
    1038           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower       );
    1039           0 :                                    !fd_tower_votes_iter_done_rev( tower, iter );
    1040           0 :                              iter = fd_tower_votes_iter_prev    ( tower, iter ) ) {
    1041             : 
    1042           0 :     fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
    1043           0 :     printf( "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf );
    1044           0 :     max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
    1045           0 :   }
    1046           0 :   printf( "%*lu | root\n", digit_cnt, root );
    1047           0 :   printf( "\n" );
    1048           0 : }
    1049             : 
    1050             : void
    1051             : fd_tower_from_vote_acc_data( uchar const * data,
    1052           0 :                              fd_tower_t *  tower_out ) {
    1053           0 : # if FD_TOWER_PARANOID
    1054           0 :   FD_TEST( fd_tower_votes_empty( tower_out ) );
    1055           0 : # endif
    1056             : 
    1057           0 :   fd_voter_state_t const * state = (fd_voter_state_t const *)fd_type_pun_const( data );
    1058             : 
    1059             :   /* Push all the votes onto the tower. */
    1060           0 :   for( ulong i = 0; i < fd_voter_state_cnt( state ); i++ ) {
    1061           0 :     fd_tower_vote_t vote = { 0 };
    1062           0 :     if( FD_UNLIKELY( state->kind == fd_vote_state_versioned_enum_v0_23_5 ) ) {
    1063           0 :       vote.slot = state->v0_23_5.votes[i].slot;
    1064           0 :       vote.conf = state->v0_23_5.votes[i].conf;
    1065           0 :     } else if( FD_UNLIKELY( state->kind == fd_vote_state_versioned_enum_v1_14_11 ) ) {
    1066           0 :       vote.slot = state->v1_14_11.votes[i].slot;
    1067           0 :       vote.conf = state->v1_14_11.votes[i].conf;
    1068           0 :     } else if ( FD_UNLIKELY( state->kind == fd_vote_state_versioned_enum_current ) ) {
    1069           0 :       vote.slot = state->votes[i].slot;
    1070           0 :       vote.conf = state->votes[i].conf;
    1071           0 :     } else {
    1072           0 :       FD_LOG_CRIT(( "[%s] unknown vote state version. discriminant %u", __func__, state->kind ));
    1073           0 :     }
    1074           0 :     fd_tower_votes_push_tail( tower_out, vote );
    1075           0 :   }
    1076           0 : }

Generated by: LCOV version 1.14