LCOV - code coverage report
Current view: top level - choreo/tower - fd_tower.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 66 349 18.9 %
Date: 2025-08-05 05:04:49 Functions: 8 17 47.1 %

          Line data    Source code
       1             : #include "fd_tower.h"
       2             : #include "../../flamenco/txn/fd_txn_generate.h"
       3             : #include "../../flamenco/runtime/fd_system_ids.h"
       4             : 
       5           0 : #define THRESHOLD_DEPTH         (8)
       6           0 : #define THRESHOLD_PCT           (2.0 / 3.0)
       7             : #define SHALLOW_THRESHOLD_DEPTH (4)
       8             : #define SHALLOW_THRESHOLD_PCT   (0.38)
       9           0 : #define SWITCH_PCT              (0.38)
      10             : 
      11             : void *
      12           3 : fd_tower_new( void * shmem ) {
      13           3 :   if( FD_UNLIKELY( !shmem ) ) {
      14           0 :     FD_LOG_WARNING(( "NULL mem" ));
      15           0 :     return NULL;
      16           0 :   }
      17             : 
      18           3 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_tower_align() ) ) ) {
      19           0 :     FD_LOG_WARNING(( "misaligned mem" ));
      20           0 :     return NULL;
      21           0 :   }
      22             : 
      23           3 :   return fd_tower_votes_new( shmem );
      24           3 : }
      25             : 
      26             : fd_tower_t *
      27           3 : fd_tower_join( void * shtower ) {
      28             : 
      29           3 :   if( FD_UNLIKELY( !shtower ) ) {
      30           0 :     FD_LOG_WARNING(( "NULL tower" ));
      31           0 :     return NULL;
      32           0 :   }
      33             : 
      34           3 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shtower, fd_tower_align() ) ) ) {
      35           0 :     FD_LOG_WARNING(( "misaligned tower" ));
      36           0 :     return NULL;
      37           0 :   }
      38             : 
      39           3 :   return fd_tower_votes_join( shtower );
      40           3 : }
      41             : 
      42             : void *
      43           3 : fd_tower_leave( fd_tower_t * tower ) {
      44             : 
      45           3 :   if( FD_UNLIKELY( !tower ) ) {
      46           0 :     FD_LOG_WARNING(( "NULL tower" ));
      47           0 :     return NULL;
      48           0 :   }
      49             : 
      50           3 :   return fd_tower_votes_leave( tower );
      51           3 : }
      52             : 
      53             : void *
      54           3 : fd_tower_delete( void * tower ) {
      55             : 
      56           3 :   if( FD_UNLIKELY( !tower ) ) {
      57           0 :     FD_LOG_WARNING(( "NULL tower" ));
      58           0 :     return NULL;
      59           0 :   }
      60             : 
      61           3 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)tower, fd_tower_align() ) ) ) {
      62           0 :     FD_LOG_WARNING(( "misaligned tower" ));
      63           0 :     return NULL;
      64           0 :   }
      65             : 
      66           3 :   return fd_tower_votes_delete( tower );
      67           3 : }
      68             : 
      69             : static inline ulong
      70         105 : expiration( fd_tower_vote_t const * vote ) {
      71         105 :   ulong lockout = 1UL << vote->conf;
      72         105 :   return vote->slot + lockout;
      73         105 : }
      74             : 
      75             : static inline ulong
      76         102 : simulate_vote( fd_tower_t const * tower, ulong slot ) {
      77         102 :   ulong cnt = fd_tower_votes_cnt( tower );
      78         108 :   while( cnt ) {
      79             : 
      80             :     /* Return early if we can't pop the top tower vote, even if votes
      81             :        below it are expired. */
      82             : 
      83         105 :     if( FD_LIKELY( expiration( fd_tower_votes_peek_index_const( tower, cnt - 1 ) ) >= slot ) ) {
      84          99 :       break;
      85          99 :     }
      86           6 :     cnt--;
      87           6 :   }
      88         102 :   return cnt;
      89         102 : }
      90             : 
      91             : int
      92             : fd_tower_lockout_check( fd_tower_t const * tower,
      93             :                         fd_ghost_t const * ghost,
      94             :                         ulong              switch_slot,
      95           0 :                         fd_hash_t const  * hash_id ) {
      96           0 :   #if FD_TOWER_USE_HANDHOLDING
      97           0 :   FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
      98           0 :   #endif
      99             : 
     100             :   /* Simulate a vote to pop off all the votes that have been expired at
     101             :      the top of the tower. */
     102             : 
     103           0 :   ulong cnt = simulate_vote( tower, switch_slot );
     104             : 
     105             :   /* By definition, all votes in the tower must be for the same fork, so
     106             :      check if the previous vote (ie. the last vote in the tower) is on
     107             :      the same fork as the fork we want to vote for. We do this using
     108             :      ghost by checking if the previous vote slot is an ancestor of the
     109             :      `slot`. If the previous vote slot is too old (ie. older than
     110             :      ghost->root), then we don't have ancestry information anymore and
     111             :      we just assume it is on the same fork.
     112             : 
     113             :      FIXME discuss if it is safe to assume that? */
     114             : 
     115           0 :   fd_tower_vote_t const * vote = fd_tower_votes_peek_index_const( tower, cnt - 1 );
     116           0 :   fd_ghost_ele_t const *  root = fd_ghost_root_const( ghost );
     117             : 
     118           0 :   int lockout_check = vote->slot < root->slot ||
     119           0 :                       fd_ghost_is_ancestor( ghost, fd_ghost_hash( ghost, vote->slot ), hash_id );
     120           0 :   FD_LOG_NOTICE(( "[fd_tower_lockout_check] ok? %d. top: (slot: %lu, conf: %lu). switch: %lu.", lockout_check, vote->slot, vote->conf, switch_slot ));
     121           0 :   return lockout_check;
     122           0 : }
     123             : 
     124             : int
     125             : fd_tower_switch_check( fd_tower_t const * tower,
     126             :                        fd_epoch_t const * epoch,
     127             :                        fd_ghost_t const * ghost,
     128             :                        ulong              switch_slot,
     129           0 :                        fd_hash_t const *  switch_hash_id ) {
     130           0 :   #if FD_TOWER_USE_HANDHOLDING
     131           0 :   FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
     132           0 :   #endif
     133             : 
     134           0 :   fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
     135           0 :   fd_ghost_ele_t const *  root = fd_ghost_root_const( ghost );
     136             : 
     137           0 :   if( FD_UNLIKELY( vote->slot < root->slot ) ) {
     138             : 
     139             :     /* It is possible our last vote slot precedes our ghost root. This
     140             :        can happen, for example, when we restart from a snapshot and set
     141             :        the ghost root to the snapshot slot (we won't have an ancestry
     142             :        before the snapshot slot.)
     143             : 
     144             :        If this is the case, we assume it's ok to switch. */
     145             : 
     146           0 :     return 1;
     147           0 :   }
     148             : 
     149             :   /* fd_tower_switch_check is only called if latest_vote->slot and
     150             :      fork->slot are on different forks (determined by is_descendant), so
     151             :      they must not fall on the same ancestry path back to the gca.
     152             : 
     153             :      INVALID:
     154             : 
     155             :        0
     156             :         \
     157             :          1    <- a
     158             :           \
     159             :            2  <- b
     160             : 
     161             :      VALID:
     162             : 
     163             :        0
     164             :       / \
     165             :      1   2
     166             :      ^   ^
     167             :      a   b
     168             : 
     169             :   */
     170             : 
     171           0 :   #if FD_TOWER_USE_HANDHOLDING
     172           0 :   FD_TEST( !fd_ghost_is_ancestor( ghost, fd_ghost_hash( ghost, vote->slot ), switch_hash_id ) );
     173           0 :   #endif
     174           0 :   fd_hash_t     const * vote_block_id = fd_ghost_hash( ghost, vote->slot );
     175           0 :   fd_ghost_hash_map_t const * maph    = fd_ghost_hash_map_const( ghost );
     176           0 :   fd_ghost_ele_t      const * pool    = fd_ghost_pool_const( ghost );
     177           0 :   fd_ghost_ele_t      const * gca     = fd_ghost_gca( ghost, vote_block_id, switch_hash_id );
     178           0 :   ulong                       gca_idx = fd_ghost_hash_map_idx_query_const( maph, &gca->key, ULONG_MAX, pool );
     179             : 
     180             :   /* gca_child is our latest_vote slot's ancestor that is also a direct
     181             :      child of GCA.  So we do not count it towards the stake of the
     182             :      different forks. */
     183             : 
     184           0 :   fd_ghost_ele_t const * gca_child = fd_ghost_query_const( ghost, vote_block_id );
     185           0 :   while( FD_LIKELY( gca_child->parent != gca_idx ) ) {
     186           0 :     gca_child = fd_ghost_pool_ele_const( pool, gca_child->parent );
     187           0 :   }
     188             : 
     189           0 :   ulong switch_stake = 0;
     190           0 :   fd_ghost_ele_t const * child = fd_ghost_child_const( ghost, gca );
     191           0 :   while( FD_LIKELY( child ) ) {
     192           0 :     if( FD_LIKELY( child != gca_child ) ) {
     193           0 :       switch_stake += child->weight;
     194           0 :     }
     195           0 :     child = fd_ghost_pool_ele_const( pool, child->sibling );
     196           0 :   }
     197             : 
     198           0 :   double switch_pct = (double)switch_stake / (double)epoch->total_stake;
     199           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, switch_slot, switch_pct * 100.0 ));
     200           0 :   return switch_pct > SWITCH_PCT;
     201           0 : }
     202             : 
     203             : int
     204             : fd_tower_threshold_check( fd_tower_t const *    tower,
     205             :                           fd_epoch_t const *    epoch,
     206             :                           fd_funk_t *           funk,
     207             :                           fd_funk_txn_t const * txn,
     208             :                           ulong                 slot,
     209           0 :                           fd_tower_t *          scratch ) {
     210             : 
     211             :   /* First, simulate a vote, popping off everything that would be
     212             :      expired by voting for the current slot. */
     213             : 
     214           0 :   ulong cnt = simulate_vote( tower, slot );
     215             : 
     216             :   /* Return early if our tower is not at least THRESHOLD_DEPTH deep
     217             :      after simulating. */
     218             : 
     219           0 :   if( FD_UNLIKELY( cnt < THRESHOLD_DEPTH ) ) return 1;
     220             : 
     221             :   /* Get the vote slot from THRESHOLD_DEPTH back. Note THRESHOLD_DEPTH
     222             :      is the 8th index back _including_ the simulated vote at index 0,
     223             :      which is not accounted for by `cnt`, so subtracting THRESHOLD_DEPTH
     224             :      will conveniently index the threshold vote. */
     225             : 
     226           0 :   ulong threshold_slot = fd_tower_votes_peek_index_const( tower, cnt - THRESHOLD_DEPTH )->slot;
     227             : 
     228             :   /* Track the amount of stake that has vote slot >= threshold_slot. */
     229             : 
     230           0 :   ulong threshold_stake = 0;
     231             : 
     232             :   /* Iterate all the vote accounts. */
     233             : 
     234           0 :   fd_voter_t const * epoch_voters = fd_epoch_voters_const( epoch );
     235           0 :   for (ulong i = 0; i < fd_epoch_voters_slot_cnt( epoch_voters ); i++ ) {
     236           0 :     if( FD_LIKELY( fd_epoch_voters_key_inval( epoch_voters[i].key ) ) ) continue /* most slots are empty */;
     237             : 
     238           0 :     fd_voter_t const * voter = &epoch_voters[i];
     239             : 
     240             :     /* Convert the landed_votes into tower's vote_slots interface. */
     241             : 
     242           0 :     fd_tower_votes_remove_all( scratch );
     243           0 :     int err = fd_tower_from_vote_acc( scratch, funk, txn, &voter->rec );
     244           0 :     if( FD_UNLIKELY( err ) ) {
     245           0 :       FD_LOG_WARNING(( "[%s] failed to read vote account %s", __func__, FD_BASE58_ENC_32_ALLOCA(&voter->key) ));
     246           0 :       continue;
     247           0 :     }
     248             : 
     249             :     /* If this voter has not voted, continue. */
     250             : 
     251           0 :     if( FD_UNLIKELY( fd_tower_votes_empty( scratch ) ) ) continue;
     252             : 
     253           0 :     ulong cnt = simulate_vote( scratch, slot );
     254             : 
     255             :     /* Continue if their tower is empty after simulating. */
     256             : 
     257           0 :     if( FD_UNLIKELY( !cnt ) ) continue;
     258             : 
     259             :     /* Get their latest vote. */
     260             : 
     261           0 :     fd_tower_vote_t const * vote = fd_tower_votes_peek_index( scratch, cnt - 1 );
     262             : 
     263             :     /* Count their stake towards the threshold check if their latest
     264             :         vote slot >= our threshold slot.
     265             : 
     266             :         Because we are iterating vote accounts on the same fork that we
     267             :         we want to vote for, we know these slots must all occur along
     268             :         the same fork ancestry.
     269             : 
     270             :         Therefore, if their latest vote slot >= our threshold slot, we
     271             :         know that vote must be for the threshold slot itself or one of
     272             :         threshold slot's descendants. */
     273             : 
     274           0 :     if( FD_LIKELY( vote->slot >= threshold_slot ) ) {
     275           0 :       threshold_stake += voter->stake;
     276           0 :     }
     277           0 :   }
     278             : 
     279           0 :   double threshold_pct = (double)threshold_stake / (double)epoch->total_stake;
     280           0 :   FD_LOG_NOTICE(( "[%s] ok? %d. top: %lu. threshold: %lu. stake: %.0lf%%.", __func__, threshold_pct > THRESHOLD_PCT, fd_tower_votes_peek_tail_const( tower )->slot, threshold_slot, threshold_pct * 100.0 ));
     281           0 :   return threshold_pct > THRESHOLD_PCT;
     282           0 : }
     283             : 
     284             : ulong
     285             : fd_tower_reset_slot( fd_tower_t const * tower,
     286           0 :                      fd_ghost_t const * ghost ) {
     287             : 
     288           0 :   fd_tower_vote_t const *    vote = fd_tower_votes_peek_tail_const( tower );
     289           0 :   fd_ghost_ele_t const *     root = fd_ghost_root_const( ghost );
     290           0 :   fd_ghost_ele_t const *     head = fd_ghost_head( ghost, root );
     291           0 :   fd_hash_t const * vote_block_id = fd_ghost_hash( ghost, vote->slot );
     292             : 
     293             :   /* Reset to the ghost head if any of the following is true:
     294             :        1. haven't voted
     295             :        2. last vote < ghost root
     296             :        3. ghost root is not an ancestory of last vote */
     297             : 
     298           0 :   if( FD_UNLIKELY( !vote || vote->slot < root->slot ||
     299           0 :                    !fd_ghost_is_ancestor( ghost, &root->key, vote_block_id ) ) ) {
     300           0 :     return head->slot;
     301           0 :   }
     302             : 
     303             :   /* Find the ghost node keyed by our last vote slot. It is invariant
     304             :      that this node must always be found after doing the above check.
     305             :      Otherwise ghost and tower contain implementation bugs and/or are
     306             :      corrupt. */
     307             : 
     308           0 :   fd_ghost_ele_t const * vote_node = fd_ghost_query_const( ghost, vote_block_id );
     309           0 :   #if FD_TOWER_USE_HANDHOLDING
     310           0 :   if( FD_UNLIKELY( !vote_node ) ) {
     311           0 :     fd_ghost_print( ghost, 0, root );
     312           0 :     FD_LOG_ERR(( "[%s] invariant violation: unable to find last tower vote slot %lu in ghost.", __func__, vote->slot ));
     313           0 :   }
     314           0 :   #endif
     315             : 
     316             :   /* Starting from the node keyed by the last vote slot, greedy traverse
     317             :      for the head. */
     318             : 
     319           0 :   return fd_ghost_head( ghost, vote_node )->slot;
     320           0 : }
     321             : 
     322             : ulong
     323             : fd_tower_vote_slot( fd_tower_t *          tower,
     324             :                     fd_epoch_t const *    epoch,
     325             :                     fd_funk_t *           funk,
     326             :                     fd_funk_txn_t const * txn,
     327             :                     fd_ghost_t const *    ghost,
     328           0 :                     fd_tower_t *          scratch ) {
     329             : 
     330           0 :   fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
     331           0 :   fd_ghost_ele_t const *  root = fd_ghost_root_const( ghost );
     332           0 :   fd_ghost_ele_t const *  head = fd_ghost_head( ghost, root );
     333             : 
     334             :   /* Vote for the ghost head if any of the following is true:
     335             : 
     336             :      1. haven't voted
     337             :      2. last vote < ghost root
     338             :      3. ghost root is not an ancestory of last vote
     339             : 
     340             :      FIXME need to ensure lockout safety for case 2 and 3 */
     341             : 
     342           0 :   if( FD_UNLIKELY( !vote || vote->slot < root->slot ) ) {
     343           0 :     return head->slot;
     344           0 :   }
     345           0 :   fd_hash_t const * vote_block_id = fd_ghost_hash( ghost, vote->slot );
     346           0 :   if( FD_UNLIKELY( !fd_ghost_is_ancestor( ghost, &root->key, vote_block_id ) ) ) {
     347           0 :     return head->slot;
     348           0 :   }
     349             : 
     350             :   /* Optimize for when there is just one fork or that we already
     351             :      previously voted for the best fork. */
     352             : 
     353           0 :   if( FD_LIKELY( fd_ghost_is_ancestor( ghost, vote_block_id, &head->key ) ) ) {
     354             : 
     355             :     /* The ghost head is on the same fork as our last vote slot, so we
     356             :        can vote fork it as long as we pass the threshold check. */
     357             : 
     358           0 :     if( FD_LIKELY( fd_tower_threshold_check( tower, epoch, funk, txn, head->slot, scratch ) ) ) {
     359           0 :       FD_LOG_DEBUG(( "[%s] success (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
     360           0 :       return head->slot;
     361           0 :     }
     362           0 :     FD_LOG_DEBUG(( "[%s] failure (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
     363           0 :     return FD_SLOT_NULL; /* can't vote. need to wait for threshold check. */
     364           0 :   }
     365             : 
     366             :   /* The ghost head is on a different fork from our last vote slot, so
     367             :       try to switch if we pass lockout and switch threshold. */
     368             : 
     369           0 :   if( FD_UNLIKELY( fd_tower_lockout_check( tower, ghost, head->slot, &head->key ) &&
     370           0 :                    fd_tower_switch_check( tower, epoch, ghost, head->slot, &head->key ) ) ) {
     371           0 :     FD_LOG_DEBUG(( "[%s] success (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
     372           0 :     return head->slot;
     373           0 :   }
     374           0 :   FD_LOG_DEBUG(( "[%s] failure (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
     375           0 :   return FD_SLOT_NULL;
     376           0 : }
     377             : 
     378             : ulong
     379          99 : fd_tower_vote( fd_tower_t * tower, ulong slot ) {
     380          99 :   FD_LOG_DEBUG(( "[%s] voting for slot %lu", __func__, slot ));
     381             : 
     382          99 :   #if FD_TOWER_USE_HANDHOLDING
     383          99 :   fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
     384          99 :   if( FD_UNLIKELY( vote && slot < vote->slot ) ) FD_LOG_ERR(( "[%s] slot %lu < vote->slot %lu", __func__, slot, vote->slot )); /* caller error*/
     385          99 :   #endif
     386             : 
     387             :   /* Use simulate_vote to determine how many expired votes to pop. */
     388             : 
     389          99 :   ulong cnt = simulate_vote( tower, slot );
     390             : 
     391             :   /* Pop everything that got expired. */
     392             : 
     393         102 :   while( fd_tower_votes_cnt( tower ) > cnt ) {
     394           3 :     fd_tower_votes_pop_tail( tower );
     395           3 :   }
     396             : 
     397             :   /* If the tower is still full after expiring, then pop and return the
     398             :      bottom vote slot as the new root because this vote has incremented
     399             :      it to max lockout.  Otherwise this is a no-op and there is no new
     400             :      root (FD_SLOT_NULL). */
     401             : 
     402          99 :   ulong root = FD_SLOT_NULL;
     403          99 :   if( FD_LIKELY( fd_tower_votes_full( tower ) ) ) { /* optimize for full tower */
     404           3 :     root = fd_tower_votes_pop_head( tower ).slot;
     405           3 :   }
     406             : 
     407             :   /* Increment confirmations (double lockouts) for consecutive
     408             :      confirmations in prior votes. */
     409             : 
     410          99 :   ulong prev_conf = 0;
     411          99 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
     412        1584 :        !fd_tower_votes_iter_done_rev( tower, iter );
     413        1488 :        iter = fd_tower_votes_iter_prev( tower, iter ) ) {
     414        1488 :     fd_tower_vote_t * vote = fd_tower_votes_iter_ele( tower, iter );
     415        1488 :     if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) {
     416           3 :       break;
     417           3 :     }
     418        1485 :     vote->conf++;
     419        1485 :   }
     420             : 
     421             :   /* Add the new vote to the tower. */
     422             : 
     423          99 :   fd_tower_votes_push_tail( tower, (fd_tower_vote_t){ .slot = slot, .conf = 1 } );
     424             : 
     425             :   /* Return the new root (FD_SLOT_NULL if there is none). */
     426             : 
     427          99 :   return root;
     428          99 : }
     429             : 
     430             : ulong
     431           3 : fd_tower_simulate_vote( fd_tower_t const * tower, ulong slot ) {
     432           3 :   #if FD_TOWER_USE_HANDHOLDING
     433           3 :   FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
     434           3 :   #endif
     435             : 
     436           3 :   return simulate_vote( tower, slot );
     437           3 : }
     438             : 
     439             : int
     440             : fd_tower_from_vote_acc( fd_tower_t *              tower,
     441             :                         fd_funk_t *               funk,
     442             :                         fd_funk_txn_t const *     txn,
     443           0 :                         fd_funk_rec_key_t const * vote_acc ) {
     444           0 : # if FD_TOWER_USE_HANDHOLDING
     445           0 :   FD_TEST( fd_tower_votes_empty( tower ) );
     446           0 : # endif
     447             : 
     448           0 :   for(;;) {
     449             : 
     450             :     /* Speculatively query the record and parse the voter state. If the
     451             :        record is missing or the voter state fails to parse, then return
     452             :        early (tower will be empty). */
     453             : 
     454           0 :     fd_funk_rec_query_t   query;
     455           0 :     fd_funk_rec_t const * rec = fd_funk_rec_query_try_global( funk, txn, vote_acc, NULL, &query );
     456           0 :     if( FD_UNLIKELY( !rec ) ) return -1; /* record not found */
     457           0 :     fd_voter_state_t const * state = fd_voter_state( funk, rec );
     458           0 :     if( FD_UNLIKELY( !state ) ) return -1; /* unable to parse voter state */
     459             : 
     460             :     /* Speculatively query the cnt.  */
     461             : 
     462           0 :     ulong cnt = fd_voter_state_cnt( state ); /* TODO remove once Funk reads are safe */
     463           0 :     if( FD_UNLIKELY( fd_funk_rec_query_test( &query ) != FD_FUNK_SUCCESS ) ) continue;
     464           0 :     if( FD_UNLIKELY( cnt > 31UL ) ) FD_LOG_ERR(( "[%s] funk vote account corruption. cnt %lu > 31", __func__, cnt ));
     465             : 
     466             :     /* Speculatively read the votes out of the state and push them onto
     467             :        the tower. If there is a conflicting operation during this read,
     468             :        rollback the tower. */
     469             : 
     470           0 :     fd_tower_vote_t vote = { 0 };
     471           0 :     ulong sz = sizeof(fd_voter_vote_old_t);
     472           0 :     for( ulong i = 0; i < cnt; i++ ) {
     473           0 :       if( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_v0_23_5 ) ) {
     474           0 :         memcpy( (uchar *)&vote, (uchar *)(state->v0_23_5.votes + i), sz );
     475           0 :       } else if ( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_v1_14_11 ) ) {
     476           0 :         memcpy( (uchar *)&vote, (uchar *)(state->v1_14_11.votes + i), sz );
     477           0 :       } else if ( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_current ) ) {
     478           0 :         memcpy( (uchar *)&vote, (uchar *)(state->votes + i) + sizeof(uchar) /* latency */, sz );
     479           0 :       } else {
     480           0 :         FD_LOG_ERR(( "[%s] unknown state->discriminant %u", __func__, state->discriminant ));
     481           0 :       }
     482           0 :       fd_tower_votes_push_tail( tower, vote );
     483           0 :     }
     484             : 
     485           0 :     if( FD_LIKELY( fd_funk_rec_query_test( &query ) == FD_FUNK_SUCCESS ) ) return 0;
     486           0 :     else fd_tower_votes_remove_all( tower ); /* reset the tower and try again  */
     487           0 :   }
     488           0 : }
     489             : 
     490             : void
     491             : fd_tower_to_vote_txn( fd_tower_t const *    tower,
     492             :                       ulong                 root,
     493             :                       fd_lockout_offset_t * lockouts_scratch,
     494             :                       fd_hash_t const *     bank_hash,
     495             :                       fd_hash_t const *     recent_blockhash,
     496             :                       fd_pubkey_t const *   validator_identity,
     497             :                       fd_pubkey_t const *   vote_authority,
     498             :                       fd_pubkey_t const *   vote_acc,
     499           0 :                       fd_txn_p_t *          vote_txn ) {
     500             : 
     501           0 :   fd_compact_vote_state_update_t tower_sync;
     502           0 :   tower_sync.root          = root;
     503           0 :   tower_sync.lockouts_len  = (ushort)fd_tower_votes_cnt( tower );
     504           0 :   tower_sync.lockouts      = lockouts_scratch;
     505           0 :   tower_sync.timestamp     = fd_log_wallclock();
     506           0 :   tower_sync.has_timestamp = 1;
     507             : 
     508           0 :   ulong prev = tower_sync.root;
     509           0 :   ulong i    = 0UL;
     510           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
     511           0 :       !fd_tower_votes_iter_done( tower, iter );
     512           0 :       iter = fd_tower_votes_iter_next( tower, iter ) ) {
     513           0 :     fd_tower_vote_t const * vote              = fd_tower_votes_iter_ele_const( tower, iter );
     514           0 :     tower_sync.lockouts[i].offset             = vote->slot - prev;
     515           0 :     tower_sync.lockouts[i].confirmation_count = (uchar)vote->conf;
     516           0 :     prev                                      = vote->slot;
     517           0 :     i++;
     518           0 :   }
     519           0 :   memcpy( tower_sync.hash.uc, bank_hash, sizeof(fd_hash_t) );
     520             : 
     521           0 :   uchar * txn_out = vote_txn->payload;
     522           0 :   uchar * txn_meta_out = vote_txn->_;
     523             : 
     524           0 :   int same_addr = !memcmp( validator_identity, vote_authority, sizeof(fd_pubkey_t) );
     525           0 :   if( FD_LIKELY( same_addr ) ) {
     526             : 
     527             :     /* 0: validator identity
     528             :        1: vote account address
     529             :        2: vote program */
     530             : 
     531           0 :     fd_txn_accounts_t accts;
     532           0 :     accts.signature_cnt         = 1;
     533           0 :     accts.readonly_signed_cnt   = 0;
     534           0 :     accts.readonly_unsigned_cnt = 1;
     535           0 :     accts.acct_cnt              = 3;
     536           0 :     accts.signers_w             = validator_identity;
     537           0 :     accts.signers_r             = NULL;
     538           0 :     accts.non_signers_w         = vote_acc;
     539           0 :     accts.non_signers_r         = &fd_solana_vote_program_id;
     540           0 :     FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, accts.signature_cnt, &accts, recent_blockhash->uc ) );
     541           0 :   } else {
     542             : 
     543             :     /* 0: validator identity
     544             :        1: vote authority
     545             :        2: vote account address
     546             :        3: vote program */
     547             : 
     548           0 :     fd_txn_accounts_t accts;
     549           0 :     accts.signature_cnt         = 2;
     550           0 :     accts.readonly_signed_cnt   = 1;
     551           0 :     accts.readonly_unsigned_cnt = 1;
     552           0 :     accts.acct_cnt              = 4;
     553           0 :     accts.signers_w             = validator_identity;
     554           0 :     accts.signers_r             = vote_authority;
     555           0 :     accts.non_signers_w         = vote_acc;
     556           0 :     accts.non_signers_r         = &fd_solana_vote_program_id;
     557           0 :     FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, accts.signature_cnt, &accts, recent_blockhash->uc ) );
     558           0 :   }
     559             : 
     560             :   /* Add the vote instruction to the transaction. */
     561             : 
     562           0 :   fd_vote_instruction_t vote_ix;
     563           0 :   uchar                 vote_ix_buf[FD_TXN_MTU];
     564           0 :   vote_ix.discriminant                    = fd_vote_instruction_enum_compact_update_vote_state;
     565           0 :   vote_ix.inner.compact_update_vote_state = tower_sync;
     566           0 :   fd_bincode_encode_ctx_t encode = { .data = vote_ix_buf, .dataend = ( vote_ix_buf + FD_TXN_MTU ) };
     567           0 :   fd_vote_instruction_encode( &vote_ix, &encode );
     568           0 :   uchar program_id;
     569           0 :   uchar ix_accs[2];
     570           0 :   if( FD_LIKELY( same_addr ) ) {
     571           0 :     ix_accs[0] = 1; /* vote account address */
     572           0 :     ix_accs[1] = 0; /* vote authority */
     573           0 :     program_id = 2; /* vote program */
     574           0 :   } else {
     575           0 :     ix_accs[0] = 2; /* vote account address */
     576           0 :     ix_accs[1] = 1; /* vote authority */
     577           0 :     program_id = 3; /* vote program */
     578           0 :   }
     579           0 :   ushort vote_ix_sz = (ushort)fd_vote_instruction_size( &vote_ix );
     580           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 );
     581           0 : }
     582             : 
     583             : int
     584           0 : fd_tower_verify( fd_tower_t const * tower ) {
     585           0 :   fd_tower_vote_t const * prev = NULL;
     586           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
     587           0 :        !fd_tower_votes_iter_done( tower, iter );
     588           0 :        iter = fd_tower_votes_iter_next( tower, iter ) ) {
     589           0 :     fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
     590           0 :     if( FD_LIKELY( prev && !( vote->slot < prev->slot && vote->conf < prev->conf ) ) ) {
     591           0 :       FD_LOG_WARNING(( "[%s] invariant violation: vote %lu %lu. prev %lu %lu", __func__, vote->slot, vote->conf, prev->slot, prev->conf ));
     592           0 :       return -1;
     593           0 :     }
     594           0 :     prev = vote;
     595           0 :   }
     596           0 :   return 0;
     597           0 : }
     598             : 
     599             : #include <stdio.h>
     600             : 
     601             : void
     602           0 : fd_tower_print( fd_tower_t const * tower, ulong root ) {
     603           0 :   FD_LOG_NOTICE( ( "\n\n[Tower]" ) );
     604           0 :   ulong max_slot = 0;
     605             : 
     606             :   /* Determine spacing. */
     607             : 
     608           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
     609           0 :        !fd_tower_votes_iter_done_rev( tower, iter );
     610           0 :        iter = fd_tower_votes_iter_prev( tower, iter ) ) {
     611             : 
     612           0 :     max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
     613           0 :   }
     614             : 
     615             :   /* Calculate the number of digits in the maximum slot value. */
     616             : 
     617           0 :   int           digit_cnt = 0;
     618           0 :   unsigned long rem       = max_slot;
     619           0 :   do {
     620           0 :     rem /= 10;
     621           0 :     ++digit_cnt;
     622           0 :   } while( rem > 0 );
     623             : 
     624             :   /* Print the table header */
     625             : 
     626           0 :   printf( "slot%*s | %s\n", digit_cnt - (int)strlen("slot"), "", "confirmation count" );
     627             : 
     628             :   /* Print the divider line */
     629             : 
     630           0 :   for( int i = 0; i < digit_cnt; i++ ) {
     631           0 :     printf( "-" );
     632           0 :   }
     633           0 :   printf( " | " );
     634           0 :   for( ulong i = 0; i < strlen( "confirmation count" ); i++ ) {
     635           0 :     printf( "-" );
     636           0 :   }
     637           0 :   printf( "\n" );
     638             : 
     639             :   /* Print each record in the table */
     640             : 
     641           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
     642           0 :        !fd_tower_votes_iter_done_rev( tower, iter );
     643           0 :        iter = fd_tower_votes_iter_prev( tower, iter ) ) {
     644             : 
     645           0 :     fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
     646           0 :     printf( "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf );
     647           0 :     max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
     648           0 :   }
     649           0 :   printf( "%*lu | root\n", digit_cnt, root );
     650             :   printf( "\n" );
     651           0 : }

Generated by: LCOV version 1.14