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