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