LCOV - code coverage report
Current view: top level - choreo/tower - fd_tower.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 348 0.0 %
Date: 2025-03-20 12:08:36 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_spad_t *           runtime_spad ) {
     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           0 :     FD_SPAD_FRAME_BEGIN( runtime_spad ) {
     238           0 :       void * mem = fd_spad_alloc( runtime_spad, fd_tower_align(), fd_tower_footprint() );
     239           0 :       fd_tower_t * voter_tower = fd_tower_join( fd_tower_new( mem ) );
     240           0 :       fd_tower_from_vote_acc( voter_tower, funk, txn, &voter->rec );
     241             : 
     242             :       /* If this voter has not voted, continue. */
     243             : 
     244           0 :       if( FD_UNLIKELY( fd_tower_votes_empty( voter_tower ) ) ) continue;
     245             : 
     246           0 :       ulong cnt = simulate_vote( voter_tower, slot );
     247             : 
     248             :       /* Continue if their tower is empty after simulating. */
     249             : 
     250           0 :       if( FD_UNLIKELY( !cnt ) ) continue;
     251             : 
     252             :       /* Get their latest vote. */
     253             : 
     254           0 :       fd_tower_vote_t const * vote = fd_tower_votes_peek_index( voter_tower, cnt - 1 );
     255             : 
     256             :       /* Count their stake towards the threshold check if their latest
     257             :          vote slot >= our threshold slot.
     258             : 
     259             :          Because we are iterating vote accounts on the same fork that we
     260             :          we want to vote for, we know these slots must all occur along
     261             :          the same fork ancestry.
     262             : 
     263             :          Therefore, if their latest vote slot >= our threshold slot, we
     264             :          know that vote must be for the threshold slot itself or one of
     265             :          threshold slot's descendants. */
     266             : 
     267           0 :       if( FD_LIKELY( vote->slot >= threshold_slot ) ) {
     268           0 :         threshold_stake += voter->stake;
     269           0 :       }
     270           0 :     } FD_SPAD_FRAME_END;
     271           0 :   }
     272             : 
     273           0 :   double threshold_pct = (double)threshold_stake / (double)epoch->total_stake;
     274           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 ));
     275           0 :   return threshold_pct > THRESHOLD_PCT;
     276           0 : }
     277             : 
     278             : ulong
     279             : fd_tower_reset_slot( fd_tower_t const * tower,
     280             :                      fd_epoch_t const * epoch,
     281           0 :                      fd_ghost_t const * ghost ) {
     282             : 
     283           0 :   fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
     284           0 :   fd_ghost_node_t const * root = fd_ghost_root( ghost );
     285           0 :   fd_ghost_node_t const * head = fd_ghost_head( ghost, root );
     286             : 
     287             :   /* Reset to the ghost head if any of the following is true:
     288             :        1. haven't voted
     289             :        2. last vote < ghost root
     290             :        3. ghost root is not an ancestory of last vote */
     291             : 
     292           0 :   if( FD_UNLIKELY( !vote || vote->slot < root->slot ||
     293           0 :                    !fd_ghost_is_ancestor( ghost, root->slot, vote->slot ) ) ) {
     294           0 :     return head->slot;
     295           0 :   }
     296             : 
     297             :   /* Find the ghost node keyed by our last vote slot. It is invariant
     298             :      that this node must always be found after doing the above check.
     299             :      Otherwise ghost and tower contain implementation bugs and/or are
     300             :      corrupt. */
     301             : 
     302           0 :   fd_ghost_node_t const * vote_node = fd_ghost_query( ghost, vote->slot );
     303           0 :   #if FD_TOWER_USE_HANDHOLDING
     304           0 :   if( FD_UNLIKELY( !vote_node ) ) {
     305           0 :     fd_ghost_print( ghost, epoch, root );
     306           0 :     FD_LOG_ERR(( "[%s] invariant violation: unable to find last tower vote slot %lu in ghost.", __func__, vote->slot ));
     307           0 :   }
     308           0 :   #endif
     309             : 
     310             :   /* Starting from the node keyed by the last vote slot, greedy traverse
     311             :      for the head. */
     312             : 
     313           0 :   return fd_ghost_head( ghost, vote_node )->slot;
     314           0 : }
     315             : 
     316             : ulong
     317             : fd_tower_vote_slot( fd_tower_t *          tower,
     318             :                     fd_epoch_t const *    epoch,
     319             :                     fd_funk_t *           funk,
     320             :                     fd_funk_txn_t const * txn,
     321             :                     fd_ghost_t const *    ghost,
     322           0 :                     fd_spad_t *           runtime_spad ) {
     323             : 
     324           0 :   fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
     325           0 :   fd_ghost_node_t const * root = fd_ghost_root( ghost );
     326           0 :   fd_ghost_node_t const * head = fd_ghost_head( ghost, root );
     327             : 
     328             :   /* Vote for the ghost head if any of the following is true:
     329             :      
     330             :      1. haven't voted
     331             :      2. last vote < ghost root
     332             :      3. ghost root is not an ancestory of last vote
     333             :        
     334             :      FIXME need to ensure lockout safety for case 2 and 3 */
     335             : 
     336           0 :   if( FD_UNLIKELY( !vote || vote->slot < root->slot ||
     337           0 :                    !fd_ghost_is_ancestor( ghost, root->slot, vote->slot ) ) ) {
     338           0 :     return head->slot;
     339           0 :   }
     340             : 
     341             :   /* Optimize for when there is just one fork or that we already
     342             :      previously voted for the best fork. */
     343             : 
     344           0 :   if( FD_LIKELY( fd_ghost_is_ancestor( ghost, vote->slot, head->slot ) ) ) {
     345             : 
     346             :     /* The ghost head is on the same fork as our last vote slot, so we
     347             :        can vote fork it as long as we pass the threshold check. */
     348             : 
     349           0 :     if( FD_LIKELY( fd_tower_threshold_check( tower, epoch, funk, txn, head->slot, runtime_spad ) ) ) {
     350           0 :       FD_LOG_DEBUG(( "[%s] success (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
     351           0 :       return head->slot;
     352           0 :     }
     353           0 :     FD_LOG_DEBUG(( "[%s] failure (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
     354           0 :     return FD_SLOT_NULL; /* can't vote. need to wait for threshold check. */
     355           0 :   }
     356             : 
     357             :   /* The ghost head is on a different fork from our last vote slot, so
     358             :       try to switch if we pass lockout and switch threshold. */
     359             : 
     360           0 :   if( FD_UNLIKELY( fd_tower_lockout_check( tower, ghost, head->slot ) &&
     361           0 :                    fd_tower_switch_check( tower, epoch, ghost, head->slot ) ) ) {
     362           0 :     FD_LOG_DEBUG(( "[%s] success (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
     363           0 :     return head->slot;
     364           0 :   } 
     365           0 :   FD_LOG_DEBUG(( "[%s] failure (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
     366           0 :   return FD_SLOT_NULL;
     367           0 : }
     368             : 
     369             : ulong
     370           0 : fd_tower_vote( fd_tower_t * tower, ulong slot ) {
     371           0 :   FD_LOG_DEBUG(( "[%s] voting for slot %lu", __func__, slot ));
     372             : 
     373           0 :   #if FD_TOWER_USE_HANDHOLDING
     374           0 :   fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
     375           0 :   if( FD_UNLIKELY( vote && slot < vote->slot ) ) FD_LOG_ERR(( "[%s] slot %lu < vote->slot %lu", __func__, slot, vote->slot )); /* caller error*/
     376           0 :   #endif
     377             : 
     378             :   /* Use simulate_vote to determine how many expired votes to pop. */
     379             : 
     380           0 :   ulong cnt = simulate_vote( tower, slot );
     381             : 
     382             :   /* Pop everything that got expired. */
     383             : 
     384           0 :   while( fd_tower_votes_cnt( tower ) > cnt ) {
     385           0 :     fd_tower_votes_pop_tail( tower );
     386           0 :   }
     387             : 
     388             :   /* If the tower is still full after expiring, then pop and return the
     389             :      bottom vote slot as the new root because this vote has incremented
     390             :      it to max lockout.  Otherwise this is a no-op and there is no new
     391             :      root (FD_SLOT_NULL). */
     392             : 
     393           0 :   ulong root = FD_SLOT_NULL;
     394           0 :   if( FD_LIKELY( fd_tower_votes_full( tower ) ) ) { /* optimize for full tower */
     395           0 :     root = fd_tower_votes_pop_head( tower ).slot;
     396           0 :   }
     397             : 
     398             :   /* Increment confirmations (double lockouts) for consecutive
     399             :      confirmations in prior votes. */
     400             : 
     401           0 :   ulong prev_conf = 0;
     402           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
     403           0 :        !fd_tower_votes_iter_done_rev( tower, iter );
     404           0 :        iter = fd_tower_votes_iter_prev( tower, iter ) ) {
     405           0 :     fd_tower_vote_t * vote = fd_tower_votes_iter_ele( tower, iter );
     406           0 :     if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) {
     407           0 :       break;
     408           0 :     }
     409           0 :     vote->conf++;
     410           0 :   }
     411             : 
     412             :   /* Add the new vote to the tower. */
     413             : 
     414           0 :   fd_tower_votes_push_tail( tower, (fd_tower_vote_t){ .slot = slot, .conf = 1 } );
     415             : 
     416             :   /* Return the new root (FD_SLOT_NULL if there is none). */
     417             :   
     418           0 :   return root;
     419           0 : }
     420             : 
     421             : ulong
     422           0 : fd_tower_simulate_vote( fd_tower_t const * tower, ulong slot ) {
     423           0 :   #if FD_TOWER_USE_HANDHOLDING
     424           0 :   FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
     425           0 :   #endif
     426             : 
     427           0 :   return simulate_vote( tower, slot );
     428           0 : }
     429             : 
     430             : void
     431             : fd_tower_from_vote_acc( fd_tower_t *              tower,
     432             :                         fd_funk_t *               funk,
     433             :                         fd_funk_txn_t const *     txn,
     434           0 :                         fd_funk_rec_key_t const * vote_acc ) {
     435           0 :   #if FD_TOWER_USE_HANDHOLDING
     436           0 :   if( FD_UNLIKELY( !fd_tower_votes_empty( tower ) ) ) FD_LOG_ERR(( "[%s] cannot write to non-empty tower", __func__ ));
     437           0 :   #endif
     438             : 
     439           0 :   fd_voter_state_t const * state = fd_voter_state( funk, txn, vote_acc );
     440           0 :   if( FD_UNLIKELY(!state ) ) return;
     441             : 
     442           0 :   fd_tower_vote_t vote = { 0 };
     443           0 :   ulong sz = sizeof(fd_voter_vote_old_t);
     444           0 :   for( ulong i = 0; i < fd_voter_state_cnt( state ); i++ ) {
     445           0 :     if( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_v0_23_5 ) ) {
     446           0 :       memcpy( (uchar *)&vote, (uchar *)(state->v0_23_5.votes + i), sz );
     447           0 :     } else if ( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_v1_14_11 ) ) {
     448           0 :       memcpy( (uchar *)&vote, (uchar *)(state->v1_14_11.votes + i), sz );
     449           0 :     } else if ( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_current ) ) {
     450           0 :       memcpy( (uchar *)&vote, (uchar *)(state->votes + i) + sizeof(uchar) /* latency */, sz );
     451           0 :     } else {
     452           0 :       FD_LOG_ERR(( "[%s] unknown state->discriminant %u", __func__, state->discriminant ));
     453           0 :     }
     454           0 :     fd_tower_votes_push_tail( tower, vote );
     455           0 :   }
     456           0 : }
     457             : 
     458             : void
     459             : fd_tower_to_vote_txn( fd_tower_t const *  tower,
     460             :                       ulong               root,
     461             :                       fd_hash_t const *   bank_hash,
     462             :                       fd_hash_t const *   recent_blockhash,
     463             :                       fd_pubkey_t const * validator_identity,
     464             :                       fd_pubkey_t const * vote_authority,
     465             :                       fd_pubkey_t const * vote_acc,
     466             :                       fd_txn_p_t *        vote_txn,
     467           0 :                       fd_spad_t *         runtime_spad ) {
     468             : 
     469           0 :   FD_SPAD_FRAME_BEGIN( runtime_spad ) {
     470             : 
     471           0 :   fd_compact_vote_state_update_t tower_sync[1] = { 0 };
     472             : 
     473           0 :   tower_sync->root          = root;
     474           0 :   long ts                   = fd_log_wallclock();
     475           0 :   tower_sync->has_timestamp = 1;
     476           0 :   tower_sync->timestamp     = ts;
     477           0 :   tower_sync->lockouts_len  = (ushort)fd_tower_votes_cnt( tower );
     478           0 :   tower_sync->lockouts      = (fd_lockout_offset_t *)fd_spad_alloc( runtime_spad, alignof(fd_lockout_offset_t),tower_sync->lockouts_len * sizeof(fd_lockout_offset_t) );
     479             : 
     480           0 :   ulong i         = 0UL;
     481           0 :   ulong curr_slot = tower_sync->root;
     482             : 
     483           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
     484           0 :        !fd_tower_votes_iter_done( tower, iter );
     485           0 :        iter = fd_tower_votes_iter_next( tower, iter ) ) {
     486           0 :     fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
     487           0 :     FD_TEST( vote->slot >= tower_sync->root );
     488           0 :     ulong offset                               = vote->slot - curr_slot;
     489           0 :     curr_slot                                  = vote->slot;
     490           0 :     uchar conf                                 = (uchar)vote->conf;
     491           0 :     tower_sync->lockouts[i].offset             = offset;
     492           0 :     tower_sync->lockouts[i].confirmation_count = conf;
     493           0 :     memcpy( tower_sync->hash.uc, bank_hash, sizeof(fd_hash_t) );
     494           0 :     i++;
     495           0 :   }
     496             : 
     497           0 :   uchar * txn_out = vote_txn->payload;
     498           0 :   uchar * txn_meta_out = vote_txn->_;
     499             : 
     500           0 :   int same_addr = !memcmp( validator_identity, vote_authority, sizeof(fd_pubkey_t) );
     501           0 :   if( FD_LIKELY( same_addr ) ) {
     502             : 
     503             :     /* 0: validator identity
     504             :        1: vote account address
     505             :        2: vote program */
     506             : 
     507           0 :     fd_txn_accounts_t accts;
     508           0 :     accts.signature_cnt         = 1;
     509           0 :     accts.readonly_signed_cnt   = 0;
     510           0 :     accts.readonly_unsigned_cnt = 1;
     511           0 :     accts.acct_cnt              = 3;
     512           0 :     accts.signers_w             = validator_identity;
     513           0 :     accts.signers_r             = NULL;
     514           0 :     accts.non_signers_w         = vote_acc;
     515           0 :     accts.non_signers_r         = &fd_solana_vote_program_id;
     516           0 :     FD_TEST( fd_txn_base_generate( txn_meta_out,
     517           0 :                                    txn_out,
     518           0 :                                    accts.signature_cnt,
     519           0 :                                    &accts,
     520           0 :                                    recent_blockhash->uc ) );
     521           0 :   } else {
     522             : 
     523             :     /* 0: validator identity
     524             :        1: vote authority
     525             :        2: vote account address
     526             :        3: vote program */
     527             : 
     528           0 :     fd_txn_accounts_t accts;
     529           0 :     accts.signature_cnt         = 2;
     530           0 :     accts.readonly_signed_cnt   = 1;
     531           0 :     accts.readonly_unsigned_cnt = 1;
     532           0 :     accts.acct_cnt              = 4;
     533           0 :     accts.signers_w             = validator_identity;
     534           0 :     accts.signers_r             = vote_authority;
     535           0 :     accts.non_signers_w         = vote_acc;
     536           0 :     accts.non_signers_r         = &fd_solana_vote_program_id;
     537           0 :     FD_TEST( fd_txn_base_generate( txn_meta_out,
     538           0 :                                    txn_out,
     539           0 :                                    accts.signature_cnt,
     540           0 :                                    &accts,
     541           0 :                                    recent_blockhash->uc ) );
     542           0 :   }
     543             : 
     544             :   /* Add the vote instruction to the transaction. */
     545             : 
     546           0 :   fd_vote_instruction_t vote_ix;
     547           0 :   uchar                 vote_ix_buf[FD_TXN_MTU];
     548           0 :   vote_ix.discriminant                    = fd_vote_instruction_enum_compact_update_vote_state;
     549           0 :   vote_ix.inner.compact_update_vote_state = *tower_sync;
     550           0 :   fd_bincode_encode_ctx_t encode = { .data = vote_ix_buf, .dataend = ( vote_ix_buf + FD_TXN_MTU ) };
     551           0 :   fd_vote_instruction_encode( &vote_ix, &encode );
     552           0 :   ushort vote_ix_size = (ushort)fd_vote_instruction_size( &vote_ix );
     553             : 
     554           0 :   uchar ix_accs[2];
     555           0 :   uchar program_id;
     556           0 :   if( FD_LIKELY( same_addr ) ) {
     557           0 :     ix_accs[0] = 1; /* vote account address */
     558           0 :     ix_accs[1] = 0; /* vote authority */
     559           0 :     program_id = 2; /* vote program */
     560           0 :   } else {
     561           0 :     ix_accs[0] = 2; /* vote account address */
     562           0 :     ix_accs[1] = 1; /* vote authority */
     563           0 :     program_id = 3; /* vote program */
     564           0 :   }
     565           0 :   vote_txn->payload_sz = fd_txn_add_instr( txn_meta_out, txn_out, program_id, ix_accs, 2, vote_ix_buf, vote_ix_size );
     566             : 
     567           0 :   } FD_SPAD_FRAME_END;
     568           0 : }
     569             : 
     570             : int
     571           0 : fd_tower_verify( fd_tower_t const * tower ) {
     572           0 :   fd_tower_vote_t const * prev = NULL;
     573           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
     574           0 :        !fd_tower_votes_iter_done( tower, iter );
     575           0 :        iter = fd_tower_votes_iter_next( tower, iter ) ) {
     576           0 :     fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
     577           0 :     if( FD_LIKELY( prev && !( vote->slot < prev->slot && vote->conf < prev->conf ) ) ) {
     578           0 :       FD_LOG_WARNING(( "[%s] invariant violation: vote %lu %lu. prev %lu %lu", __func__, vote->slot, vote->conf, prev->slot, prev->conf ));
     579           0 :       return -1;
     580           0 :     }
     581           0 :     prev = vote;
     582           0 :   }
     583           0 :   return 0;
     584           0 : }
     585             : 
     586             : #include <stdio.h>
     587             : 
     588             : void
     589           0 : fd_tower_print( fd_tower_t const * tower, ulong root ) {
     590           0 :   FD_LOG_NOTICE( ( "\n\n[Tower]" ) );
     591           0 :   ulong max_slot = 0;
     592             : 
     593             :   /* Determine spacing. */
     594             : 
     595           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
     596           0 :        !fd_tower_votes_iter_done_rev( tower, iter );
     597           0 :        iter = fd_tower_votes_iter_prev( tower, iter ) ) {
     598             : 
     599           0 :     max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
     600           0 :   }
     601             : 
     602             :   /* Calculate the number of digits in the maximum slot value. */
     603             : 
     604           0 :   int           digit_cnt = 0;
     605           0 :   unsigned long rem       = max_slot;
     606           0 :   do {
     607           0 :     rem /= 10;
     608           0 :     ++digit_cnt;
     609           0 :   } while( rem > 0 );
     610             : 
     611             :   /* Print the table header */
     612             : 
     613           0 :   printf( "slot%*s | %s\n", digit_cnt - (int)strlen("slot"), "", "confirmation count" );
     614             : 
     615             :   /* Print the divider line */
     616             : 
     617           0 :   for( int i = 0; i < digit_cnt; i++ ) {
     618           0 :     printf( "-" );
     619           0 :   }
     620           0 :   printf( " | " );
     621           0 :   for( ulong i = 0; i < strlen( "confirmation count" ); i++ ) {
     622           0 :     printf( "-" );
     623           0 :   }
     624           0 :   printf( "\n" );
     625             : 
     626             :   /* Print each record in the table */
     627             : 
     628           0 :   for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
     629           0 :        !fd_tower_votes_iter_done_rev( tower, iter );
     630           0 :        iter = fd_tower_votes_iter_prev( tower, iter ) ) {
     631             : 
     632           0 :     fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
     633           0 :     printf( "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf );
     634           0 :     max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
     635           0 :   }
     636           0 :   printf( "%*lu | root\n", digit_cnt, root );
     637           0 :   printf( "\n" );
     638           0 : }

Generated by: LCOV version 1.14