LCOV - code coverage report
Current view: top level - flamenco/leaders - fd_leaders.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 195 212 92.0 %
Date: 2026-11-02 04:25:37 Functions: 17 23 73.9 %

          Line data    Source code
       1             : #include "fd_leaders.h"
       2             : #include "../../ballet/base58/fd_base58.h"
       3             : #include "../../ballet/chacha/fd_chacha_rng.h"
       4             : #include "../../ballet/sha256/fd_sha256.h"
       5             : #include "../../ballet/wsample/fd_wsample.h"
       6             : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
       7             : 
       8             : #define SORT_NAME sort_vote_weights_by_stake_id
       9             : #define SORT_KEY_T fd_vote_stake_weight_t
      10             : #define SORT_BEFORE(a,b) ((a).stake > (b).stake ? 1 : ((a).stake < (b).stake ? 0 : memcmp( (a).id_key.uc, (b).id_key.uc, 32UL )>0))
      11             : #include "../../util/tmpl/fd_sort.c"
      12             : 
      13             : #define SORT_NAME sort_vote_weights_by_id
      14             : #define SORT_KEY_T fd_vote_stake_weight_t
      15             : #define SORT_BEFORE(a,b) (memcmp( (a).id_key.uc, (b).id_key.uc, 32UL )>0)
      16             : #include "../../util/tmpl/fd_sort.c"
      17             : 
      18             : #define SORT_NAME sort_weights_by_stake_id
      19      154941 : #define SORT_KEY_T fd_stake_weight_t
      20      228528 : #define SORT_BEFORE(a,b) ((a).stake > (b).stake ? 1 : ((a).stake < (b).stake ? 0 : memcmp( (a).key.uc, (b).key.uc, 32UL )>0))
      21             : #include "../../util/tmpl/fd_sort.c"
      22             : 
      23             : #define SORT_NAME sort_weights_by_id
      24      144273 : #define SORT_KEY_T fd_stake_weight_t
      25      210558 : #define SORT_BEFORE(a,b) (memcmp( (a).key.uc, (b).key.uc, 32UL )>0)
      26             : #include "../../util/tmpl/fd_sort.c"
      27             : 
      28             : /* FD_EPOCH_LEADERS_GEO_RUN_LENGTH is the maximum number of leaders in
      29             :    a single cluster. Defined by SIMD-0675. */
      30    16012893 : #define FD_EPOCH_LEADERS_GEO_RUN_LENGTH  (3UL)
      31             : 
      32             : /* FD_EPOCH_LEADERS_GEO_STAKE_FLOOR is the minimum % of the total stake
      33             :    that each leaders radius must contain. Defined by SIMD-0675. */
      34     1059306 : #define FD_EPOCH_LEADERS_GEO_STAKE_FLOOR (10UL)
      35             : 
      36             : ulong
      37             : compute_id_weights_from_vote_weights( fd_stake_weight_t *            stake_weight,
      38             :                                       fd_vote_stake_weight_t const * vote_stake_weight,
      39         528 :                                       ulong                          staked_cnt ) {
      40             : 
      41             :   /* Copy from input message [(vote, id, stake)] into old format [(id, stake)]. */
      42         528 :   ulong idx = 0UL;
      43       19632 :   for( ulong i=0UL; i<staked_cnt; i++ ) {
      44       19104 :     memcpy( stake_weight[ idx ].key.uc, vote_stake_weight[ i ].id_key.uc, sizeof(fd_pubkey_t) );
      45       19104 :     stake_weight[ idx ].stake = vote_stake_weight[ i ].stake;
      46       19104 :     idx++;
      47       19104 :   }
      48             : 
      49             :   /* Sort [(id, stake)] by id, so we can dedup */
      50         528 :   sort_weights_by_id_inplace( stake_weight, idx );
      51             : 
      52             :   /* Dedup entries, aggregating stake */
      53         528 :   ulong j=0UL;
      54       19104 :   for( ulong i=1UL; i<idx; i++ ) {
      55       18576 :     fd_pubkey_t * pre = &stake_weight[ j ].key;
      56       18576 :     fd_pubkey_t * cur = &stake_weight[ i ].key;
      57       18576 :     if( 0==memcmp( pre, cur, sizeof(fd_pubkey_t) ) ) {
      58          60 :       stake_weight[ j ].stake += stake_weight[ i ].stake;
      59       18516 :     } else {
      60       18516 :       ++j;
      61       18516 :       stake_weight[ j ].stake = stake_weight[ i ].stake;
      62       18516 :       memcpy( stake_weight[ j ].key.uc, stake_weight[ i ].key.uc, sizeof(fd_pubkey_t) );
      63       18516 :     }
      64       18576 :   }
      65         528 :   ulong staked_cnt_by_id = fd_ulong_min( idx, j+1 );
      66             : 
      67             :   /* Sort [(id, stake)] by stake then id, as expected */
      68         528 :   sort_weights_by_stake_id_inplace( stake_weight, staked_cnt_by_id );
      69             : 
      70         528 :   return staked_cnt_by_id;
      71         528 : }
      72             : 
      73             : ulong
      74           0 : fd_epoch_leaders_align( void ) {
      75           0 :   return FD_EPOCH_LEADERS_ALIGN;
      76           0 : }
      77             : 
      78             : FD_FN_CONST ulong
      79             : fd_epoch_leaders_footprint( ulong pub_cnt,
      80       13785 :                             ulong slot_cnt ) {
      81       13785 :   if( FD_UNLIKELY( ( pub_cnt  ==     0UL     )
      82       13785 :                  | ( pub_cnt   >UINT_MAX-3UL )
      83       13785 :                  | ( slot_cnt==     0UL  ) ) )
      84           0 :     return 0UL;
      85       13785 :   return FD_EPOCH_LEADERS_FOOTPRINT( pub_cnt, slot_cnt );
      86       13785 : }
      87             : 
      88             : struct fd_geo_neighbor {
      89             :   ulong dist2;
      90             :   ulong stake;
      91             : };
      92             : typedef struct fd_geo_neighbor fd_geo_neighbor_t;
      93             : 
      94             : struct fd_geo_cluster {
      95             :   uint members_cnt;
      96             :   uint members[ FD_EPOCH_LEADERS_GEO_RUN_LENGTH ];
      97             : };
      98             : typedef struct fd_geo_cluster fd_geo_cluster_t;
      99             : 
     100             : #define SORT_NAME        sort_geo_neighbor
     101   434973735 : #define SORT_KEY_T       fd_geo_neighbor_t
     102   500507046 : #define SORT_BEFORE(a,b) ((a).dist2<(b).dist2)
     103             : #include "../../util/tmpl/fd_sort.c"
     104             : 
     105             : static void
     106             : fisher_yates_shuffle( fd_chacha_rng_t * rng,
     107             :                       uint *            array,
     108     3070425 :                       ulong             n ) {
     109     9079734 :   for( ulong i=n; i>1UL; i-- ) {
     110     6009309 :     ulong j        = fd_chacha_rng_ulong_roll( rng, i );
     111     6009309 :     uint  t        = array[ i-1UL ];
     112     6009309 :     array[ i-1UL ] = array[ j ];
     113     6009309 :     array[ j ]     = t;
     114     6009309 :   }
     115     3070425 : }
     116             : 
     117             : /* compute_smallest_radius returns the smallest squared distance r
     118             :    from the validator v, such that the validators at most r away from
     119             :    v make up at least FD_EPOCH_LEADERS_GEO_STAKE_FLOOR % of the total
     120             :    stake.
     121             : 
     122             :    neighbor is an array of the pre-computed distances and stakes of
     123             :    each validator from v.
     124             : 
     125             :    The algorithm is as such:
     126             :    - Sort the neighbours by distance
     127             :    - Sum the stake until we get to the stake floor */
     128             : static ulong
     129             : compute_smallest_radius( fd_geo_neighbor_t * neighbor,
     130             :                          ulong               cnt,
     131     1059306 :                          ulong               total ) {
     132     1059306 :   sort_geo_neighbor_inplace( neighbor, cnt );
     133     1059306 :   uint128 thresh = (uint128)total*FD_EPOCH_LEADERS_GEO_STAKE_FLOOR;
     134     1059306 :   ulong   acc    = 0UL;
     135     6292596 :   for( ulong i=0UL; i<cnt; i++ ) {
     136     6292596 :     acc += neighbor[ i ].stake;
     137     6292596 :     if( (uint128)acc*100UL>=thresh ) return neighbor[ i ].dist2;
     138     6292596 :   }
     139           0 :   FD_LOG_CRIT(( "unreachable" ));
     140           0 : }
     141             : 
     142             : /* can_join_cluster returns 1 if the given validator leader can join
     143             :    the given cluster. The leader can join if it is "close enough":
     144             :    the squared distance between the leader and the cluster isn't
     145             :    greater than the larger of the radii. I.e. the leader is within the
     146             :    cluster's radius or the cluster is within the leader's radius. */
     147             : static inline int
     148             : can_join_cluster( fd_vote_stake_weight_t const * stakes,
     149             :                   ulong const *                  radius,
     150             :                   ulong                          cluster,
     151    33024834 :                   ulong                          leader ) {
     152    33024834 :   return fd_validator_location_dist2(
     153    33024834 :     &stakes[ cluster ].location, &stakes[ leader ].location )<=
     154    33024834 :       fd_ulong_max( radius[ cluster ], radius[ leader ] );
     155    33024834 : }
     156             : 
     157             : static void
     158             : assign_to_clusters( uint *                         sched,
     159             :                     ulong                          sched_cnt,
     160             :                     fd_vote_stake_weight_t const * stakes,
     161             :                     ulong const *                  radius,
     162             :                     fd_geo_cluster_t *             clusters,
     163             :                     uint *                         open_clusters,
     164             :                     uint *                         eligible_clusters,
     165       27990 :                     fd_chacha_rng_t *              rng ) {
     166       27990 :   ulong open_clusters_cnt     = 0UL;
     167       27990 :   ulong finalized_leaders_cnt = 0UL;
     168             : 
     169             :   /* Assign each leader in the schedule to a cluster, overwriting the
     170             :      final leader schedule as we go. */
     171     9011643 :   for( ulong i=0UL; i<sched_cnt; i++ ) {
     172     8983653 :     uint leader = sched[ i ];
     173             : 
     174             :     /* Find all the clusters that the leader can join. */
     175     8983653 :     ulong eligible_cnt = 0UL;
     176    42008487 :     for( ulong j=0UL; j<open_clusters_cnt; j++ ) {
     177    33024834 :       if( can_join_cluster( stakes, radius, open_clusters[ j ], leader ) ) {
     178     6167079 :         eligible_clusters[ eligible_cnt++ ] = (uint)j;
     179     6167079 :       }
     180    33024834 :     }
     181             : 
     182             :     /* Choose one of the eligible clusters for the leader to join.
     183             :        If there are none, start a new one. */
     184     8983653 :     ulong cluster_idx;
     185     8983653 :     if( eligible_cnt==0UL ) {
     186     3042435 :       clusters[ leader ].members_cnt       = 0U;
     187     3042435 :       cluster_idx                          = open_clusters_cnt;
     188     3042435 :       open_clusters[ open_clusters_cnt++ ] = leader;
     189     5941218 :     } else {
     190     5941218 :       cluster_idx = eligible_clusters[ fd_chacha_rng_ulong_roll( rng, eligible_cnt ) ];
     191     5941218 :     }
     192     8983653 :     fd_geo_cluster_t * cluster                 = clusters + open_clusters[ cluster_idx ];
     193     8983653 :     cluster->members[ cluster->members_cnt++ ] = leader;
     194             : 
     195             :     /* If the cluster is now full, shuffle its members and add it to
     196             :        the finalized leader schedule. */
     197     8983653 :     if( cluster->members_cnt==FD_EPOCH_LEADERS_GEO_RUN_LENGTH ) {
     198     2946903 :       fisher_yates_shuffle( rng, cluster->members, cluster->members_cnt );
     199     2946903 :       memcpy( sched+finalized_leaders_cnt, cluster->members, cluster->members_cnt*sizeof(uint) );
     200     2946903 :       finalized_leaders_cnt += cluster->members_cnt;
     201     2946903 :       open_clusters_cnt--;
     202     2946903 :       memmove( open_clusters+cluster_idx, open_clusters+cluster_idx+1UL, (open_clusters_cnt-cluster_idx)*sizeof(uint) );
     203     2946903 :     }
     204     8983653 :   }
     205             : 
     206             :   /* Add any leftover open clusters to the final leader schedule,
     207             :      shuffling them first. */
     208       27990 :   fisher_yates_shuffle( rng, open_clusters, open_clusters_cnt );
     209      123522 :   for( ulong j=0UL; j<open_clusters_cnt; j++ ) {
     210       95532 :     fd_geo_cluster_t * cluster = clusters + open_clusters[ j ];
     211       95532 :     fisher_yates_shuffle( rng, cluster->members, cluster->members_cnt );
     212       95532 :     memcpy( sched+finalized_leaders_cnt, cluster->members, cluster->members_cnt*sizeof(uint) );
     213       95532 :     finalized_leaders_cnt += cluster->members_cnt;
     214       95532 :   }
     215       27990 : }
     216             : 
     217             : /* geo_aware_reorder re-orders the given sched in-place, according to
     218             :    SIMD-0675 Geo-Aware Leader Schedule.
     219             :    - sched: the input leader schedule
     220             :    - sched_cnt: the number of validators in the leader schedule
     221             :    - stakes: metadata about each vote account, including their stake
     222             :              weight and validator location
     223             :    - stakes_cnt: the number of entries in the stakes
     224             :    - epoch: the epoch this leader schedule is computed for
     225             :    - scratch: scratch memory region used for intermediate computations */
     226             : static void
     227             : geo_aware_reorder( uint *                         sched,
     228             :                    ulong                          sched_cnt,
     229             :                    fd_vote_stake_weight_t const * stakes,
     230             :                    ulong                          stakes_cnt,
     231             :                    ulong                          epoch,
     232       27996 :                    void *                         scratch ) {
     233       27996 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
     234             :   /* radius[v] will hold the smallest radius for each validator v which
     235             :      contains FD_EPOCH_LEADERS_GEO_STAKE_FLOOR % of the total stake. */
     236       27996 :   ulong *             radius            = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong),             stakes_cnt*sizeof(ulong) );
     237             :   /* neighbor[ u ] will hold the squared distance from the validator
     238             :      whose smallest radius is being computed to validator u, and u's
     239             :      stake. The same memory is then reused in assign_to_clusters,
     240             :      so we size it for the larger of the two uses. */
     241       27996 :   fd_geo_neighbor_t * neighbor          = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_geo_neighbor_t),
     242       27996 :     stakes_cnt*fd_ulong_max( sizeof(fd_geo_neighbor_t), sizeof(fd_geo_cluster_t) ) );
     243             :   /* open_clusters and eligible_clusters are used in assign_to_clusters,
     244             :      we just reserve the memory here. */
     245       27996 :   uint *              open_clusters     = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),              stakes_cnt*sizeof(uint)  );
     246       27996 :   uint *              eligible_clusters = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),              stakes_cnt*sizeof(uint)  );
     247       27996 :   FD_SCRATCH_ALLOC_FINI( l, 1UL );
     248             : 
     249             :   /* Sum up the total stake and sanity-check that all the validators
     250             :      have a valid location set. As geo_aware_leader_schedule can only
     251             :      be activated after enforce_validator_location_registration, all
     252             :      validators should have a valid location set. */
     253       27996 :   ulong total = 0UL;
     254      907293 :   for( ulong i=0UL; i<stakes_cnt; i++ ) {
     255      879303 :     if( FD_UNLIKELY( !fd_validator_location_is_valid( &stakes[ i ].location ) ) ) {
     256           6 :       FD_BASE58_ENCODE_32_BYTES( stakes[ i ].vote_key.uc, vote_b58 );
     257           6 :       FD_LOG_ERR(( "vote account %s has no valid validator location for epoch %lu", vote_b58, epoch ));
     258           6 :     }
     259      879297 :     total += stakes[ i ].stake;
     260      879297 :   }
     261             : 
     262             :   /* Compute the smallest radius around each validator that contains at
     263             :      least FD_EPOCH_LEADERS_GEO_STAKE_FLOOR % of the total stake,
     264             :      including the validator at the center. */
     265      907278 :   for( ulong v=0UL; v<stakes_cnt; v++ ) {
     266    39893544 :     for( ulong u=0UL; u<stakes_cnt; u++ ) {
     267    39014256 :       neighbor[ u ].dist2 = fd_validator_location_dist2( &stakes[ v ].location, &stakes[ u ].location );
     268    39014256 :       neighbor[ u ].stake = stakes[ u ].stake;
     269    39014256 :     }
     270      879288 :     radius[ v ] = compute_smallest_radius( neighbor, stakes_cnt, total );
     271      879288 :   }
     272             : 
     273             :   /* Use a different rng stream for the geo-aware re-ordering so that
     274             :      it is independent of the order of the original schedule's PRNG
     275             :      scheme, as per SIMD-0675.
     276             : 
     277             :      TODO: is this really necessary? */
     278       27990 :   static char const tag[] = "geo-aware-leader-schedule";
     279       27990 :   uchar             key[ 32 ];
     280       27990 :   fd_sha256_t       sha[1];
     281       27990 :   fd_sha256_init  ( sha );
     282       27990 :   fd_sha256_append( sha, tag,    sizeof(tag)-1UL );
     283       27990 :   fd_sha256_append( sha, &epoch, sizeof(ulong)   );
     284       27990 :   fd_sha256_fini  ( sha, key );
     285             : 
     286       27990 :   fd_chacha_rng_t _rng[1];
     287       27990 :   fd_chacha_rng_t * rng = fd_chacha_rng_join( fd_chacha_rng_new( _rng, FD_CHACHA_RNG_MODE_MOD ) );
     288       27990 :   fd_chacha_rng_init( rng, key, FD_CHACHA_RNG_ALGO_CHACHA20 );
     289             : 
     290             :   /* Group the validators into clusters */
     291             :   /* We re-purpose the neighbor memory region used for the radius
     292             :      calculation, because it is not used anymore after the radius
     293             :      calculations. */
     294       27990 :   fd_geo_cluster_t * clusters = (fd_geo_cluster_t *)fd_type_pun( neighbor );
     295       27990 :   assign_to_clusters( sched, sched_cnt, stakes, radius, clusters, open_clusters, eligible_clusters, rng );
     296             : 
     297       27990 :   fd_chacha_rng_delete( fd_chacha_rng_leave( rng ) );
     298       27990 : }
     299             : 
     300             : int
     301             : fd_epoch_leaders_use_geo_aware( fd_features_t const *       features,
     302             :                                 fd_epoch_schedule_t const * epoch_schedule,
     303         453 :                                 ulong                       schedule_epoch ) {
     304         453 :   ulong activation_slot = features->geo_aware_leader_schedule;
     305         453 :   if( activation_slot==FD_FEATURE_DISABLED ) return 0;
     306          45 :   return schedule_epoch>fd_slot_to_epoch( epoch_schedule, activation_slot, NULL );
     307         453 : }
     308             : 
     309             : void *
     310             : fd_epoch_leaders_new( void  *                  shmem,
     311             :                       ulong                    epoch,
     312             :                       ulong                    slot0,
     313             :                       ulong                    slot_cnt,
     314             :                       ulong                    pub_cnt,
     315             :                       fd_vote_stake_weight_t * stakes,
     316       13752 :                       int                      geo_aware ) {
     317       13752 :   if( FD_UNLIKELY( !shmem ) ) {
     318           0 :     FD_LOG_WARNING(( "NULL shmem" ));
     319           0 :     return NULL;
     320           0 :   }
     321             : 
     322       13752 :   ulong laddr = (ulong)shmem;
     323       13752 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( laddr, FD_EPOCH_LEADERS_ALIGN ) ) ) {
     324           0 :     FD_LOG_WARNING(( "misaligned shmem" ));
     325           0 :     return NULL;
     326           0 :   }
     327             : 
     328       13752 :   if( FD_UNLIKELY( !pub_cnt ) ) {
     329           0 :     FD_LOG_WARNING(( "pub_cnt is 0" ));
     330           0 :     return NULL;
     331           0 :   }
     332             : 
     333       13752 :   if( FD_UNLIKELY( slot_cnt>MAX_SLOTS_PER_EPOCH ) ) {
     334           0 :     FD_LOG_ERR(( "epoch %lu has %lu slots, but the maximum supported is %lu", epoch, slot_cnt, MAX_SLOTS_PER_EPOCH ));
     335           0 :   }
     336             : 
     337             :   /* The eventual layout that we want is:
     338             :      struct                   (align=8, footprint=64)
     339             :      list of indices          (align=4, footprint=4*ceil(slot_cnt/4))
     340             :      (up to 28 bytes of padding to align to 32)
     341             :      vote addr per rotation   (align=32, footprint=32*ceil(slot_cnt/4))
     342             :      (up to 32 bytes of padding to align to 64)
     343             :      list of pubkeys          (align=32, footprint=32*pub_cnt)
     344             :      (possibly 32 bytes of padding to align to 64)
     345             : 
     346             :      The memory for the list of pubkeys is used three times, in this
     347             :      order:
     348             :      1. by wsample, to generate the list of indices.  The region is
     349             :         sized for the larger of 32*pub_cnt and wsample's footprint.
     350             :      2. by the geo-aware re-ordering, as scratch space: at most 32
     351             :         bytes per pubkey.
     352             :      3. for the pubkeys themselves, which we only copy in once both
     353             :         are done.
     354             : 
     355             :      There's a lot of type punning going on here, so watch out. */
     356             : 
     357       13752 :   ulong sched_cnt = (slot_cnt+FD_EPOCH_SLOTS_PER_ROTATION-1UL)/FD_EPOCH_SLOTS_PER_ROTATION;
     358             : 
     359       13752 :   fd_epoch_leaders_t * leaders = (fd_epoch_leaders_t *)fd_type_pun( (void *)laddr );
     360       13752 :   laddr += sizeof(fd_epoch_leaders_t);
     361             : 
     362       13752 :   laddr  = fd_ulong_align_up( laddr, alignof(uint) );
     363       13752 :   uint * sched     = (uint *)fd_type_pun( (void *)laddr );
     364       13752 :   laddr += sizeof(uint)*sched_cnt;
     365             : 
     366       13752 :   laddr  = fd_ulong_align_up( laddr, 32UL );
     367       13752 :   fd_pubkey_t * vote_addr = (fd_pubkey_t *)fd_type_pun( (void *)laddr );
     368       13752 :   laddr += 32UL*sched_cnt;
     369             : 
     370       13752 :   laddr  = fd_ulong_align_up( laddr, fd_ulong_max( sizeof(fd_pubkey_t), FD_WSAMPLE_ALIGN ) );
     371             :   /* These two alias, like a union.  We don't need pubkeys until we're
     372             :      done with wsample. */
     373       13752 :   void        * wsample_mem = (void        *)fd_type_pun( (void *)laddr );
     374       13752 :   fd_pubkey_t * pubkeys     = (fd_pubkey_t *)fd_type_pun( (void *)laddr );
     375             : 
     376       13752 :   FD_STATIC_ASSERT( sizeof(fd_geo_neighbor_t)<=sizeof(fd_geo_cluster_t),                          geo_scratch );
     377       13752 :   FD_STATIC_ASSERT( sizeof(ulong)+sizeof(fd_geo_cluster_t)+2UL*sizeof(uint)<=sizeof(fd_pubkey_t), geo_scratch );
     378             : 
     379       13752 :   FD_TEST( laddr+fd_wsample_footprint( pub_cnt, 0 )<=(ulong)wsample_mem + fd_epoch_leaders_footprint( pub_cnt, slot_cnt ) );
     380             : 
     381             :   /* Create and seed ChaCha20Rng */
     382       13752 :   fd_chacha_rng_t _rng[1];
     383       13752 :   fd_chacha_rng_t * rng = fd_chacha_rng_join( fd_chacha_rng_new( _rng, FD_CHACHA_RNG_MODE_MOD ) );
     384       13752 :   uchar key[ 32 ] = {0};
     385       13752 :   memcpy( key, &epoch, sizeof(ulong) );
     386       13752 :   fd_chacha_rng_init( rng, key, FD_CHACHA_RNG_ALGO_CHACHA20 );
     387             : 
     388       13752 :   void * _wsample = fd_wsample_new_init( wsample_mem, rng, pub_cnt, 0, FD_WSAMPLE_HINT_POWERLAW_NOREMOVE );
     389      314733 :   for( ulong i=0UL; i<pub_cnt; i++ ) _wsample = fd_wsample_new_add( _wsample, stakes[i].stake );
     390       13752 :   fd_wsample_t * wsample = fd_wsample_join( fd_wsample_new_fini( _wsample, 0UL ) );
     391       13752 :   FD_TEST( wsample );
     392             : 
     393             :   /* Generate samples.  We need uints, so we can't use sample_many. */
     394     6848409 :   for( ulong i=0UL; i<sched_cnt; i++ ) sched[ i ] = (uint)fd_wsample_sample( wsample );
     395             : 
     396             :   /* Clean up the wsample object */
     397       13752 :   fd_wsample_delete( fd_wsample_leave( wsample ) );
     398       13752 :   fd_chacha_rng_delete( fd_chacha_rng_leave( rng ) );
     399             : 
     400             :   /* Apply the geo-aware re-ordering */
     401       13752 :   if( geo_aware ) {
     402         990 :     geo_aware_reorder( sched, sched_cnt, stakes, pub_cnt, epoch, wsample_mem );
     403         990 :   }
     404             : 
     405             :   /* Now we can use the space for the pubkeys */
     406      314733 :   for( ulong i=0UL; i<pub_cnt; i++ ) memcpy( pubkeys+i, &stakes[ i ].id_key, 32UL );
     407             : 
     408             :   /* record the leader vote address per rotation */
     409     6848409 :   for( ulong i=0UL; i<sched_cnt; i++ ) memcpy( vote_addr+i, &stakes[ sched[ i ] ].vote_key, 32UL );
     410             : 
     411             :   /* Construct the final struct */
     412       13752 :   leaders->epoch     = epoch;
     413       13752 :   leaders->slot0     = slot0;
     414       13752 :   leaders->slot_cnt  = slot_cnt;
     415       13752 :   leaders->pub       = pubkeys;
     416       13752 :   leaders->pub_cnt   = pub_cnt;
     417       13752 :   leaders->sched     = sched;
     418       13752 :   leaders->sched_cnt = sched_cnt;
     419       13752 :   leaders->vote_addr = vote_addr;
     420             : 
     421       13752 :   return (void *)shmem;
     422       13752 : }
     423             : 
     424             : fd_epoch_leaders_t *
     425       13752 : fd_epoch_leaders_join( void * shleaders ) {
     426       13752 :   return (fd_epoch_leaders_t *)shleaders;
     427       13752 : }
     428             : 
     429             : void *
     430        6360 : fd_epoch_leaders_leave( fd_epoch_leaders_t * leaders ) {
     431        6360 :   return (void *)leaders;
     432        6360 : }
     433             : 
     434             : void *
     435        6360 : fd_epoch_leaders_delete( void * shleaders ) {
     436        6360 :   return shleaders;
     437        6360 : }

Generated by: LCOV version 1.14