LCOV - code coverage report
Current view: top level - disco/shred - fd_stake_ci.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 260 271 95.9 %
Date: 2025-09-19 04:41:14 Functions: 21 21 100.0 %

          Line data    Source code
       1             : #include "fd_stake_ci.h"
       2             : #include "fd_shred_dest.h"
       3             : #include "../../util/net/fd_ip4.h" /* Just for debug */
       4             : 
       5             : #define SORT_NAME sort_pubkey
       6     3472818 : #define SORT_KEY_T fd_shred_dest_weighted_t
       7     5511192 : #define SORT_BEFORE(a,b) (memcmp( (a).pubkey.uc, (b).pubkey.uc, 32UL )<0)
       8             : #include "../../util/tmpl/fd_sort.c"
       9             : 
      10             : #define SORT_NAME sort_weights_by_stake_id
      11     6727038 : #define SORT_KEY_T fd_stake_weight_t
      12    10779579 : #define SORT_BEFORE(a,b) ((a).stake > (b).stake ? 1 : ((a).stake < (b).stake ? 0 : memcmp( (a).key.uc, (b).key.uc, 32UL )>0))
      13             : #include "../../util/tmpl/fd_sort.c"
      14             : 
      15             : #define SORT_NAME sort_weights_by_id
      16     6337950 : #define SORT_KEY_T fd_stake_weight_t
      17    10109631 : #define SORT_BEFORE(a,b) (memcmp( (a).key.uc, (b).key.uc, 32UL )>0)
      18             : #include "../../util/tmpl/fd_sort.c"
      19             : 
      20             : /* We don't have or need real contact info for the local validator, but
      21             :    we want to be able to distinguish it from staked nodes with no
      22             :    contact info. */
      23         156 : #define SELF_DUMMY_IP 1U
      24             : 
      25             : void *
      26             : fd_stake_ci_new( void             * mem,
      27          45 :                 fd_pubkey_t const * identity_key ) {
      28          45 :   fd_stake_ci_t * info = (fd_stake_ci_t *)mem;
      29             : 
      30          45 :   fd_vote_stake_weight_t dummy_stakes[ 1 ] = {{ .vote_key = {{0}}, .id_key = {{0}}, .stake = 1UL }};
      31          45 :   fd_shred_dest_weighted_t dummy_dests[ 1 ] = {{ .pubkey = *identity_key, .ip4 = SELF_DUMMY_IP }};
      32             : 
      33             :   /* Initialize first 2 to satisfy invariants */
      34          45 :   info->vote_stake_weight[ 0 ] = dummy_stakes[ 0 ];
      35          45 :   info->shred_dest  [ 0 ] = dummy_dests [ 0 ];
      36         135 :   for( ulong i=0UL; i<2UL; i++ ) {
      37          90 :     fd_per_epoch_info_t * ei = info->epoch_info + i;
      38          90 :     ei->epoch          = i;
      39          90 :     ei->start_slot     = 0UL;
      40          90 :     ei->slot_cnt       = 0UL;
      41          90 :     ei->excluded_stake = 0UL;
      42          90 :     ei->vote_keyed_lsched = 0UL;
      43             : 
      44          90 :     ei->lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( ei->_lsched, 0UL, 0UL, 1UL, 1UL,    info->vote_stake_weight,  0UL, ei->vote_keyed_lsched ) );
      45          90 :     ei->sdest  = fd_shred_dest_join   ( fd_shred_dest_new   ( ei->_sdest,  info->shred_dest, 1UL, ei->lsched, identity_key, 0UL ) );
      46          90 :   }
      47          45 :   info->identity_key[ 0 ] = *identity_key;
      48             : 
      49          45 :   return (void *)info;
      50          45 : }
      51             : 
      52          45 : fd_stake_ci_t * fd_stake_ci_join( void * mem ) { return (fd_stake_ci_t *)mem; }
      53             : 
      54          42 : void * fd_stake_ci_leave ( fd_stake_ci_t * info ) { return (void *)info; }
      55          42 : void * fd_stake_ci_delete( void          * mem  ) { return mem;          }
      56             : 
      57             : 
      58             : void
      59             : fd_stake_ci_stake_msg_init( fd_stake_ci_t               * info,
      60         120 :                             fd_stake_weight_msg_t const * msg ) {
      61         120 :   if( FD_UNLIKELY( msg->staked_cnt > MAX_SHRED_DESTS ) )
      62           0 :     FD_LOG_ERR(( "The stakes -> Firedancer splice sent a malformed update with %lu stakes in it,"
      63         120 :                  " but the maximum allowed is %lu", msg->staked_cnt, MAX_SHRED_DESTS ));
      64             : 
      65         120 :   info->scratch->epoch          = msg->epoch;
      66         120 :   info->scratch->start_slot     = msg->start_slot;
      67         120 :   info->scratch->slot_cnt       = msg->slot_cnt;
      68         120 :   info->scratch->staked_cnt     = msg->staked_cnt;
      69         120 :   info->scratch->excluded_stake = msg->excluded_stake;
      70         120 :   info->scratch->vote_keyed_lsched = msg->vote_keyed_lsched;
      71             : 
      72         120 :   fd_memcpy( info->vote_stake_weight, msg->weights, msg->staked_cnt*sizeof(fd_vote_stake_weight_t) );
      73         120 : }
      74             : 
      75             : static inline void
      76         204 : log_summary( char const * msg, fd_stake_ci_t * info ) {
      77             : #if 0
      78             :   fd_per_epoch_info_t const * ei = info->epoch_info;
      79             :   FD_LOG_NOTICE(( "Dumping stake contact information because %s", msg ));
      80             :   for( ulong i=0UL; i<2UL; i++ ) {
      81             :     FD_LOG_NOTICE(( "  Dumping shred destination details for epoch %lu, slots [%lu, %lu)", ei[i].epoch, ei[i].start_slot, ei[i].start_slot+ei[i].slot_cnt ));
      82             :     fd_shred_dest_t * sdest = ei[i].sdest;
      83             :     for( fd_shred_dest_idx_t j=0; j<(fd_shred_dest_idx_t)fd_shred_dest_cnt_all( sdest ); j++ ) {
      84             :       fd_shred_dest_weighted_t * dest = fd_shred_dest_idx_to_dest( sdest, j );
      85             :       FD_LOG_NOTICE(( "    %16lx  %20lu " FD_IP4_ADDR_FMT " %hu ", *(ulong *)dest->pubkey.uc, dest->stake_lamports, FD_IP4_ADDR_FMT_ARGS( dest->ip4 ), dest->port ));
      86             :     }
      87             :   }
      88             : #else
      89         204 :   (void)msg;
      90         204 :   (void)info;
      91         204 : #endif
      92         204 : }
      93             : 
      94             : ulong
      95             : compute_id_weights_from_vote_weights( fd_stake_weight_t *            stake_weight,
      96             :                                       fd_vote_stake_weight_t const * vote_stake_weight,
      97         117 :                                       ulong                          staked_cnt ) {
      98             :   /* Copy from input message [(vote, id, stake)] into old format [(id, stake)]. */
      99      482841 :   for( ulong i=0UL; i<staked_cnt; i++ ) {
     100      482724 :     memcpy( stake_weight[ i ].key.uc, vote_stake_weight[ i ].id_key.uc, sizeof(fd_pubkey_t) );
     101      482724 :     stake_weight[ i ].stake = vote_stake_weight[ i ].stake;
     102      482724 :   }
     103             : 
     104             :   /* Sort [(id, stake)] by id, so we can dedup */
     105         117 :   sort_weights_by_id_inplace( stake_weight, staked_cnt );
     106             : 
     107             :   /* Dedup entries, aggregating stake */
     108         117 :   ulong j=0UL;
     109      482724 :   for( ulong i=1UL; i<staked_cnt; i++ ) {
     110      482607 :     fd_pubkey_t * pre = &stake_weight[ j ].key;
     111      482607 :     fd_pubkey_t * cur = &stake_weight[ i ].key;
     112      482607 :     if( 0==memcmp( pre, cur, sizeof(fd_pubkey_t) ) ) {
     113          30 :       stake_weight[ j ].stake += stake_weight[ i ].stake;
     114      482577 :     } else {
     115      482577 :       ++j;
     116      482577 :       stake_weight[ j ].stake = stake_weight[ i ].stake;
     117      482577 :       memcpy( stake_weight[ j ].key.uc, stake_weight[ i ].key.uc, sizeof(fd_pubkey_t) );
     118      482577 :     }
     119      482607 :   }
     120         117 :   ulong staked_cnt_by_id = fd_ulong_min( staked_cnt, j+1 );
     121             : 
     122             :   /* Sort [(id, stake)] by stake then id, as expected */
     123         117 :   sort_weights_by_stake_id_inplace( stake_weight, staked_cnt_by_id );
     124             : 
     125         117 :   return staked_cnt_by_id;
     126         117 : }
     127             : 
     128             : #define SET_NAME unhit_set
     129         117 : #define SET_MAX  MAX_SHRED_DESTS
     130             : #include "../../util/tmpl/fd_set.c"
     131             : 
     132             : void
     133         117 : fd_stake_ci_stake_msg_fini( fd_stake_ci_t * info ) {
     134             :   /* The grossness here is a sign our abstractions are wrong and need to
     135             :      be fixed instead of just patched.  We need to generate weighted
     136             :      shred destinations using a combination of the new stake information
     137             :      and whatever contact info we previously knew. */
     138         117 :   ulong epoch                  = info->scratch->epoch;
     139         117 :   ulong staked_cnt             = info->scratch->staked_cnt;
     140         117 :   ulong unchanged_staked_cnt   = info->scratch->staked_cnt;
     141         117 :   ulong vote_keyed_lsched      = info->scratch->vote_keyed_lsched;
     142             : 
     143             :   /* Just take the first one arbitrarily because they both have the same
     144             :      contact info, other than possibly some staked nodes with no contact
     145             :      info. */
     146         117 :   fd_shred_dest_t * existing_sdest    = info->epoch_info->sdest;
     147         117 :   ulong             existing_dest_cnt = fd_shred_dest_cnt_all( existing_sdest );
     148             : 
     149             :   /* Keep track of the destinations in existing_sdest that are not
     150             :      staked in this new epoch, i.e. the ones we don't hit in the loop
     151             :      below. */
     152         117 :   unhit_set_t _unhit[ unhit_set_word_cnt ];
     153             :   /* This memsets to 0, right before we memset to 1, and is probably
     154             :      unnecessary, but using it without joining seems like a hack. */
     155         117 :   unhit_set_t * unhit = unhit_set_join( unhit_set_new( _unhit ) );
     156         117 :   unhit_set_full( unhit );
     157             : 
     158         117 :   staked_cnt = compute_id_weights_from_vote_weights( info->stake_weight, info->vote_stake_weight, staked_cnt );
     159             : 
     160      482811 :   for( ulong i=0UL; i<staked_cnt; i++ ) {
     161      482694 :     fd_shred_dest_idx_t old_idx = fd_shred_dest_pubkey_to_idx( existing_sdest, &(info->stake_weight[ i ].key) );
     162      482694 :     fd_shred_dest_weighted_t * in_prev = fd_shred_dest_idx_to_dest( existing_sdest, old_idx );
     163      482694 :     info->shred_dest[ i ] = *in_prev;
     164      482694 :     if( FD_UNLIKELY( old_idx==FD_SHRED_DEST_NO_DEST ) ) {
     165             :       /* We got the generic empty entry, so fixup the pubkey */
     166      120747 :       info->shred_dest[ i ].pubkey = info->stake_weight[ i ].key;
     167      361947 :     } else {
     168      361947 :       unhit_set_remove( unhit, old_idx );
     169      361947 :     }
     170      482694 :     info->shred_dest[ i ].stake_lamports = info->stake_weight[ i ].stake;
     171      482694 :   }
     172             : 
     173         117 :   int any_destaked = 0;
     174         117 :   ulong j = staked_cnt;
     175         354 :   for( ulong idx=unhit_set_iter_init( unhit ); (idx<existing_dest_cnt) & (!unhit_set_iter_done( idx )) & (j<MAX_SHRED_DESTS);
     176         237 :              idx=unhit_set_iter_next( unhit, idx ) ) {
     177         237 :     fd_shred_dest_weighted_t * in_prev = fd_shred_dest_idx_to_dest( existing_sdest, (fd_shred_dest_idx_t)idx );
     178         237 :     if( FD_LIKELY( in_prev->ip4 ) ) {
     179         189 :       info->shred_dest[ j ] = *in_prev;
     180         189 :       any_destaked |= (in_prev->stake_lamports > 0UL);
     181         189 :       info->shred_dest[ j ].stake_lamports = 0UL;
     182         189 :       j++;
     183         189 :     }
     184         237 :   }
     185             : 
     186         117 :   unhit_set_delete( unhit_set_leave( unhit ) );
     187             : 
     188         117 :   if( FD_UNLIKELY( any_destaked ) ) {
     189             :     /* The unstaked list might be a little out of order because the
     190             :        destinations that were previously staked will be at the start of
     191             :        the unstaked list, sorted by their previous stake, instead of
     192             :        where they should be.  If there weren't any destaked, then the
     193             :        only unstaked nodes come from the previous list, which we know
     194             :        was in order, perhaps skipping some, which doesn't ruin the
     195             :        order. */
     196          24 :     sort_pubkey_inplace( info->shred_dest + staked_cnt, j - staked_cnt );
     197          24 :   }
     198             : 
     199             :   /* Now we have a plausible shred_dest list. */
     200             : 
     201             :   /* Clear the existing info */
     202         117 :   fd_per_epoch_info_t * new_ei = info->epoch_info + (epoch % 2UL);
     203         117 :   fd_shred_dest_delete   ( fd_shred_dest_leave   ( new_ei->sdest  ) );
     204         117 :   fd_epoch_leaders_delete( fd_epoch_leaders_leave( new_ei->lsched ) );
     205             : 
     206             :   /* And create the new one */
     207         117 :   ulong excluded_stake = info->scratch->excluded_stake;
     208             : 
     209         117 :   new_ei->epoch          = epoch;
     210         117 :   new_ei->start_slot     = info->scratch->start_slot;
     211         117 :   new_ei->slot_cnt       = info->scratch->slot_cnt;
     212         117 :   new_ei->excluded_stake = excluded_stake;
     213         117 :   new_ei->vote_keyed_lsched = vote_keyed_lsched;
     214             : 
     215         117 :   new_ei->lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( new_ei->_lsched, epoch, new_ei->start_slot, new_ei->slot_cnt,
     216         117 :                                                                 unchanged_staked_cnt, info->vote_stake_weight, excluded_stake, vote_keyed_lsched ) );
     217         117 :   new_ei->sdest  = fd_shred_dest_join   ( fd_shred_dest_new   ( new_ei->_sdest, info->shred_dest, j,
     218         117 :                                                                 new_ei->lsched, info->identity_key,  excluded_stake ) );
     219         117 :   log_summary( "stake update", info );
     220         117 : }
     221             : 
     222          90 : fd_shred_dest_weighted_t * fd_stake_ci_dest_add_init( fd_stake_ci_t * info ) { return info->shred_dest; }
     223             : 
     224             : static inline void
     225             : fd_stake_ci_dest_add_fini_impl( fd_stake_ci_t       * info,
     226             :                                 ulong                 cnt,
     227         174 :                                 fd_per_epoch_info_t * ei ) {
     228             :   /* Initially we start with one list containing S+U staked and unstaked
     229             :      destinations jumbled together.  In order to update sdest, we need
     230             :      to convert the list to S' staked destinations (taken from the
     231             :      existing sdest, though possibly updated) followed by U unstaked
     232             :      destinations.
     233             : 
     234             :      It's possible to do this in place, but at a cost of additional
     235             :      complexity (similar to memcpy vs memmove).  Rather than do that, we
     236             :      build the combined list in shred_dest_temp. */
     237             : 
     238         174 :   ulong found_unstaked_cnt = 0UL;
     239         174 :   int   any_new_unstaked   = 0;
     240             : 
     241         174 :   ulong const staked_cnt = fd_shred_dest_cnt_staked( ei->sdest );
     242         174 :   ulong j = staked_cnt;
     243             : 
     244     3859698 :   for( ulong i=0UL; i<cnt; i++ ) {
     245     3859524 :     fd_shred_dest_idx_t idx = fd_shred_dest_pubkey_to_idx( ei->sdest, &(info->shred_dest[ i ].pubkey) );
     246     3859524 :     fd_shred_dest_weighted_t * dest = fd_shred_dest_idx_to_dest( ei->sdest, idx );
     247     3859524 :     if( FD_UNLIKELY( (dest->stake_lamports==0UL)&(j<MAX_SHRED_DESTS) ) ) {
     248             :       /* Copy this destination to the unstaked part of the new list.
     249             :          This also handles the new unstaked case */
     250      482760 :       info->shred_dest_temp[ j ] = info->shred_dest[ i ];
     251      482760 :       info->shred_dest_temp[ j ].stake_lamports = 0UL;
     252      482760 :       j++;
     253      482760 :     }
     254             : 
     255     3859524 :     if( FD_LIKELY( idx!=FD_SHRED_DEST_NO_DEST ) ) {
     256     3738693 :       dest->ip4  = info->shred_dest[ i ].ip4;
     257     3738693 :       dest->port = info->shred_dest[ i ].port;
     258     3738693 :     }
     259             : 
     260     3859524 :     any_new_unstaked   |= (idx==FD_SHRED_DEST_NO_DEST);
     261     3859524 :     found_unstaked_cnt += (ulong)((idx!=FD_SHRED_DEST_NO_DEST) & (dest->stake_lamports==0UL));
     262     3859524 :   }
     263             : 
     264         174 :   if( FD_LIKELY( !any_new_unstaked && found_unstaked_cnt==fd_shred_dest_cnt_unstaked( ei->sdest ) ) ) {
     265             :     /* Because any_new_unstaked==0, the set of unstaked nodes in this
     266             :        update is fully contained in the set of unstaked nodes in the
     267             :        sdest.  Then additionally, because the sets are the same size,
     268             :        they must actually be equal.  In this case, we've already updated
     269             :        the existing shred_dest_weighted with the newest contact info we
     270             :        have, so there's nothing else to do. */
     271          48 :     return;
     272          48 :   }
     273             : 
     274             :   /* Otherwise something more significant changed and we need to
     275             :      regenerate the sdest.  At this point, elements [staked_cnt, j) now
     276             :      contain all the current unstaked destinations. */
     277             : 
     278             :   /* Copy staked nodes to [0, staked_cnt). We've already applied the
     279             :      updated contact info to these. */
     280     1929831 :   for( ulong i=0UL; i<staked_cnt; i++ )
     281     1929705 :     info->shred_dest_temp[ i ] = *fd_shred_dest_idx_to_dest( ei->sdest, (fd_shred_dest_idx_t)i );
     282             : 
     283             :   /* The staked nodes are sorted properly because we use the index from
     284             :      sdest.  We need to sort the unstaked nodes by pubkey though. */
     285         126 :   sort_pubkey_inplace( info->shred_dest_temp + staked_cnt, j - staked_cnt );
     286             : 
     287         126 :   fd_shred_dest_delete( fd_shred_dest_leave( ei->sdest ) );
     288             : 
     289         126 :   ei->sdest  = fd_shred_dest_join( fd_shred_dest_new( ei->_sdest, info->shred_dest_temp, j, ei->lsched, info->identity_key,
     290         126 :                                                       ei->excluded_stake ) );
     291             : 
     292         126 :   if( FD_UNLIKELY( ei->sdest==NULL ) ) {
     293             :     /* Happens if the identity key is not present, which can only happen
     294             :        if the current validator's stake is not in the top 40,200.  We
     295             :        could initialize ei->sdest to a dummy value, but having the wrong
     296             :        stake weights could lead to potentially slashable issues
     297             :        elsewhere (e.g. we might product a block when we're not actually
     298             :        leader).  We're just going to terminate in this case. */
     299           0 :     FD_LOG_ERR(( "Too many validators have higher stake than this validator.  Cannot continue." ));
     300           0 :   }
     301         126 : }
     302             : 
     303             : 
     304             : void
     305             : fd_stake_ci_dest_add_fini( fd_stake_ci_t * info,
     306          87 :                            ulong           cnt ) {
     307             :   /* The Rust side uses tvu_peers which typically excludes the local
     308             :      validator.  In some cases, after a set-identity, it might still
     309             :      include the local validator though.  If it doesn't include it, we
     310             :      need to add the local validator back. */
     311          87 :   FD_TEST( cnt<MAX_SHRED_DESTS );
     312          87 :   ulong i=0UL;
     313     1929762 :   for(; i<cnt; i++ ) if( FD_UNLIKELY( 0==memcmp( info->shred_dest[ i ].pubkey.uc, info->identity_key, 32UL ) ) ) break;
     314             : 
     315          87 :   if( FD_LIKELY( i==cnt ) ) {
     316          87 :     fd_shred_dest_weighted_t self_dests = { .pubkey = info->identity_key[ 0 ], .ip4 = SELF_DUMMY_IP };
     317          87 :     info->shred_dest[ cnt++ ] = self_dests;
     318          87 :   } else {
     319           0 :     info->shred_dest[ i ].ip4 = SELF_DUMMY_IP;
     320           0 :   }
     321             : 
     322             :   /* Update both of them */
     323          87 :   fd_stake_ci_dest_add_fini_impl( info, cnt, info->epoch_info + 0UL );
     324          87 :   fd_stake_ci_dest_add_fini_impl( info, cnt, info->epoch_info + 1UL );
     325             : 
     326          87 :   log_summary( "dest update", info );
     327          87 : }
     328             : 
     329             : 
     330             : /* Returns a value in [0, 2) if found, and ULONG_MAX if not */
     331             : static inline ulong
     332             : fd_stake_ci_get_idx_for_slot( fd_stake_ci_t const * info,
     333        1557 :                               ulong                 slot ) {
     334        1557 :   fd_per_epoch_info_t const * ei = info->epoch_info;
     335        1557 :   ulong idx = ULONG_MAX;
     336        4671 :   for( ulong i=0UL; i<2UL; i++ ) idx = fd_ulong_if( (ei[i].start_slot<=slot) & (slot-ei[i].start_slot<ei[i].slot_cnt), i, idx );
     337        1557 :   return idx;
     338        1557 : }
     339             : 
     340             : 
     341             : void
     342             : fd_stake_ci_set_identity( fd_stake_ci_t *     info,
     343          12 :                           fd_pubkey_t const * identity_key ) {
     344             :   /* None of the stakes are changing, so we just need to regenerate the
     345             :      sdests, sightly adjusting the destination IP addresses.  The only
     346             :      corner case is if the new identity is not present. */
     347          36 :   for( ulong i=0UL; i<2UL; i++ ) {
     348          24 :     fd_per_epoch_info_t * ei = info->epoch_info+i;
     349             : 
     350          24 :     fd_shred_dest_idx_t old_idx = fd_shred_dest_pubkey_to_idx( ei->sdest, info->identity_key );
     351          24 :     fd_shred_dest_idx_t new_idx = fd_shred_dest_pubkey_to_idx( ei->sdest, identity_key       );
     352             : 
     353          24 :     FD_TEST( old_idx!=FD_SHRED_DEST_NO_DEST );
     354             : 
     355          24 :     if( FD_LIKELY( new_idx!=FD_SHRED_DEST_NO_DEST ) ) {
     356          18 :       fd_shred_dest_idx_to_dest( ei->sdest, old_idx )->ip4 = 0U;
     357          18 :       fd_shred_dest_idx_to_dest( ei->sdest, new_idx )->ip4 = SELF_DUMMY_IP;
     358             : 
     359          18 :       fd_shred_dest_update_source( ei->sdest, new_idx );
     360          18 :     } else {
     361           6 :       ulong staked_cnt   = fd_shred_dest_cnt_staked  ( ei->sdest );
     362           6 :       ulong unstaked_cnt = fd_shred_dest_cnt_unstaked( ei->sdest );
     363           6 :       if( FD_UNLIKELY( staked_cnt+unstaked_cnt==MAX_SHRED_DESTS ) ) {
     364           0 :         FD_LOG_ERR(( "too many validators in shred table to add a new validator with set-identity" ));
     365           0 :       }
     366             :       /* We'll add identity_key as a new unstaked validator.  First copy
     367             :          all the staked ones, then place the new validator in the spot
     368             :          where it belongs according to lexicographic order. */
     369           6 :       ulong j=0UL;
     370          24 :       for(; j<staked_cnt; j++ ) info->shred_dest_temp[ j ] = *fd_shred_dest_idx_to_dest( ei->sdest, (fd_shred_dest_idx_t)j );
     371          33 :       for(; j<staked_cnt+unstaked_cnt; j++ ) {
     372          33 :         fd_shred_dest_weighted_t * wj = fd_shred_dest_idx_to_dest( ei->sdest, (fd_shred_dest_idx_t)j );
     373          33 :         if( FD_UNLIKELY( (memcmp( wj->pubkey.uc, identity_key->uc, 32UL )>=0) ) ) break;
     374          27 :         info->shred_dest_temp[ j ] = *wj;
     375          27 :       }
     376             : 
     377           6 :       info->shred_dest_temp[ j ].pubkey         = *identity_key;
     378           6 :       info->shred_dest_temp[ j ].stake_lamports = 0UL;
     379           6 :       info->shred_dest_temp[ j ].ip4            = SELF_DUMMY_IP;
     380             : 
     381          12 :       for(; j<staked_cnt+unstaked_cnt; j++ ) info->shred_dest_temp[ j+1UL ] = *fd_shred_dest_idx_to_dest( ei->sdest, (fd_shred_dest_idx_t)j );
     382             : 
     383           6 :       fd_shred_dest_delete( fd_shred_dest_leave( ei->sdest ) );
     384             : 
     385           6 :       ei->sdest  = fd_shred_dest_join( fd_shred_dest_new( ei->_sdest, info->shred_dest_temp, j+1UL, ei->lsched, identity_key,
     386           6 :                                                           ei->excluded_stake ) );
     387           6 :       FD_TEST( ei->sdest );
     388           6 :     }
     389             : 
     390          24 :   }
     391          12 :   *info->identity_key = *identity_key;
     392          12 : }
     393             : 
     394             : void
     395             : refresh_sdest( fd_stake_ci_t *            info,
     396             :                fd_shred_dest_weighted_t * shred_dest_temp,
     397             :                ulong                      cnt,
     398             :                ulong                      staked_cnt,
     399          78 :                fd_per_epoch_info_t *      ei ) {
     400          78 :   sort_pubkey_inplace( shred_dest_temp + staked_cnt, cnt - staked_cnt );
     401             : 
     402          78 :   fd_shred_dest_delete( fd_shred_dest_leave( ei->sdest ) );
     403          78 :   ei->sdest = fd_shred_dest_join( fd_shred_dest_new( ei->_sdest, shred_dest_temp, cnt, ei->lsched, info->identity_key, ei->excluded_stake ) );
     404          78 :   if( FD_UNLIKELY( ei->sdest==NULL ) ) {
     405           0 :     FD_LOG_ERR(( "Too many validators have higher stake than this validator.  Cannot continue." ));
     406           0 :   }
     407          78 : }
     408             : 
     409             : void
     410             : ci_dest_add_one_unstaked( fd_stake_ci_t *            info,
     411             :                           fd_shred_dest_weighted_t * new_entry,
     412          57 :                           fd_per_epoch_info_t *      ei ) {
     413          57 :   if( fd_shred_dest_cnt_all( ei->sdest )>=MAX_SHRED_DESTS ) {
     414           0 :     FD_LOG_WARNING(( "Too many validators in shred table to add a new validator." ));
     415           0 :   }
     416          57 :   ulong cur_cnt = fd_shred_dest_cnt_all( ei->sdest );
     417         300 :   for( ulong i=0UL; i<cur_cnt; i++ ) {
     418         243 :     info->shred_dest_temp[ i ] = *fd_shred_dest_idx_to_dest( ei->sdest, (fd_shred_dest_idx_t)i );
     419         243 :   }
     420             : 
     421             :   /* TODO: Alternative batched copy using memcpy. Check with Philip if safe */
     422             :   // fd_shred_dest_weighted_t * cur_dest = ei->sdest->all_destinations;
     423             :   // fd_memcpy( info->shred_dest_temp, cur_dest, sizeof(fd_shred_dest_weighted_t)*cur_cnt );
     424          57 :   info->shred_dest_temp[ cur_cnt++ ] = *new_entry;
     425          57 :   refresh_sdest( info, info->shred_dest_temp, cur_cnt, fd_shred_dest_cnt_staked( ei->sdest ), ei );
     426          57 : }
     427             : 
     428             : void
     429             : ci_dest_update_impl( fd_stake_ci_t *       info,
     430             :                      fd_pubkey_t const *   pubkey,
     431             :                      uint                  ip4,
     432             :                      ushort                port,
     433          90 :                      fd_per_epoch_info_t * ei ) {
     434          90 :   fd_shred_dest_idx_t idx = fd_shred_dest_pubkey_to_idx( ei->sdest, pubkey );
     435          90 :   if( idx==FD_SHRED_DEST_NO_DEST ) {
     436          57 :     fd_shred_dest_weighted_t new_entry = { .pubkey = *pubkey, .ip4 = ip4, .port = port, .stake_lamports = 0UL };
     437          57 :     ci_dest_add_one_unstaked( info, &new_entry, ei );
     438          57 :     return;
     439          57 :   }
     440          33 :   fd_shred_dest_weighted_t * dest = fd_shred_dest_idx_to_dest( ei->sdest, idx );
     441          33 :   dest->ip4                       = ip4;
     442          33 :   dest->port                      = port;
     443          33 : }
     444             : 
     445             : void
     446             : ci_dest_remove_impl( fd_stake_ci_t *       info,
     447             :                      fd_pubkey_t const *   pubkey,
     448          30 :                      fd_per_epoch_info_t * ei ) {
     449          30 :   fd_shred_dest_idx_t idx = fd_shred_dest_pubkey_to_idx( ei->sdest, pubkey );
     450          30 :   if( FD_UNLIKELY( idx==FD_SHRED_DEST_NO_DEST ) ) return;
     451             : 
     452          24 :   fd_shred_dest_weighted_t * dest = fd_shred_dest_idx_to_dest( ei->sdest, idx );
     453          24 :   if( FD_UNLIKELY( dest->stake_lamports>0UL ) ) {
     454             :     /* A staked entry is not "removed", instead its "stale" address is
     455             :        retained */
     456           3 :     return;
     457           3 :   }
     458          21 :   ulong cur_cnt = fd_shred_dest_cnt_all( ei->sdest );
     459         147 :   for( ulong i=0UL, j=0UL; i<cur_cnt; i++ ) {
     460         126 :     if( FD_UNLIKELY( i==idx ) ) continue;
     461         105 :     info->shred_dest_temp[ j++ ] = *fd_shred_dest_idx_to_dest( ei->sdest, (fd_shred_dest_idx_t) i );
     462         105 :   }
     463             :   /* TODO: Alternative batched copy using memcpy. Check with Philip if this is safe */
     464             :   // fd_shred_dest_weighted_t * cur_dest = ei->sdest->all_destinations;
     465             :   // fd_memcpy( info->shred_dest_temp, cur_dest, sizeof(fd_shred_dest_weighted_t)*(idx) );
     466             :   // fd_memcpy( info->shred_dest_temp + idx, cur_dest + idx + 1UL, sizeof(fd_shred_dest_weighted_t)*(cur_cnt - idx - 1UL) );
     467          21 :   refresh_sdest( info, info->shred_dest_temp, cur_cnt-1UL, fd_shred_dest_cnt_staked( ei->sdest ), ei );
     468          21 : }
     469             : 
     470             : void
     471             : fd_stake_ci_dest_update( fd_stake_ci_t *       info,
     472             :                          fd_pubkey_t const *   pubkey,
     473             :                          uint                  ip4,
     474          45 :                          ushort                port ) {
     475          45 :   ci_dest_update_impl( info, pubkey, ip4, port, info->epoch_info+0UL );
     476          45 :   ci_dest_update_impl( info, pubkey, ip4, port, info->epoch_info+1UL );
     477          45 : }
     478             : 
     479             : void
     480             : fd_stake_ci_dest_remove( fd_stake_ci_t * info,
     481          15 :                          fd_pubkey_t const * pubkey ) {
     482          15 :   ci_dest_remove_impl( info, pubkey, info->epoch_info+0UL );
     483          15 :   ci_dest_remove_impl( info, pubkey, info->epoch_info+1UL );
     484             : 
     485          15 : }
     486             : 
     487             : 
     488             : fd_shred_dest_t *
     489             : fd_stake_ci_get_sdest_for_slot( fd_stake_ci_t const * info,
     490         789 :                                 ulong                 slot ) {
     491         789 :   ulong idx = fd_stake_ci_get_idx_for_slot( info, slot );
     492         789 :   return idx!=ULONG_MAX ? info->epoch_info[ idx ].sdest : NULL;
     493         789 : }
     494             : 
     495             : fd_epoch_leaders_t *
     496             : fd_stake_ci_get_lsched_for_slot( fd_stake_ci_t const * info,
     497         768 :                                  ulong                 slot ) {
     498         768 :   ulong idx = fd_stake_ci_get_idx_for_slot( info, slot );
     499         768 :   return idx!=ULONG_MAX ? info->epoch_info[ idx ].lsched : NULL;
     500         768 : }

Generated by: LCOV version 1.14