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