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

Generated by: LCOV version 1.14