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 : }
|