LCOV - code coverage report
Current view: top level - disco/shred - fd_shred_dest.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 245 256 95.7 %
Date: 2026-07-03 05:38:14 Functions: 12 12 100.0 %

          Line data    Source code
       1             : #include "fd_shred_dest.h"
       2             : #include "../../flamenco/accdb/fd_accdb.h"
       3             : 
       4             : struct pubkey_to_idx {
       5             :   fd_pubkey_t key;
       6             :   ulong       idx;
       7             : };
       8             : typedef struct pubkey_to_idx pubkey_to_idx_t;
       9             : 
      10             : static const fd_pubkey_t null_pubkey = {{ 0 }};
      11             : 
      12             : #define MAP_NAME              pubkey_to_idx
      13   242330046 : #define MAP_T                 pubkey_to_idx_t
      14   295839672 : #define MAP_KEY_T             fd_pubkey_t
      15   834201558 : #define MAP_KEY_NULL          null_pubkey
      16             : #define MAP_KEY_EQUAL_IS_SLOW 1
      17             : #define MAP_MEMOIZE           0
      18   295839672 : #define MAP_KEY_INVAL(k)      MAP_KEY_EQUAL((k),MAP_KEY_NULL)
      19   355844073 : #define MAP_KEY_EQUAL(k0,k1)  (!memcmp( (k0).key, (k1).key, 32UL ))
      20   241703118 : #define MAP_KEY_HASH(k,s)     ((MAP_HASH_T)fd_accdb_hash( (k).key, (s) ))
      21             : 
      22             : #include "../../util/tmpl/fd_map_dynamic.c"
      23             : 
      24             : 
      25             : /* This 45 byte struct gets hashed to compute the seed for ChaCha to
      26             :    compute the shred destinations. */
      27             : struct __attribute__((packed)) shred_dest_input {
      28             :   ulong slot;
      29             :   uchar type; /*     Data = 0b1010_0101, Code = 0b0101_1010 */
      30             :   uint  idx;
      31             :   uchar leader_pubkey[32];
      32             : };
      33             : typedef struct shred_dest_input shred_dest_input_t;
      34             : 
      35             : ulong
      36      120612 : fd_shred_dest_footprint( ulong staked_cnt, ulong unstaked_cnt ) {
      37      120612 :   ulong cnt = staked_cnt+unstaked_cnt;
      38      120612 :   int lg_cnt = fd_ulong_find_msb( fd_ulong_pow2_up( 2UL*fd_ulong_max( cnt, 1UL ) ) );
      39      120612 :   return FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND(
      40      120612 :                 FD_LAYOUT_INIT,
      41      120612 :                 fd_shred_dest_align(),             sizeof(fd_shred_dest_t)              ),
      42      120612 :                 pubkey_to_idx_align(),             pubkey_to_idx_footprint( lg_cnt )    ),
      43      120612 :                 alignof(fd_shred_dest_weighted_t), sizeof(fd_shred_dest_weighted_t)*cnt ),
      44      120612 :                 fd_wsample_align(),                fd_wsample_footprint( staked_cnt, 1 )),
      45      120612 :                 alignof(ulong),                    sizeof(ulong)*unstaked_cnt           ),
      46      120612 :       FD_SHRED_DEST_ALIGN );
      47      120612 : }
      48             : 
      49             : 
      50             : void *
      51             : fd_shred_dest_new( void                           * mem,
      52             :                    fd_shred_dest_weighted_t const * info,
      53             :                    ulong                            cnt,
      54             :                    fd_epoch_leaders_t const       * lsched,
      55             :                    fd_pubkey_t const              * source,
      56      313470 :                    ulong                            excluded_stake ) {
      57             : 
      58      313470 :   if( FD_UNLIKELY( !mem ) ) {
      59           3 :     FD_LOG_WARNING(( "NULL mem" ));
      60           3 :     return NULL;
      61           3 :   }
      62             : 
      63      313467 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_shred_dest_align() ) ) ) {
      64           3 :     FD_LOG_WARNING(( "misaligned mem" ));
      65           3 :     return NULL;
      66           3 :   }
      67             : 
      68      313464 :   int lg_cnt = fd_ulong_find_msb( fd_ulong_pow2_up( 2UL*fd_ulong_max( cnt, 1UL ) ) );
      69      313464 :   FD_SCRATCH_ALLOC_INIT( footprint, mem );
      70      313464 :   fd_shred_dest_t * sdest;
      71      313464 :   /* */  sdest     = FD_SCRATCH_ALLOC_APPEND( footprint, fd_shred_dest_align(),             sizeof(fd_shred_dest_t)              );
      72      313464 :   void * _map      = FD_SCRATCH_ALLOC_APPEND( footprint, pubkey_to_idx_align(),             pubkey_to_idx_footprint( lg_cnt )    );
      73      313464 :   void * _info     = FD_SCRATCH_ALLOC_APPEND( footprint, alignof(fd_shred_dest_weighted_t), sizeof(fd_shred_dest_weighted_t)*cnt );
      74             : 
      75      313464 :   ulong cnts[2] = { 0UL, 0UL }; /* cnts[0] = staked, cnts[1] = unstaked */
      76             : 
      77      313464 :   fd_shred_dest_weighted_t * copy = (fd_shred_dest_weighted_t *)_info;
      78   236148735 :   for( ulong i=0UL; i<cnt; i++ ) {
      79   235835271 :     copy[i] = info[i];
      80   235835271 :     ulong stake = info[i].stake_lamports;
      81             :     /* Check to make we never have a staked node following an unstaked
      82             :        node, which would mean info is not sorted properly. */
      83   235835271 :     if( FD_UNLIKELY( (stake>0UL) & (cnts[1]>0UL) ) ) {
      84           0 :       FD_LOG_WARNING(( "info was not sorted properly. info[%lu] has non-zero stake %lu but follows an unstaked node", i, stake ));
      85           0 :       return NULL;
      86           0 :     }
      87   235835271 :     cnts[ stake==0UL ]++;
      88   235835271 :   }
      89             : 
      90      313464 :   ulong staked_cnt   = cnts[0];
      91      313464 :   ulong unstaked_cnt = cnts[1];
      92             : 
      93      313464 :   if( FD_UNLIKELY( (excluded_stake>0UL) & (unstaked_cnt>0UL) ) ) {
      94             :     /* If excluded stake > 0, then the list must be filled with staked nodes. */
      95           0 :     FD_LOG_WARNING(( "cannot have excluded stake and unstaked validators" ));
      96           0 :     return NULL;
      97           0 :   }
      98             : 
      99      313464 :   void * _wsample  = FD_SCRATCH_ALLOC_APPEND( footprint, fd_wsample_align(), fd_wsample_footprint( staked_cnt, 1 ));
     100      313464 :   void * _unstaked = FD_SCRATCH_ALLOC_APPEND( footprint, alignof(ulong),     sizeof(ulong)*unstaked_cnt           );
     101             : 
     102             : 
     103      313464 :   fd_chacha_rng_t * rng = fd_chacha_rng_join( fd_chacha_rng_new( sdest->rng, FD_CHACHA_RNG_MODE_SHIFT ) );
     104             : 
     105      313464 :   void  *  _staked   = fd_wsample_new_init( _wsample,  rng, staked_cnt,   1, FD_WSAMPLE_HINT_POWERLAW_REMOVE );
     106             : 
     107   202928880 :   for( ulong i=0UL; i<staked_cnt;   i++ ) _staked   = fd_wsample_new_add( _staked,   info[i].stake_lamports );
     108      313464 :   _staked   = fd_wsample_new_fini( _staked, excluded_stake );
     109             : 
     110      313464 :   pubkey_to_idx_t * pubkey_to_idx_map = pubkey_to_idx_join( pubkey_to_idx_new( _map, lg_cnt, 0UL ) );
     111   236148735 :   for( ulong i=0UL; i<cnt; i++ ) {
     112             :     /* we should never have duplicates in info[i].pubkey, but in case
     113             :        of duplicates it's better to skip than to segfault. */
     114   235835271 :     pubkey_to_idx_t * inserted = pubkey_to_idx_insert( pubkey_to_idx_map, info[i].pubkey );
     115   235835271 :     if( FD_UNLIKELY( !inserted ) ) {
     116           0 :       continue;
     117           0 :     }
     118   235835271 :     inserted->idx = i;
     119   235835271 :   }
     120      313464 :   pubkey_to_idx_t * query = pubkey_to_idx_query( pubkey_to_idx_map, *source, NULL );
     121      313464 :   if( FD_UNLIKELY( !query ) ) {
     122          12 :     FD_LOG_WARNING(( "source pubkey not found" ));
     123          12 :     return NULL;
     124          12 :   }
     125             : 
     126      313452 :   memset( sdest->null_dest, 0, sizeof(fd_shred_dest_weighted_t) );
     127      313452 :   sdest->lsched                     = lsched;
     128      313452 :   sdest->cnt                        = cnt;
     129      313452 :   sdest->all_destinations           = copy;
     130      313452 :   sdest->staked                     = fd_wsample_join( _staked );
     131      313452 :   sdest->unstaked                   = _unstaked;
     132      313452 :   sdest->unstaked_unremoved_cnt     = 0UL; /* unstaked doesn't get initialized until it's needed */
     133      313452 :   sdest->staked_cnt                 = staked_cnt;
     134      313452 :   sdest->unstaked_cnt               = unstaked_cnt;
     135      313452 :   sdest->excluded_stake             = excluded_stake;
     136      313452 :   sdest->pubkey_to_idx_map          = pubkey_to_idx_map;
     137      313452 :   sdest->source_validator_orig_idx  = query->idx;
     138             : 
     139      313452 :   return (void *)sdest;
     140      313464 : }
     141             : 
     142      313464 : fd_shred_dest_t * fd_shred_dest_join( void * mem  ) { return (fd_shred_dest_t *)mem; }
     143      313224 : void * fd_shred_dest_leave( fd_shred_dest_t * sdest ) { return (void *)sdest;          }
     144             : 
     145      313224 : void * fd_shred_dest_delete( void * mem ) {
     146      313224 :   fd_shred_dest_t * sdest = (fd_shred_dest_t *)mem;
     147             : 
     148      313224 :   fd_chacha_rng_delete( fd_chacha_rng_leave( sdest->rng               ) );
     149      313224 :   fd_wsample_delete   ( fd_wsample_leave   ( sdest->staked            ) );
     150      313224 :   pubkey_to_idx_delete( pubkey_to_idx_leave( sdest->pubkey_to_idx_map ) );
     151      313224 :   return mem;
     152      313224 : }
     153             : 
     154             : /* sample_unstaked, sample_unstaked_noprepare, and
     155             :    prepare_unstaked_sampling are used to perform the specific form of
     156             :    unweighted random sampling that Solana uses for unstaked validators.
     157             :    In essence, you:
     158             :     1. construct a list of all the unstaked validators,
     159             :     2. delete the leader (if present)
     160             :     then repeatedly:
     161             :     3. choose the chacha_rng_roll( |unstaked| )th element.
     162             :     4. swap the last element in unstaked with the chosen element
     163             :     5. return and remove the chosen element (which is now in the last
     164             :     position, so remove is O(1)).
     165             :    Steps 1 and 2 are both O(|unstaked|), but they can be combined
     166             :    relatively easily if we wait to construct the list until we know
     167             :    which element we need to delete.  prepare_unstaked_sampling performs
     168             :    steps 1 and 2. sample_unstaked performs steps 3-5.  Thus, you must
     169             :    call prepare_unstaked_sampling prior to calling sample_unstaked.
     170             : 
     171             :    When only sampling a single element from the list, forming the whole
     172             :    array is wasteful; we just need to know whether the element we choose
     173             :    comes before or after the element we deleted.
     174             :    sample_unstaked_noprepare returns the same result as
     175             :    prepare_unstaked_sampling followed by one call to sample_unstaked.
     176             :    sample_unstaked_noprepare does not read or modify the unstaked array,
     177             :    so it can be called without calling prepare_unstaked_sampling.
     178             : 
     179             :    remove_idx is the index of the element to remove in step 2.  If it is
     180             :    not in [sdest->staked_cnt, sdest->staked_cnt+sdest->unstaked_cnt),
     181             :    then it will be ignored.  sample_unstaked and
     182             :    sample_unstaked_noprepare return the index into
     183             :    sdest->all_destinations of the selected sample.  The returned value
     184             :    will be in [sdest->staked_cnt, sdest->staked_cnt+sdest->unstaked_cnt)
     185             :    or FD_WSAMPLE_EMPTY. */
     186             : static inline ulong
     187             : sample_unstaked_noprepare( fd_shred_dest_t  * sdest,
     188          99 :                            ulong              remove_idx ) {
     189             :   /* is remove_idx in
     190             :      [sdest->staked_cnt, sdest->staked_cnt+sdest->unstaked_cnt) ? */
     191          99 :   int remove_in_interval = (sdest->staked_cnt <= remove_idx) & (remove_idx < (sdest->staked_cnt+sdest->unstaked_cnt) );
     192          99 :   ulong unstaked_cnt = sdest->unstaked_cnt - (ulong)remove_in_interval;
     193          99 :   if( FD_UNLIKELY( unstaked_cnt==0UL ) ) return FD_WSAMPLE_EMPTY;
     194             : 
     195          99 :   ulong sample = sdest->staked_cnt + fd_chacha_rng_ulong_roll( sdest->rng, unstaked_cnt );
     196          99 :   return fd_ulong_if( (!remove_in_interval) | (sample<remove_idx), sample, sample+1UL );
     197          99 : }
     198             : 
     199             : /* It's cheaper to initialize unstaked without the element we want to
     200             :    delete than to delete it after initializing, so we defer the
     201             :    initialization until we know who the leader is. */
     202             : static inline void
     203             : prepare_unstaked_sampling( fd_shred_dest_t  * sdest,
     204      136191 :                            ulong              remove_idx ) {
     205      136191 :   int remove_in_interval = (sdest->staked_cnt <= remove_idx) & (remove_idx < (sdest->staked_cnt+sdest->unstaked_cnt) );
     206      136191 :   ulong unstaked_cnt = sdest->unstaked_cnt - (ulong)remove_in_interval;
     207      136191 :   sdest->unstaked_unremoved_cnt = unstaked_cnt;
     208      136191 :   if( FD_UNLIKELY( unstaked_cnt==0UL ) ) return;
     209             : 
     210             :   /* If we had to remove something in the interval, we want to make sure
     211             :      it doesn't occur in the list of indices.  Otherwise just take them
     212             :      all. */
     213      130062 :   ulong direct_index_up_to = fd_ulong_if( remove_in_interval, remove_idx - sdest->staked_cnt, unstaked_cnt );
     214      130062 :   ulong i=0UL;
     215    10804059 :   for( ; i<direct_index_up_to; i++ ) sdest->unstaked[i] = i+sdest->staked_cnt;
     216      624657 :   for( ; i<unstaked_cnt;       i++ ) sdest->unstaked[i] = i+sdest->staked_cnt + 1UL;
     217      130062 : }
     218             : 
     219             : static inline ulong
     220    11027055 : sample_unstaked( fd_shred_dest_t * sdest ) {
     221    11027055 :   if( FD_UNLIKELY( sdest->unstaked_unremoved_cnt==0UL ) ) return FD_WSAMPLE_EMPTY;
     222             : 
     223    10909608 :   ulong sample = fd_chacha_rng_ulong_roll( sdest->rng, sdest->unstaked_unremoved_cnt );
     224    10909608 :   ulong to_return = sdest->unstaked[sample];
     225    10909608 :   sdest->unstaked[sample] = sdest->unstaked[--sdest->unstaked_unremoved_cnt];
     226    10909608 :   return to_return;
     227    11027055 : }
     228             : 
     229             : 
     230             : /* Returns 0 on success
     231             :    https://github.com/anza-xyz/agave/blob/v2.2.1/ledger/src/shred.rs#L293 */
     232             : static inline int
     233             : compute_seeds( fd_shred_dest_t           * sdest,
     234             :                fd_shred_t  const * const * input_shreds,
     235             :                ulong                       shred_cnt,
     236             :                fd_pubkey_t       const   * leader,
     237             :                ulong                       slot,
     238     1056747 :                uchar                       dest_hash_output[ FD_SHRED_DEST_MAX_SHRED_CNT ][ 32 ] ) {
     239             : 
     240     1056747 :   shred_dest_input_t dest_hash_inputs [ FD_SHRED_DEST_MAX_SHRED_CNT ];
     241     1056747 :   fd_sha256_batch_t * sha256 = fd_sha256_batch_init( sdest->_sha256_batch );
     242             : 
     243     2863494 :   for( ulong i=0UL; i<shred_cnt; i++ ) {
     244     1806747 :     shred_dest_input_t * h_in  = dest_hash_inputs+i;
     245     1806747 :     fd_shred_t const   * shred = input_shreds[i];
     246     1806747 :     if( FD_UNLIKELY( shred->slot != slot ) ) return -1;
     247             : 
     248     1806747 :     uchar shred_type = fd_shred_type( shred->variant );
     249     1806747 :     h_in->slot = slot;
     250     1806747 :     h_in->type = fd_uchar_if( fd_shred_is_data( shred_type ), 0xA5, 0x5A );
     251     1806747 :     h_in->idx  = shred->idx;
     252     1806747 :     memcpy( h_in->leader_pubkey, leader, 32UL );
     253             : 
     254     1806747 :     fd_sha256_batch_add( sha256, dest_hash_inputs+i,   sizeof(shred_dest_input_t), dest_hash_output[ i ] );
     255     1806747 :   }
     256     1056747 :   fd_sha256_batch_fini( sha256 );
     257     1056747 :   return 0;
     258     1056747 : }
     259             : 
     260             : 
     261             : fd_shred_dest_idx_t *
     262             : fd_shred_dest_compute_first( fd_shred_dest_t          * sdest,
     263             :                              fd_shred_t const * const * input_shreds,
     264             :                              ulong                      shred_cnt,
     265        5823 :                              fd_shred_dest_idx_t      * out ) {
     266             : 
     267        5823 :   if( FD_UNLIKELY( shred_cnt==0UL ) ) return out;
     268             : 
     269        5823 :   if( FD_UNLIKELY( sdest->cnt<=1UL ) ) {
     270             :     /* We are the only validator that we know about, and we can't send
     271             :        it to ourself, so there's nobody we can send the shred to. */
     272           0 :     for( ulong i=0UL; i<shred_cnt; i++ ) out[ i ] = FD_SHRED_DEST_NO_DEST;
     273           0 :     return out;
     274           0 :   }
     275             : 
     276        5823 :   uchar dest_hash_outputs[ FD_SHRED_DEST_MAX_SHRED_CNT ][ 32 ];
     277             : 
     278        5823 :   ulong slot = input_shreds[0]->slot;
     279        5823 :   fd_pubkey_t const * leader = fd_epoch_leaders_get( sdest->lsched, slot );
     280        5823 :   if( FD_UNLIKELY( !leader ) ) return NULL;
     281             : 
     282        5823 :   if( FD_UNLIKELY( compute_seeds( sdest, input_shreds, shred_cnt, leader, slot, dest_hash_outputs ) ) ) return NULL;
     283             : 
     284             :   /* If we're calling this, we must be the leader.  That means we had
     285             :      some stake when the leader schedule was created, but maybe not
     286             :      anymore?  This version of the code is safe either way, but I should
     287             :      probably confirm this can happen. */
     288        5823 :   int source_validator_is_staked = sdest->source_validator_orig_idx<sdest->staked_cnt;
     289        5823 :   if( FD_LIKELY( source_validator_is_staked ) )
     290        4161 :     fd_wsample_remove_idx( sdest->staked, sdest->source_validator_orig_idx );
     291             : 
     292        5823 :   int any_staked_candidates = sdest->staked_cnt > (ulong)source_validator_is_staked;
     293       11646 :   for( ulong i=0UL; i<shred_cnt; i++ ) {
     294        5823 :     fd_wsample_seed_rng( sdest->staked, dest_hash_outputs[ i ] );
     295             :     /* Map FD_WSAMPLE_INDETERMINATE (UINT_MAX-1) and FD_WSAMPLE_EMPTY
     296             :        (UINT_MAX) to FD_SHRED_DEST_NO_DEST.  If wsample returns either
     297             :        sentinel value, it will be cast to -2 or -1, so the max will be
     298             :        -1, as desired.  Otherwise, since wsample guarantees the returned
     299             :        index is in [0, INT_MAX], it will remain non-negative when cast
     300             :        to an int, so the max will be that value. */
     301        5823 :     FD_STATIC_ASSERT( (int)FD_WSAMPLE_INDETERMINATE             <=-1, wsample_val );
     302        5823 :     FD_STATIC_ASSERT( (int)FD_WSAMPLE_EMPTY                     <=-1, wsample_val );
     303        5823 :     FD_STATIC_ASSERT( FD_SHRED_DEST_NO_DEST==(fd_shred_dest_idx_t)-1, wsample_val );
     304        5823 :     if( FD_LIKELY( any_staked_candidates ) ) out[i] = (fd_shred_dest_idx_t)fd_int_max( (int)fd_wsample_sample( sdest->staked ), -1 );
     305          99 :     else                                     out[i] = (fd_shred_dest_idx_t)sample_unstaked_noprepare( sdest, sdest->source_validator_orig_idx );
     306        5823 :   }
     307        5823 :   fd_wsample_restore_all( sdest->staked );
     308             : 
     309        5823 :   return out;
     310        5823 : }
     311             : 
     312             : fd_shred_dest_idx_t *
     313             : fd_shred_dest_compute_children( fd_shred_dest_t          * sdest,
     314             :                                 fd_shred_t const * const * input_shreds,
     315             :                                 ulong                      shred_cnt,
     316             :                                 fd_shred_dest_idx_t      * out,
     317             :                                 ulong                      out_stride,
     318             :                                 ulong                      fanout,
     319             :                                 ulong                      dest_cnt,
     320     1089225 :                                 ulong                    * opt_max_dest_cnt ) {
     321             : 
     322             :   /* The logic here is a little tricky since we are keeping track of
     323             :      staked and unstaked separately and only logically concatenating
     324             :      them [staked, unstaked] , but that does allow us to skip some
     325             :      samples sometimes.  We're operating from the source validator's
     326             :      perspective here, so everything in the first person singular refers
     327             :      to the source validator. */
     328             : 
     329     1089225 :   ulong my_orig_idx = sdest->source_validator_orig_idx;
     330     1089225 :   int   i_am_staked = my_orig_idx<sdest->staked_cnt;
     331             : 
     332     1089225 :   fd_ulong_store_if( !!opt_max_dest_cnt, opt_max_dest_cnt, 0UL );
     333             : 
     334     1089225 :   if( FD_UNLIKELY( (shred_cnt==0UL) | (dest_cnt==0UL) ) ) return out; /* Nothing to do */
     335             : 
     336     1089225 :   ulong               slot   = input_shreds[0]->slot;
     337     1089225 :   fd_pubkey_t const * leader = fd_epoch_leaders_get   ( sdest->lsched, slot );
     338     1089225 :   if( FD_UNLIKELY( !leader                 ) ) return NULL; /* Unknown slot */
     339             : 
     340     1089225 :   pubkey_to_idx_t *   query  = pubkey_to_idx_query( sdest->pubkey_to_idx_map, *leader, NULL );
     341     1089225 :   int                 leader_is_staked = query ? (query->idx<sdest->staked_cnt): 0;
     342     1089225 :   ulong               leader_idx       = query ?  query->idx                   : ULONG_MAX;
     343     1089225 :   if( FD_UNLIKELY( leader_idx==my_orig_idx ) ) return NULL; /* I am the leader. Use compute_first */
     344             : 
     345     1089225 :   if( FD_UNLIKELY( (sdest->cnt<=1UL) |                    /* We don't know about a single destination, so we can't send
     346             :                                                              anything. */
     347     1089225 :         ( (!i_am_staked) & (sdest->staked_cnt-(ulong)leader_is_staked>fanout) ) ) ) {
     348             :     /* My position is somewhere after all the staked nodes, which means
     349             :        my shuffled index is always greater than fanout.  That means I'm
     350             :        always at the bottom of the Turbine tree so I don't have to send
     351             :        any shreds to anyone. */
     352    19622568 :     for( ulong j=0UL; j<dest_cnt; j++ ) for( ulong i=0UL; i<shred_cnt; i++ ) out[ j*out_stride + i ] = FD_SHRED_DEST_NO_DEST;
     353       38301 :     return out;
     354       38301 :   }
     355             : 
     356     1050924 :   uchar dest_hash_outputs[ FD_SHRED_DEST_MAX_SHRED_CNT ][ 32 ];
     357             : 
     358             : 
     359     1050924 :   if( FD_UNLIKELY( compute_seeds( sdest, input_shreds, shred_cnt, leader, slot, dest_hash_outputs ) ) ) return NULL;
     360             : 
     361     1050924 :   ulong max_dest_cnt = 0UL;
     362             : 
     363     1050924 :   ulong staked_shuffle[ sdest->staked_cnt+1UL ];
     364     1050924 :   ulong staked_shuffle_populated_cnt = 0UL;
     365             : 
     366     2851848 :   for( ulong i=0UL; i<shred_cnt; i++ ) {
     367             :     /* Remove the leader. */
     368     1800924 :     if( FD_LIKELY( query && leader_is_staked ) ) fd_wsample_remove_idx( sdest->staked, leader_idx );
     369             : 
     370     1800924 :     ulong my_idx         = 0UL;
     371     1800924 :     fd_wsample_seed_rng( sdest->staked, dest_hash_outputs[ i ] ); /* Seeds both samplers since the rng is shared */
     372             : 
     373     1800924 :     if( FD_UNLIKELY( !i_am_staked ) ) {
     374             :       /* If there's excluded stake, we don't know about any unstaked
     375             :          validators, so if I am unstaked, then I'm in the excluded
     376             :          region, and we have no hope of computing my position in the
     377             :          shuffle. */
     378       37503 :       if( FD_UNLIKELY( sdest->excluded_stake>0UL ) ) return NULL;
     379             : 
     380             :       /* Quickly burn through all the staked nodes since I'll be in the
     381             :          unstaked portion.  We don't care about the values, but we need
     382             :          to advance the RNG the right number of times, and sadly there's
     383             :          no other way to do it than this. There can't be too many of
     384             :          them since otherwise we would have taken the quick exit at the
     385             :          start of the function. */
     386       37503 :       staked_shuffle_populated_cnt = sdest->staked_cnt + 1UL;
     387       37503 :       fd_wsample_sample_and_remove_many( sdest->staked, staked_shuffle, staked_shuffle_populated_cnt );
     388       37503 :       my_idx += sdest->staked_cnt - (ulong)(query && leader_is_staked);
     389             : 
     390       37503 :       prepare_unstaked_sampling( sdest, leader_idx );
     391      342495 :       while( my_idx <= fanout ) {
     392      325584 :         ulong sample = sample_unstaked( sdest );
     393      325584 :         if( FD_UNLIKELY( sample==my_orig_idx      ) ) break; /* Found me! */
     394      304992 :         if( FD_UNLIKELY( sample==FD_WSAMPLE_EMPTY ) ) return NULL; /* I couldn't find myself.  This should be impossible. */
     395      304992 :         my_idx++;
     396      304992 :       }
     397     1763421 :     } else {
     398     1763421 :       staked_shuffle_populated_cnt = fd_ulong_min( fanout+1UL, sdest->staked_cnt+1UL );
     399     1763421 :       fd_wsample_sample_and_remove_many( sdest->staked, staked_shuffle, staked_shuffle_populated_cnt );
     400   204332979 :       while( my_idx <= fanout ) {
     401             :         /* my_idx < fanout+1UL because of the while loop condition.
     402             :            my_idx < staked_cnt+1UL because my_idx==staked_cnt will
     403             :            trigger sample==FD_WSAMPLE_EMPTY below.  Thus, this access is
     404             :            safe. */
     405   203418723 :         ulong sample = staked_shuffle[ my_idx ];
     406   203418723 :         if( FD_UNLIKELY( sample==my_orig_idx              ) ) break; /* Found me! */
     407   202569558 :         if( FD_UNLIKELY( sample==FD_WSAMPLE_EMPTY         ) ) return NULL; /* I couldn't find myself.  This should be impossible. */
     408   202569558 :         if( FD_UNLIKELY( sample==FD_WSAMPLE_INDETERMINATE ) ) my_idx=ULONG_MAX-1UL; /* Hit poisoned region before myself. No
     409             :                                                                                        clue about position. Break immediately and
     410             :                                                                                        fill with NO_DEST below. */
     411   202569558 :         my_idx++;
     412   202569558 :       }
     413     1763421 :     }
     414             : 
     415     1800924 :     if( FD_LIKELY( my_idx > fanout ) ) {
     416             :       /* I'm at the bottom of Turbine tree for this shred.  Fill in all
     417             :          the destinations with NO_DEST. */
     418   189362067 :       for( ulong j=0UL; j<dest_cnt; j++ ) out[ j*out_stride + i ] = FD_SHRED_DEST_NO_DEST;
     419             : 
     420      931167 :       fd_wsample_restore_all( sdest->staked   );
     421      931167 :       continue; /* Next shred */
     422      931167 :     }
     423             :     /* If my index is    |  Send to indices
     424             :        ------------------------------------
     425             :         Leader (no idx)  |  0             (just for reference)
     426             :         0                |  1, 2, ..., F
     427             :         j in [1, F]      |  j + l*F for l in [1,F]
     428             :         [F+1, F^2+F]     |  Nobody
     429             :         [F^2+F+1, inf)   |  Not yet implemented in Agave code
     430             :      */
     431      869757 :     ulong last_dest_idx = fd_ulong_if( my_idx==0UL, fanout, my_idx+fanout*fanout ); /* inclusive */
     432      869757 :     ulong stride        = fd_ulong_if( my_idx==0UL, 1UL,    fanout               );
     433             : 
     434      869757 :     last_dest_idx = fd_ulong_min( last_dest_idx, my_idx + stride*dest_cnt );
     435             : 
     436      869757 :     ulong cursor     = my_idx+1UL;
     437      869757 :     ulong stored_cnt = 0UL;
     438             : 
     439      869757 :     if( FD_LIKELY( (last_dest_idx>=staked_shuffle_populated_cnt) & (staked_shuffle_populated_cnt<sdest->staked_cnt+1UL ) ) ) {
     440      404781 :       ulong adtl = fd_ulong_min( last_dest_idx+1UL, sdest->staked_cnt+1UL ) - staked_shuffle_populated_cnt;
     441             : 
     442      404781 :       fd_wsample_sample_and_remove_many( sdest->staked, staked_shuffle+staked_shuffle_populated_cnt, adtl );
     443      404781 :       staked_shuffle_populated_cnt += adtl;
     444      404781 :     }
     445             : 
     446    55716669 :     while( cursor<=fd_ulong_min( last_dest_idx, sdest->staked_cnt ) ) {
     447    54946137 :       ulong sample = staked_shuffle[ cursor ];
     448    54946137 :       if( FD_UNLIKELY( sample==FD_WSAMPLE_EMPTY         ) ) break;
     449    54849501 :       if( FD_UNLIKELY( sample==FD_WSAMPLE_INDETERMINATE ) ) break;
     450             : 
     451    54846912 :       if( FD_UNLIKELY( cursor == my_idx + stride*(stored_cnt+1UL) ) ) {
     452     3970464 :         out[ stored_cnt*out_stride + i ] = (fd_shred_dest_idx_t)sample;
     453     3970464 :         stored_cnt++;
     454     3970464 :       }
     455    54846912 :       cursor++;
     456    54846912 :     }
     457             :     /* If we broke from the above loop because of indeterminate, then
     458             :        unstaked_cnt==0, so the next loop will also be a no-op, and we'll
     459             :        fill the remaining array with NO_DEST, as desired. */
     460             : 
     461             :     /* Next set of samples (if any) come from the unstaked portion. If I
     462             :        am not staked, then prepare_unstaked_sampling was already called
     463             :        earlier. */
     464      869757 :     if( FD_LIKELY( (cursor<=last_dest_idx) & !!i_am_staked ) ) prepare_unstaked_sampling( sdest, leader_idx );
     465    11453781 :     while( cursor<=last_dest_idx ) {
     466    10701471 :       ulong sample = sample_unstaked( sdest );
     467    10701471 :       if( FD_UNLIKELY( sample==FD_WSAMPLE_EMPTY ) ) break;
     468             : 
     469    10584024 :       if( FD_UNLIKELY( cursor == my_idx + stride*(stored_cnt+1UL) ) ) {
     470       76692 :         out[ stored_cnt*out_stride + i ] = (fd_shred_dest_idx_t)sample;
     471       76692 :         stored_cnt++;
     472       76692 :       }
     473    10584024 :       cursor++;
     474    10584024 :     }
     475      869757 :     max_dest_cnt = fd_ulong_max( max_dest_cnt, stored_cnt );
     476             : 
     477             :     /* The rest of my destinations are past the end of the tree */
     478    27432753 :     for( ulong j=stored_cnt; j<dest_cnt; j++ ) out[ j*out_stride + i ] = FD_SHRED_DEST_NO_DEST;
     479             : 
     480      869757 :     fd_wsample_restore_all( sdest->staked );
     481             : 
     482      869757 :   }
     483     1050924 :   fd_ulong_store_if( !!opt_max_dest_cnt, opt_max_dest_cnt, max_dest_cnt );
     484     1050924 :   return out;
     485     1050924 : }
     486             : 
     487             : fd_shred_dest_idx_t
     488             : fd_shred_dest_pubkey_to_idx( fd_shred_dest_t   * sdest,
     489     4465158 :                              fd_pubkey_t const * pubkey     ) {
     490     4465158 :   if( FD_UNLIKELY( !memcmp( pubkey, null_pubkey.uc, 32UL ) ) ) return FD_SHRED_DEST_NO_DEST;
     491             : 
     492     4465158 :   pubkey_to_idx_t default_res[ 1 ] = {{ .idx = FD_SHRED_DEST_NO_DEST }};
     493     4465158 :   pubkey_to_idx_t * query = pubkey_to_idx_query( sdest->pubkey_to_idx_map, *pubkey, default_res );
     494             : 
     495     4465158 :   return (fd_shred_dest_idx_t)query->idx;
     496     4465158 : }
     497             : 

Generated by: LCOV version 1.14