LCOV - code coverage report
Current view: top level - choreo/tower - fd_tower.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 345 0.0 %
Date: 2025-07-01 05:00:49 Functions: 0 17 0.0 %

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

Generated by: LCOV version 1.14