Line data Source code
1 : #include "fd_tower.h"
2 : #include "../../flamenco/runtime/program/fd_vote_program.h"
3 :
4 0 : #define THRESHOLD_DEPTH ( 8 )
5 0 : #define THRESHOLD_PCT ( 2.0 / 3.0 )
6 : #define SHALLOW_THRESHOLD_DEPTH ( 4 )
7 : #define SHALLOW_THRESHOLD_PCT ( 0.38 )
8 0 : #define SWITCH_PCT ( 0.38 )
9 :
10 : /* Private implementation functions */
11 :
12 : static inline ulong
13 0 : lockout_expiration_slot( fd_tower_vote_t const * vote ) {
14 0 : ulong lockout = 1UL << vote->conf;
15 0 : return vote->slot + lockout;
16 0 : }
17 :
18 : void
19 0 : print( fd_tower_vote_t * tower_votes, ulong root ) {
20 0 : ulong max_slot = 0;
21 :
22 : /* Determine spacing. */
23 :
24 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower_votes );
25 0 : !fd_tower_votes_iter_done_rev( tower_votes, iter );
26 0 : iter = fd_tower_votes_iter_prev( tower_votes, iter ) ) {
27 :
28 0 : max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower_votes, iter )->slot );
29 0 : }
30 :
31 : /* Calculate the number of digits in the maximum slot value. */
32 :
33 0 : int digit_cnt = 0;
34 0 : unsigned long rem = max_slot;
35 0 : do {
36 0 : rem /= 10;
37 0 : ++digit_cnt;
38 0 : } while( rem > 0 );
39 :
40 : /* Print the table header */
41 :
42 0 : printf( "%*s | %s\n", digit_cnt, "slot", "confirmation count" );
43 :
44 : /* Print the divider line */
45 :
46 0 : for( int i = 0; i < digit_cnt; i++ ) {
47 0 : printf( "-" );
48 0 : }
49 0 : printf( " | " );
50 0 : for( ulong i = 0; i < strlen( "confirmation_count" ); i++ ) {
51 0 : printf( "-" );
52 0 : }
53 0 : printf( "\n" );
54 :
55 : /* Print each record in the table */
56 :
57 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower_votes );
58 0 : !fd_tower_votes_iter_done_rev( tower_votes, iter );
59 0 : iter = fd_tower_votes_iter_prev( tower_votes, iter ) ) {
60 :
61 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower_votes, iter );
62 0 : printf( "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf );
63 0 : max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower_votes, iter )->slot );
64 0 : }
65 0 : printf( "%*lu | root\n", digit_cnt, root );
66 0 : printf( "\n" );
67 0 : }
68 :
69 : static inline ulong
70 0 : simulate_vote( fd_tower_vote_t const * votes, ulong slot ) {
71 0 : ulong cnt = fd_tower_votes_cnt( votes );
72 0 : while( cnt ) {
73 :
74 : /* Return early if we can't pop the top tower vote, even if votes
75 : below it are expired. */
76 :
77 0 : if( FD_LIKELY( lockout_expiration_slot( fd_tower_votes_peek_index_const( votes, cnt - 1 ) ) >=
78 0 : slot ) ) {
79 0 : break;
80 0 : }
81 0 : cnt--;
82 0 : }
83 0 : return cnt;
84 0 : }
85 :
86 : static void
87 : tower_votes_from_landed_votes( fd_tower_vote_t * tower_votes,
88 0 : fd_landed_vote_t const * landed_votes ) {
89 0 : for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( landed_votes );
90 0 : !deq_fd_landed_vote_t_iter_done( landed_votes, iter );
91 0 : iter = deq_fd_landed_vote_t_iter_next( landed_votes, iter ) ) {
92 0 : fd_landed_vote_t const * landed_vote = deq_fd_landed_vote_t_iter_ele_const( landed_votes,
93 0 : iter );
94 0 : fd_tower_votes_push_tail( tower_votes,
95 0 : ( fd_tower_vote_t ){
96 0 : .slot = landed_vote->lockout.slot,
97 0 : .conf = landed_vote->lockout.confirmation_count } );
98 0 : }
99 0 : }
100 :
101 : /* Public API */
102 :
103 : /* clang-format off */
104 : void *
105 0 : fd_tower_new( void * shmem ) {
106 0 : if( FD_UNLIKELY( !shmem ) ) {
107 0 : FD_LOG_WARNING(( "NULL mem" ));
108 0 : return NULL;
109 0 : }
110 :
111 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_tower_align() ) ) ) {
112 0 : FD_LOG_WARNING(( "misaligned mem" ));
113 0 : return NULL;
114 0 : }
115 :
116 0 : ulong footprint = fd_tower_footprint();
117 0 : if( FD_UNLIKELY( !footprint ) ) {
118 0 : FD_LOG_WARNING(( "bad mem" ));
119 0 : return NULL;
120 0 : }
121 :
122 0 : fd_memset( shmem, 0, footprint );
123 0 : ulong laddr = (ulong)shmem;
124 0 : fd_tower_t * tower = (void *)laddr;
125 0 : laddr += sizeof( fd_tower_t );
126 :
127 0 : laddr = fd_ulong_align_up( laddr, fd_tower_votes_align() );
128 0 : tower->votes = fd_tower_votes_new( (void *)laddr );
129 0 : laddr += fd_tower_votes_footprint();
130 :
131 0 : laddr = fd_ulong_align_up( laddr, fd_tower_vote_accs_align() );
132 0 : tower->vote_accs = fd_tower_vote_accs_new( (void *)laddr );
133 0 : laddr += fd_tower_vote_accs_footprint();
134 :
135 0 : return shmem;
136 0 : }
137 : /* clang-format on */
138 :
139 : /* clang-format off */
140 : fd_tower_t *
141 0 : fd_tower_join( void * shtower ) {
142 :
143 0 : if( FD_UNLIKELY( !shtower ) ) {
144 0 : FD_LOG_WARNING(( "NULL tower" ));
145 0 : return NULL;
146 0 : }
147 :
148 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shtower, fd_tower_align() ) ) ) {
149 0 : FD_LOG_WARNING(( "misaligned tower" ));
150 0 : return NULL;
151 0 : }
152 :
153 0 : ulong laddr = (ulong)shtower; /* offset from a memory region */
154 0 : fd_tower_t * tower = (void *)shtower;
155 0 : laddr += sizeof(fd_tower_t);
156 :
157 0 : laddr = fd_ulong_align_up( laddr, fd_tower_votes_align() );
158 0 : tower->votes = fd_tower_votes_new( (void *)laddr );
159 0 : laddr += fd_tower_votes_footprint();
160 :
161 0 : laddr = fd_ulong_align_up( laddr, fd_tower_vote_accs_align() );
162 0 : tower->vote_accs = fd_tower_vote_accs_new( (void *)laddr );
163 0 : laddr += fd_tower_vote_accs_footprint();
164 :
165 0 : return (fd_tower_t *)shtower;
166 0 : }
167 : /* clang-format on */
168 :
169 : void *
170 0 : fd_tower_leave( fd_tower_t const * tower ) {
171 :
172 0 : if( FD_UNLIKELY( !tower ) ) {
173 0 : FD_LOG_WARNING(( "NULL tower" ));
174 0 : return NULL;
175 0 : }
176 :
177 0 : return (void *)tower;
178 0 : }
179 :
180 : void *
181 0 : fd_tower_delete( void * tower ) {
182 :
183 0 : if( FD_UNLIKELY( !tower ) ) {
184 0 : FD_LOG_WARNING(( "NULL tower" ));
185 0 : return NULL;
186 0 : }
187 :
188 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)tower, fd_tower_align() ) ) ) {
189 0 : FD_LOG_WARNING(( "misaligned tower" ));
190 0 : return NULL;
191 0 : }
192 :
193 0 : return tower;
194 0 : }
195 :
196 : void
197 : fd_tower_init( fd_tower_t * tower,
198 : fd_pubkey_t const * vote_acc_addr,
199 : fd_acc_mgr_t * acc_mgr,
200 : fd_exec_epoch_ctx_t const * epoch_ctx,
201 : fd_fork_t const * fork,
202 0 : ulong * smr ) {
203 :
204 0 : if( FD_UNLIKELY( !tower ) ) {
205 0 : FD_LOG_WARNING(( "NULL tower" ));
206 0 : return;
207 0 : }
208 :
209 : /* Restore our tower using the vote account state. */
210 :
211 0 : FD_SCRATCH_SCOPE_BEGIN {
212 0 : fd_vote_state_versioned_t vote_state_versioned = { 0 };
213 :
214 0 : fd_vote_state_t * vote_state = fd_tower_vote_state_query( tower,
215 0 : vote_acc_addr,
216 0 : acc_mgr,
217 0 : fork,
218 0 : fd_scratch_virtual(),
219 0 : &vote_state_versioned );
220 0 : if( FD_LIKELY( vote_state ) ) {
221 0 : fd_tower_from_vote_state( tower, vote_state );
222 0 : FD_LOG_NOTICE(( "[%s] loading vote state for vote acc: %s", __func__, FD_BASE58_ENC_32_ALLOCA( vote_acc_addr ) ));
223 0 : } else {
224 0 : FD_LOG_WARNING(( "[%s] didn't find existing vote state for vote acc: %s",
225 0 : __func__,
226 0 : FD_BASE58_ENC_32_ALLOCA( vote_acc_addr ) ));
227 0 : }
228 0 : }
229 0 : FD_SCRATCH_SCOPE_END;
230 :
231 : /* Init vote_accs and total_stake. */
232 :
233 0 : fd_tower_epoch_update( tower, epoch_ctx );
234 :
235 : /* Init the smr. */
236 :
237 0 : tower->smr = smr;
238 0 : }
239 :
240 : int
241 : fd_tower_lockout_check( fd_tower_t const * tower,
242 : fd_fork_t const * fork,
243 0 : fd_ghost_t const * ghost ) {
244 :
245 : /* Simulate a vote to pop off all the votes that have been expired at
246 : the top of the tower. */
247 :
248 0 : ulong cnt = simulate_vote( tower->votes, fork->slot );
249 :
250 : /* By definition, all votes in the tower must be for the same fork, so
251 : check if the top vote of the tower after simulating is on the same
252 : fork as the fork we want to vote for (ie. fork->slot is a
253 : descendant of top vote slot). If the top vote slot is too old (ie.
254 : older than ghost->root), we just assume it is on the same fork. */
255 :
256 0 : fd_tower_vote_t const * top_vote = fd_tower_votes_peek_index_const( tower->votes, cnt - 1 );
257 :
258 0 : int lockout_check = top_vote->slot < ghost->root->slot ||
259 0 : fd_ghost_is_descendant( ghost, fork->slot, top_vote->slot );
260 0 : FD_LOG_NOTICE(( "[fd_tower_lockout_check] ok? %d. top: (slot: %lu, conf: %lu). switch: %lu.",
261 0 : lockout_check,
262 0 : top_vote->slot,
263 0 : top_vote->conf,
264 0 : fork->slot ));
265 0 : return lockout_check;
266 0 : }
267 :
268 : int
269 : fd_tower_switch_check( fd_tower_t const * tower,
270 : fd_fork_t const * fork,
271 0 : fd_ghost_t const * ghost ) {
272 :
273 0 : fd_tower_vote_t const * latest_vote = fd_tower_votes_peek_tail_const( tower->votes );
274 :
275 0 : if( FD_UNLIKELY( latest_vote->slot < ghost->root->slot ) ) {
276 :
277 : /* It is possible our latest vote slot precedes our ghost root. This
278 : can happen, for example, when we restart from a snapshot and set
279 : the ghost root to the snapshot slot (we won't have an ancestry
280 : before the snapshot slot.)
281 :
282 : If this is the case, we assume it's ok to switch. */
283 :
284 0 : return 1;
285 0 : }
286 :
287 : /* Assumption: fd_tower_switch_check is only called if
288 : latest_vote->slot and fork->slot are on different forks (determined
289 : by is_descendant), so they must not fall on the same ancestry path
290 : back to the gca.
291 :
292 : INVALID:
293 :
294 : 0
295 : \
296 : 1 <- a
297 : \
298 : 2 <- b
299 :
300 : VALID:
301 :
302 : 0
303 : / \
304 : 1 2
305 : ^ ^
306 : a b
307 :
308 : */
309 :
310 0 : fd_ghost_node_t const * gca = fd_ghost_gca( ghost, latest_vote->slot, fork->slot );
311 :
312 : /* gca_child is our latest_vote slot's ancestor that is also a direct
313 : child of GCA. So we do not count it towards the stake of the
314 : different forks. */
315 :
316 0 : fd_ghost_node_t const * gca_child = fd_ghost_query( ghost, latest_vote->slot );
317 0 : while( gca_child->parent != gca ) {
318 0 : gca_child = gca_child->parent;
319 0 : }
320 :
321 0 : ulong switch_stake = 0;
322 0 : fd_ghost_node_t const * child = gca->child;
323 0 : while ( FD_LIKELY( child ) ) {
324 0 : if ( FD_LIKELY ( child != gca_child ) ) {
325 0 : switch_stake += child->weight;
326 0 : }
327 0 : child = child->sibling;
328 0 : }
329 :
330 0 : double switch_pct = (double)switch_stake / (double)tower->total_stake;
331 0 : FD_LOG_NOTICE(( "[fd_tower_switch_check] ok? %d. top: %lu. switch: %lu. switch stake: %.0lf%%.",
332 0 : switch_pct > SWITCH_PCT,
333 0 : fd_tower_votes_peek_tail_const( tower->votes )->slot,
334 0 : fork->slot,
335 0 : switch_pct * 100.0 ));
336 0 : return switch_pct > SWITCH_PCT;
337 0 : }
338 :
339 : int
340 : fd_tower_threshold_check( fd_tower_t const * tower,
341 : fd_fork_t const * fork,
342 0 : fd_acc_mgr_t * acc_mgr ) {
343 :
344 : /* First, simulate a vote, popping off everything that would be
345 : expired by voting for the current slot. */
346 :
347 0 : ulong cnt = simulate_vote( tower->votes, fork->slot );
348 :
349 : /* Return early if our tower is not at least THRESHOLD_DEPTH deep
350 : after simulating. */
351 :
352 0 : if( FD_UNLIKELY( cnt < THRESHOLD_DEPTH ) ) return 1;
353 :
354 : /* Get the vote slot from THRESHOLD_DEPTH back. Note THRESHOLD_DEPTH
355 : is the 8th index back _including_ the simulated vote at index 0,
356 : which is not accounted for by `cnt`, so subtracting THRESHOLD_DEPTH
357 : will conveniently index the threshold vote. */
358 :
359 0 : ulong threshold_slot = fd_tower_votes_peek_index( tower->votes, cnt - THRESHOLD_DEPTH )->slot;
360 :
361 : /* Track the amount of stake that has vote slot >= threshold_slot. */
362 :
363 0 : ulong threshold_stake = 0;
364 :
365 : /* Iterate all the vote accounts. */
366 :
367 0 : for( fd_tower_vote_accs_iter_t iter = fd_tower_vote_accs_iter_init( tower->vote_accs );
368 0 : !fd_tower_vote_accs_iter_done( tower->vote_accs, iter );
369 0 : iter = fd_tower_vote_accs_iter_next( tower->vote_accs, iter ) ) {
370 :
371 0 : fd_tower_vote_acc_t * vote_acc = fd_tower_vote_accs_iter_ele( tower->vote_accs, iter );
372 :
373 0 : FD_SCRATCH_SCOPE_BEGIN {
374 :
375 : /* FIXME */
376 0 : fd_vote_state_versioned_t vote_state_versioned = { 0 };
377 :
378 0 : fd_vote_state_t * vote_state = fd_tower_vote_state_query( tower,
379 0 : vote_acc->addr,
380 0 : acc_mgr,
381 0 : fork,
382 0 : fd_scratch_virtual(),
383 0 : &vote_state_versioned );
384 0 : if( FD_UNLIKELY( !vote_state ) ) {
385 0 : FD_LOG_WARNING(( "[%s] failed to load vote acc addr %s. skipping.",
386 0 : __func__,
387 0 : FD_BASE58_ENC_32_ALLOCA( vote_acc->addr ) ));
388 0 : continue;
389 0 : }
390 :
391 0 : fd_landed_vote_t * landed_votes = vote_state->votes;
392 :
393 : /* If the vote account has an empty tower, continue. */
394 :
395 0 : if( FD_UNLIKELY( deq_fd_landed_vote_t_empty( landed_votes ) ) ) continue;
396 :
397 : /* Convert the landed_votes into tower's vote_slots interface. */
398 :
399 0 : void * mem = fd_scratch_alloc( fd_tower_votes_align(), fd_tower_votes_footprint() );
400 0 : fd_tower_vote_t * their_tower_votes = fd_tower_votes_join( fd_tower_votes_new( mem ) );
401 0 : tower_votes_from_landed_votes( their_tower_votes, landed_votes );
402 :
403 0 : ulong cnt = simulate_vote( their_tower_votes, fork->slot );
404 :
405 : /* Continue if their tower is empty after simulating. */
406 :
407 0 : if( FD_UNLIKELY( !cnt ) ) continue;
408 :
409 : /* Get their latest vote slot.*/
410 :
411 0 : fd_tower_vote_t const * vote_slot = fd_tower_votes_peek_index( their_tower_votes, cnt - 1 );
412 :
413 : /* Count their stake towards the threshold check if their latest
414 : vote slot >= our threshold slot.
415 :
416 : Because we are iterating vote accounts on the same fork that we
417 : we want to vote for, we know these slots must all occur along
418 : the same fork ancestry.
419 :
420 : Therefore, if their latest vote slot >= our threshold slot, we
421 : know that vote must be for the threshold slot itself or one of
422 : threshold slot's descendants. */
423 :
424 0 : if( FD_LIKELY( vote_slot->slot >= threshold_slot ) ) {
425 0 : threshold_stake += vote_acc->stake;
426 0 : }
427 0 : }
428 0 : FD_SCRATCH_SCOPE_END;
429 0 : }
430 :
431 0 : double threshold_pct = (double)threshold_stake / (double)tower->total_stake;
432 0 : FD_LOG_NOTICE(( "[fd_tower_threshold_check] ok? %d. top: %lu. threshold: %lu. stake: %.0lf%%.",
433 0 : threshold_pct > THRESHOLD_PCT,
434 0 : fd_tower_votes_peek_tail_const( tower->votes )->slot,
435 0 : threshold_slot,
436 0 : threshold_pct * 100.0 ));
437 0 : return threshold_pct > THRESHOLD_PCT;
438 0 : }
439 :
440 : fd_fork_t const *
441 : fd_tower_best_fork( FD_PARAM_UNUSED fd_tower_t const * tower,
442 : fd_forks_t const * forks,
443 0 : fd_ghost_t const * ghost ) {
444 0 : fd_ghost_node_t const * head = fd_ghost_head( ghost );
445 :
446 : /* Search for the fork head in the frontier. */
447 :
448 0 : fd_fork_t const * best = fd_forks_query_const( forks, head->slot );
449 :
450 0 : #if FD_TOWER_USE_HANDHOLDING
451 0 : if( FD_UNLIKELY( !best ) ) {
452 :
453 : /* If the best fork is not in the frontier, then we must have pruned
454 : it or improperly re-used its fork and we're now in a bad state. */
455 :
456 : /* TODO eqvoc */
457 :
458 0 : FD_LOG_ERR(( "missing ghost head %lu in frontier", head->slot ));
459 0 : }
460 0 : #endif
461 :
462 0 : return best;
463 0 : }
464 :
465 : fd_fork_t const *
466 : fd_tower_reset_fork( fd_tower_t const * tower,
467 : fd_forks_t const * forks,
468 0 : fd_ghost_t const * ghost ) {
469 :
470 : /* If the tower is empty (we haven't voted or every vote was expired),
471 : we simply reset to the best fork. */
472 :
473 0 : if( FD_UNLIKELY( fd_tower_votes_empty( tower->votes ) ) ) {
474 0 : return fd_tower_best_fork( tower, forks, ghost );
475 0 : }
476 :
477 0 : fd_tower_vote_t const * latest_vote = fd_tower_votes_peek_tail_const( tower->votes );
478 :
479 : /* Consider the 2 cases when our latest vote slot does not descend
480 : from ghost root / SMR (see also top-level documentation in
481 : fd_tower.h):
482 :
483 : 1. If we are stuck on a minority fork, we know that we cannot
484 : sensibly build a block based on our last vote because we know a
485 : supermajority of the cluster has rooted a different fork. So we
486 : simply build off the best fork.
487 :
488 : 2. If our latest vote slot is older than SMR, we know we don't have
489 : ancestry information about our latest vote slot anymore, so we
490 : similarly build off the best fork. */
491 :
492 0 : if( FD_UNLIKELY( !fd_ghost_is_descendant( ghost, latest_vote->slot, ghost->root->slot ) ) ) {
493 0 : return fd_tower_best_fork( tower, forks, ghost );
494 0 : }
495 :
496 0 : fd_fork_frontier_t const * frontier = forks->frontier;
497 0 : fd_fork_t const * pool = forks->pool;
498 :
499 0 : for( fd_fork_frontier_iter_t iter = fd_fork_frontier_iter_init( frontier, pool );
500 0 : !fd_fork_frontier_iter_done( iter, frontier, pool );
501 0 : iter = fd_fork_frontier_iter_next( iter, frontier, pool ) ) {
502 0 : fd_fork_t const * fork = fd_fork_frontier_iter_ele_const( iter, frontier, pool );
503 0 : ulong slot = fd_ulong_if( fork->lock, fork->slot_ctx.slot_bank.prev_slot, fork->slot );
504 0 : if( FD_LIKELY( fd_ghost_is_descendant( ghost, slot, latest_vote->slot ) ) ) return fork;
505 0 : }
506 :
507 : /* If we've reached here, we're in a bad state. Log some diagnostics. */
508 :
509 0 : for( fd_fork_frontier_iter_t iter = fd_fork_frontier_iter_init( frontier, pool );
510 0 : !fd_fork_frontier_iter_done( iter, frontier, pool );
511 0 : iter = fd_fork_frontier_iter_next( iter, frontier, pool ) ) {
512 0 : fd_fork_t const * fork = fd_fork_frontier_iter_ele_const( iter, frontier, pool );
513 0 : ulong slot = fd_ulong_if( fork->lock, fork->slot_ctx.slot_bank.prev_slot, fork->slot );
514 :
515 0 : FD_LOG_NOTICE(( "\n\nfork lock? %d\nfork slot %lu\nfork prev slot %lu\nlatest vote slot %lu\n"
516 0 : "descendant slot %lu\ndescends? %d",
517 0 : fork->lock,
518 0 : fork->slot,
519 0 : fork->slot_ctx.slot_bank.prev_slot,
520 0 : latest_vote->slot,
521 0 : slot,
522 0 : fd_ghost_is_descendant( ghost, slot, latest_vote->slot ) ));
523 0 : }
524 :
525 0 : FD_LOG_ERR(( "invariant violation: could not find our latest vote slot in frontier even though there is a valid ancestry in ghost." ));
526 0 : }
527 :
528 : fd_fork_t const *
529 : fd_tower_vote_fork( fd_tower_t * tower,
530 : fd_forks_t const * forks,
531 : fd_acc_mgr_t * acc_mgr,
532 0 : fd_ghost_t const * ghost ) {
533 :
534 0 : fd_fork_t const * vote_fork = NULL;
535 :
536 0 : fd_fork_t const * best = fd_tower_best_fork( tower, forks, ghost );
537 :
538 : /* If the tower is empty (we haven't voted or every vote was expired),
539 : we simply vote for the best fork. */
540 :
541 0 : if( FD_UNLIKELY( fd_tower_votes_empty( tower->votes ) ) ) {
542 0 : return best;
543 0 : }
544 :
545 0 : fd_tower_vote_t const * latest_vote = fd_tower_votes_peek_tail_const( tower->votes );
546 :
547 : /* Consider the 2 cases when our latest vote slot does not descend
548 : from ghost root / SMR (see also top-level documentation in
549 : fd_tower.h):
550 :
551 : 1. If we are stuck on a minority fork, we know the cluster has
552 : rooted a fork that isn't our current vote fork, and we don't
553 : have ancestry information to determine lockout or switch
554 : percentage, so we switch and vote for the current best.
555 :
556 : 2. If our latest vote slot is older than SMR, we know we don't have
557 : ancestry information to determine whether we're locked out or
558 : can switch, so we similarly build off the best fork. */
559 :
560 0 : if( FD_UNLIKELY( !fd_ghost_is_descendant( ghost, latest_vote->slot, ghost->root->slot ) ) ) {
561 0 : return fd_tower_best_fork( tower, forks, ghost );
562 0 : }
563 :
564 : /* Optimize for when there is just one fork (most of the time), which means best fork. */
565 :
566 0 : if( FD_LIKELY( fd_ghost_is_descendant( ghost, best->slot, latest_vote->slot ) ) ) {
567 :
568 : /* The best fork is on the same fork and we can vote for
569 : best_fork->slot if we pass the threshold check. */
570 :
571 0 : if( FD_LIKELY( fd_tower_threshold_check( tower, best, acc_mgr ) ) ) {
572 0 : FD_LOG_NOTICE(( "[fd_tower_vote_fork_select] success (threshold). best: %lu. vote: "
573 0 : "(slot: %lu conf: %lu)",
574 0 : best->slot,
575 0 : latest_vote->slot,
576 0 : latest_vote->conf ));
577 0 : vote_fork = best;
578 0 : } else {
579 0 : FD_LOG_NOTICE(( "[fd_tower_vote_fork_select] failure (threshold). best: %lu. vote: "
580 0 : "(slot: %lu conf: %lu)",
581 0 : best->slot,
582 0 : latest_vote->slot,
583 0 : latest_vote->conf ));
584 0 : }
585 :
586 0 : } else {
587 :
588 : /* The best fork is on a different fork, so try to switch if we pass
589 : lockout and switch threshold. */
590 :
591 0 : if( FD_UNLIKELY( fd_tower_lockout_check( tower, best, ghost ) &&
592 0 : fd_tower_switch_check( tower, best, ghost ) ) ) {
593 0 : FD_LOG_NOTICE(( "[fd_tower_vote_fork_select] success (lockout switch). best: %lu. vote: "
594 0 : "(slot: %lu conf: %lu)",
595 0 : best->slot,
596 0 : latest_vote->slot,
597 0 : latest_vote->conf ));
598 0 : vote_fork = best;
599 0 : } else {
600 0 : FD_LOG_NOTICE(( "[fd_tower_vote_fork_select] failure (lockout switch). best: %lu. vote: "
601 0 : "(slot: %lu conf: %lu)",
602 0 : best->slot,
603 0 : latest_vote->slot,
604 0 : latest_vote->conf ));
605 0 : }
606 0 : }
607 :
608 0 : return vote_fork; /* NULL if we cannot vote. */
609 :
610 : /* Only process vote slots higher than our SMR. */
611 :
612 : // if( FD_LIKELY( latest_vote->root > tower->smr ) ) {
613 :
614 : // /* Find the previous root vote by node pubkey. */
615 :
616 : // fd_root_vote_t * prev_root_vote =
617 : // fd_root_vote_map_query( root_votes, latest_vote->node_pubkey, NULL );
618 :
619 : // if( FD_UNLIKELY( !prev_root_vote ) ) {
620 :
621 : // /* This node pubkey has not yet voted. */
622 :
623 : // prev_root_vote = fd_root_vote_map_insert( root_votes, latest_vote->node_pubkey );
624 : // } else {
625 :
626 : // fd_root_stake_t * root_stake =
627 : // fd_root_stake_map_query( tower->root_stakes, prev_root_vote->root, NULL );
628 : // root_stake->stake -= stake;
629 : // }
630 :
631 : // /* Update our bookkeeping of this node pubkey's root. */
632 :
633 : // prev_root_vote->root = latest_vote->root;
634 :
635 : // /* Add this node pubkey's stake to all slots in the ancestry back to the SMR. */
636 :
637 : // fd_root_stake_t * root_stake =
638 : // fd_root_stake_map_query( tower->root_stakes, latest_vote->root, NULL );
639 : // if( FD_UNLIKELY( !root_stake ) ) {
640 : // root_stake = fd_root_stake_map_insert( tower->root_stakes, latest_vote->root );
641 : // }
642 : // root_stake->stake += stake;
643 : // }
644 : // }
645 :
646 : // if( FD_LIKELY( smr > tower->smr ) ) tower->smr = smr;
647 0 : }
648 :
649 : void
650 0 : fd_tower_epoch_update( fd_tower_t * tower, fd_exec_epoch_ctx_t const * epoch_ctx ) {
651 0 : fd_epoch_bank_t const * epoch_bank = fd_exec_epoch_ctx_epoch_bank_const( epoch_ctx );
652 :
653 0 : ulong total_stake = 0;
654 :
655 0 : for( fd_vote_accounts_pair_t_mapnode_t * curr = fd_vote_accounts_pair_t_map_minimum(
656 0 : epoch_bank->stakes.vote_accounts.vote_accounts_pool,
657 0 : epoch_bank->stakes.vote_accounts.vote_accounts_root );
658 0 : curr;
659 0 : curr = fd_vote_accounts_pair_t_map_successor( epoch_bank->stakes.vote_accounts
660 0 : .vote_accounts_pool,
661 0 : curr ) ) {
662 :
663 0 : #if FD_TOWER_USE_HANDHOLDING
664 0 : if( FD_UNLIKELY( fd_tower_vote_accs_cnt( tower->vote_accs ) ==
665 0 : fd_tower_vote_accs_max( tower->vote_accs ) ) )
666 0 : FD_LOG_ERR(( "fd_tower_vote_accs overflow." ));
667 0 : #endif
668 :
669 0 : if( FD_UNLIKELY( curr->elem.stake > 0UL ) ) {
670 0 : fd_tower_vote_acc_t vote_acc = { .addr = &curr->elem.key, .stake = curr->elem.stake };
671 0 : fd_tower_vote_accs_push_tail( tower->vote_accs, vote_acc );
672 0 : }
673 0 : total_stake += curr->elem.stake;
674 0 : }
675 0 : tower->total_stake = total_stake;
676 0 : }
677 :
678 : void
679 : fd_tower_fork_update( fd_tower_t const * tower,
680 : fd_fork_t const * fork,
681 : fd_acc_mgr_t * acc_mgr,
682 : fd_blockstore_t * blockstore,
683 0 : fd_ghost_t * ghost ) {
684 :
685 : /* Get the parent key. Every slot except the root must have a parent. */
686 :
687 0 : fd_blockstore_start_read( blockstore );
688 0 : ulong parent_slot = fd_blockstore_parent_slot_query( blockstore, fork->slot );
689 0 : #if FD_TOWER_USE_HANDHOLDING
690 : /* we must have a parent slot and bank hash, given we just executed
691 : its child. if not, likely a bug in blockstore pruning. */
692 0 : if( FD_UNLIKELY( parent_slot == FD_SLOT_NULL ) ) {
693 0 : FD_LOG_ERR(( "missing parent slot for curr slot %lu", fork->slot ));
694 0 : };
695 0 : #endif
696 0 : fd_blockstore_end_read( blockstore );
697 :
698 : /* Insert the new fork head into ghost. */
699 :
700 0 : fd_ghost_node_t * ghost_node = fd_ghost_insert( ghost, fork->slot, parent_slot );
701 :
702 0 : #if FD_TOWER_USE_HANDHOLDING
703 0 : if( FD_UNLIKELY( !ghost_node ) ) {
704 0 : FD_LOG_ERR(( "failed to insert ghost node %lu", fork->slot ));
705 0 : }
706 0 : #endif
707 0 : for( fd_tower_vote_accs_iter_t iter = fd_tower_vote_accs_iter_init_rev( tower->vote_accs );
708 0 : !fd_tower_vote_accs_iter_done_rev( tower->vote_accs, iter );
709 0 : iter = fd_tower_vote_accs_iter_prev( tower->vote_accs, iter ) ) {
710 :
711 0 : fd_tower_vote_acc_t * vote_acc = fd_tower_vote_accs_iter_ele( tower->vote_accs, iter );
712 :
713 0 : FD_SCRATCH_SCOPE_BEGIN {
714 :
715 : /* FIXME */
716 0 : fd_vote_state_versioned_t vote_state_versioned = { 0 };
717 :
718 0 : fd_vote_state_t * vote_state = fd_tower_vote_state_query( tower,
719 0 : vote_acc->addr,
720 0 : acc_mgr,
721 0 : fork,
722 0 : fd_scratch_virtual(),
723 0 : &vote_state_versioned );
724 0 : if( FD_UNLIKELY( !vote_state ) ) {
725 0 : FD_LOG_WARNING(( "[%s] failed to load vote acc addr %s. skipping.",
726 0 : __func__,
727 0 : FD_BASE58_ENC_32_ALLOCA( vote_acc->addr ) ));
728 0 : continue;
729 0 : }
730 :
731 0 : fd_landed_vote_t * landed_votes = vote_state->votes;
732 :
733 : /* If the vote account has an empty tower, continue. */
734 :
735 0 : if( FD_UNLIKELY( deq_fd_landed_vote_t_empty( landed_votes ) ) ) continue;
736 :
737 : /* Get the vote account's latest vote. */
738 :
739 0 : ulong vote_slot = deq_fd_landed_vote_t_peek_tail_const( landed_votes )->lockout.slot;
740 :
741 0 : if( FD_UNLIKELY( vote_slot < ghost->root->slot ) ) continue;
742 :
743 : /* Only process votes for slots >= root. Ghost requires vote slot
744 : to already exist in the ghost tree. */
745 :
746 0 : if( FD_UNLIKELY( vote_slot >= ghost->root->slot ) ) {
747 0 : fd_ghost_node_t const * node = fd_ghost_replay_vote( ghost, vote_slot, &vote_state->node_pubkey, vote_acc->stake );
748 :
749 : /* Check if it has crossed the equivocation safety and optimistic confirmation thresholds. */
750 :
751 0 : fd_blockstore_start_write( blockstore );
752 0 : fd_block_map_t * block_map_entry = fd_blockstore_block_map_query( blockstore, vote_slot );
753 :
754 0 : int eqvocsafe = fd_uchar_extract_bit( block_map_entry->flags, FD_BLOCK_FLAG_EQVOCSAFE );
755 0 : if( FD_UNLIKELY( !eqvocsafe ) ) {
756 0 : double pct = (double)node->stake / (double)ghost->total_stake;
757 0 : if( FD_UNLIKELY( pct > FD_EQVOCSAFE_PCT ) ) {
758 0 : FD_LOG_NOTICE( ( "equivocation safe %lu", block_map_entry->slot ) );
759 0 : block_map_entry->flags = fd_uchar_set_bit( block_map_entry->flags, FD_BLOCK_FLAG_EQVOCSAFE );
760 0 : blockstore->hcs = fd_ulong_max( blockstore->hcs, block_map_entry->slot );
761 0 : }
762 0 : }
763 :
764 0 : int confirmed = fd_uchar_extract_bit( block_map_entry->flags, FD_BLOCK_FLAG_CONFIRMED );
765 0 : if( FD_UNLIKELY( !confirmed ) ) {
766 0 : double pct = (double)node->stake / (double)ghost->total_stake;
767 0 : if( FD_UNLIKELY( pct > FD_CONFIRMED_PCT ) ) {
768 0 : FD_LOG_NOTICE( ( "confirming %lu", block_map_entry->slot ) );
769 0 : block_map_entry->flags = fd_uchar_set_bit( block_map_entry->flags, FD_BLOCK_FLAG_CONFIRMED );
770 0 : blockstore->hcs = fd_ulong_max( blockstore->hcs, block_map_entry->slot );
771 0 : }
772 0 : }
773 :
774 0 : fd_blockstore_end_write( blockstore );
775 0 : }
776 :
777 : /* Check if this voter's root >= ghost root. We can't process
778 : other voters' roots that precede the ghost root. */
779 :
780 0 : if( FD_UNLIKELY( vote_state->root_slot >= ghost->root->slot ) ) {
781 0 : fd_ghost_node_t const * node = fd_ghost_rooted_vote( ghost, vote_state->root_slot, &vote_state->node_pubkey, vote_acc->stake );
782 :
783 : /* Check if it has crossed finalized threshold. */
784 :
785 0 : fd_blockstore_start_write( blockstore );
786 0 : fd_block_map_t * block_map_entry = fd_blockstore_block_map_query( blockstore, vote_state->root_slot );
787 0 : int finalized = fd_uchar_extract_bit( block_map_entry->flags, FD_BLOCK_FLAG_FINALIZED );
788 0 : if( FD_UNLIKELY( !finalized ) ) {
789 0 : double pct = (double)node->rooted_stake / (double)ghost->total_stake;
790 0 : if( FD_UNLIKELY( pct > FD_FINALIZED_PCT ) ) {
791 0 : ulong smr = block_map_entry->slot;
792 0 : FD_LOG_NOTICE(( "finalizing %lu", block_map_entry->slot ));
793 0 : fd_block_map_t * ancestor = block_map_entry;
794 0 : while( ancestor ) {
795 0 : ancestor->flags = fd_uchar_set_bit( ancestor->flags, FD_BLOCK_FLAG_FINALIZED );
796 0 : ancestor = fd_blockstore_block_map_query( blockstore, ancestor->parent_slot );
797 0 : }
798 0 : #if FD_TOWER_USE_HANDHOLDING
799 0 : if( FD_UNLIKELY( smr <= fd_fseq_query( tower->smr ) ) ) {
800 0 : FD_LOG_ERR(( "invariant violation. newly observed SMR %lu <= existing fseq SMR %lu.",
801 0 : smr,
802 0 : fd_fseq_query( tower->smr ) ));
803 0 : }
804 0 : #endif
805 0 : fd_fseq_update( tower->smr, smr );
806 0 : }
807 0 : }
808 0 : fd_blockstore_end_write( blockstore );
809 0 : }
810 0 : }
811 0 : FD_SCRATCH_SCOPE_END;
812 0 : }
813 0 : }
814 :
815 : ulong
816 0 : fd_tower_simulate_vote( fd_tower_t const * tower, ulong slot ) {
817 0 : return simulate_vote( tower->votes, slot );
818 0 : }
819 :
820 : void
821 0 : fd_tower_vote( fd_tower_t const * tower, ulong slot ) {
822 0 : FD_LOG_NOTICE(( "[fd_tower_vote] voting for slot %lu", slot ));
823 :
824 : /* Check we're not voting for the exact same slot as our latest tower
825 : vote. This can happen when there are forks. */
826 :
827 0 : fd_tower_vote_t * latest_vote = fd_tower_votes_peek_tail( tower->votes );
828 0 : if( FD_UNLIKELY( latest_vote && latest_vote->slot == slot ) ) {
829 0 : FD_LOG_NOTICE(( "[fd_tower_vote] already voted for slot %lu", slot ));
830 0 : return;
831 0 : }
832 :
833 0 : #if FD_TOWER_USE_HANDHOLDING
834 :
835 : /* Check we aren't voting for a slot earlier than the latest tower
836 : vote. This should not happen and indicates a bug, because on the
837 : same vote fork the slot should be monotonically non-decreasing. */
838 :
839 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower->votes );
840 0 : !fd_tower_votes_iter_done_rev( tower->votes, iter );
841 0 : iter = fd_tower_votes_iter_prev( tower->votes, iter ) ) {
842 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower->votes, iter );
843 0 : if( FD_UNLIKELY( slot == vote->slot ) ) {
844 0 : fd_tower_print( tower );
845 0 : FD_LOG_ERR(( "[fd_tower_vote] double-voting for old slot %lu (new vote: %lu)",
846 0 : slot,
847 0 : vote->slot ));
848 0 : }
849 0 : }
850 :
851 0 : #endif
852 :
853 : /* First, simulate a vote for slot. We do this purely for
854 : implementation convenience and code reuse.
855 :
856 : As the name of this function indicates, we are not just
857 : simulating and in fact voting for this fork by pushing this a new
858 : vote onto the tower. */
859 :
860 0 : ulong cnt = simulate_vote( tower->votes, slot );
861 :
862 : /* Pop everything that got expired. */
863 :
864 0 : while( fd_tower_votes_cnt( tower->votes ) > cnt ) {
865 0 : fd_tower_votes_pop_tail( tower->votes );
866 0 : }
867 :
868 : /* Increase confirmations (double lockouts) in consecutive votes. */
869 :
870 0 : ulong prev_conf = 0;
871 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower->votes );
872 0 : !fd_tower_votes_iter_done_rev( tower->votes, iter );
873 0 : iter = fd_tower_votes_iter_prev( tower->votes, iter ) ) {
874 0 : fd_tower_vote_t * vote = fd_tower_votes_iter_ele( tower->votes, iter );
875 0 : if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) {
876 0 : break;
877 0 : }
878 0 : vote->conf++;
879 0 : }
880 :
881 : /* Add the new vote to the tower. */
882 :
883 0 : fd_tower_votes_push_tail( tower->votes, ( fd_tower_vote_t ){ .slot = slot, .conf = 1 } );
884 0 : }
885 :
886 : void
887 0 : fd_tower_print( fd_tower_t const * tower ) {
888 0 : print( tower->votes, tower->root );
889 0 : }
890 :
891 : int
892 0 : fd_tower_vote_state_cmp( fd_tower_t const * tower, fd_vote_state_t * vote_state ) {
893 0 : #if FD_TOWER_USE_HANDHOLDING
894 0 : if( FD_UNLIKELY( !tower->root ) ) {
895 0 : FD_LOG_ERR(( "[%s] tower is missing root.", __func__ ));
896 0 : }
897 :
898 0 : if( FD_UNLIKELY( fd_tower_votes_empty( tower->votes ) ) ) {
899 0 : FD_LOG_ERR(( "[%s] tower is empty.", __func__ ));
900 0 : }
901 :
902 0 : if( FD_UNLIKELY( !vote_state->has_root_slot ) ) {
903 0 : FD_LOG_ERR(( "[%s] vote_state is missing root.", __func__ ));
904 0 : }
905 :
906 0 : if( FD_UNLIKELY( deq_fd_landed_vote_t_empty( vote_state->votes ) ) ) {
907 0 : FD_LOG_ERR(( "[%s] vote_state is empty.", __func__ ));
908 0 : }
909 0 : #endif
910 :
911 0 : ulong local = fd_tower_votes_peek_tail_const( tower->votes )->slot;
912 0 : ulong cluster = deq_fd_landed_vote_t_peek_tail_const( vote_state->votes )->lockout.slot;
913 0 : return fd_int_if( local == cluster, 0, fd_int_if( local > cluster, 1, -1 ) );
914 0 : }
915 :
916 : fd_vote_state_t *
917 : fd_tower_vote_state_query( FD_PARAM_UNUSED fd_tower_t const * tower,
918 : fd_pubkey_t const * vote_acc_addr,
919 : fd_acc_mgr_t * acc_mgr,
920 : fd_fork_t const * fork,
921 : fd_valloc_t valloc,
922 0 : fd_vote_state_versioned_t * versioned ) {
923 0 : int rc;
924 :
925 0 : FD_BORROWED_ACCOUNT_DECL( vote_acc );
926 0 : rc = fd_acc_mgr_view( acc_mgr, fork->slot_ctx.funk_txn, vote_acc_addr, vote_acc );
927 0 : if( FD_UNLIKELY( rc == FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) {
928 0 : FD_LOG_WARNING(( "[%s] fd_acc_mgr_view could not find vote account %s. error: %d",
929 0 : __func__,
930 0 : FD_BASE58_ENC_32_ALLOCA( vote_acc_addr ),
931 0 : rc ));
932 0 : return NULL;
933 0 : } else if( FD_UNLIKELY( rc != FD_ACC_MGR_SUCCESS ) ) {
934 0 : FD_LOG_ERR(( "[%s] fd_acc_mgr_view failed on vote account %s. error: %d",
935 0 : __func__,
936 0 : FD_BASE58_ENC_32_ALLOCA( vote_acc_addr ),
937 0 : rc ));
938 0 : }
939 :
940 0 : rc = fd_vote_get_state( vote_acc, valloc, versioned );
941 0 : if( FD_UNLIKELY( rc != FD_ACC_MGR_SUCCESS ) ) {
942 0 : FD_LOG_ERR(( "[%s] fd_vote_get_state failed on vote account %s. error: %d",
943 0 : __func__,
944 0 : FD_BASE58_ENC_32_ALLOCA( vote_acc_addr ),
945 0 : rc ));
946 0 : }
947 :
948 0 : fd_vote_convert_to_current( versioned, valloc );
949 :
950 0 : return &versioned->inner.current;
951 0 : }
952 :
953 : void
954 0 : fd_tower_from_vote_state( fd_tower_t * tower, fd_vote_state_t * vote_state ) {
955 0 : #if FD_TOWER_USE_HANDHOLDING
956 0 : if( FD_UNLIKELY( tower->root ) ) {
957 0 : FD_LOG_WARNING(( "[%s] overwriting existing tower root %lu.", __func__, tower->root ));
958 0 : }
959 :
960 0 : if( FD_UNLIKELY( !fd_tower_votes_empty( tower->votes ) ) ) {
961 0 : FD_LOG_WARNING(( "[%s] overwriting existing tower votes.", __func__ ));
962 0 : }
963 :
964 0 : if( FD_UNLIKELY( !vote_state->has_root_slot ) ) {
965 0 : FD_LOG_WARNING(( "[%s] vote_state is missing root.", __func__ ));
966 0 : }
967 :
968 0 : if( FD_UNLIKELY( deq_fd_landed_vote_t_empty( vote_state->votes ) ) ) {
969 0 : FD_LOG_WARNING(( "[%s] vote_state is empty.", __func__ ));
970 0 : }
971 :
972 0 : #endif
973 :
974 0 : tower_votes_from_landed_votes( tower->votes, vote_state->votes );
975 0 : tower->root = vote_state->root_slot;
976 0 : }
977 :
978 : // ulong i = fd_tower_votes_cnt( tower->votes );
979 : // while ( fd_tower_vote(const fd_tower_t *tower, ulong slot) )
980 :
981 : // /* If the local view of our tower is a strict subset or divergent from
982 : // the cluster view, then we need to sync with the cluster ie. replace
983 : // our local state with the cluster state. */
984 :
985 : // int sync = 0;
986 : // ulong i = 0;
987 : // for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( vote_state->votes );
988 : // !deq_fd_landed_vote_t_iter_done( vote_state->votes, iter );
989 : // iter = deq_fd_landed_vote_t_iter_next( vote_state->votes, iter ) ) {
990 : // fd_landed_vote_t const * landed_vote =
991 : // deq_fd_landed_vote_t_iter_ele_const( vote_state->votes, iter );
992 : // fd_tower_vote_t const * tower_vote = fd_tower_votes_peek_index_const( tower->votes, i++ );
993 :
994 : // if( FD_UNLIKELY( !tower_vote ) ) {
995 :
996 : // /* Local tower is shorter, need sync*/
997 :
998 : // sync = 1;
999 : // }
1000 :
1001 : // if( FD_UNLIKELY( tower_vote->slot != landed_vote->lockout.slot ||
1002 : // tower_vote->conf != landed_vote->lockout.confirmation_count ) ) {
1003 :
1004 : // /* Local tower diverges, need sync */
1005 :
1006 : // sync = 1;
1007 : // }
1008 : // }
1009 :
1010 : // /* Note we don't sync if the local view of the tower is taller.
1011 : // Syncing can lead to a problem where we can otherwise get "stuck" in
1012 : // a cluster-sync loop when our votes don't land. For example, if our
1013 : // tower is currently [1 3 5], and we vote [1 3 5 7]. That vote
1014 : // doesn't land, so we re-sync and as a result vote [1 3 5 9].
1015 : // Instead we would prefer [1 3 5 7 9] for the second vote. */
1016 :
1017 : // if( FD_UNLIKELY( sync ) ) {
1018 :
1019 : // FD_LOG_WARNING(("syncing with cluster"));
1020 :
1021 : // /* Sync local with cluster. */
1022 :
1023 : // fd_tower_votes_remove_all( tower->votes );
1024 : // tower_votes_from_landed_votes( tower->votes, vote_state->votes );
1025 : // tower->root = vote_state->root_slot;
1026 : // }
1027 :
1028 : void
1029 : fd_tower_to_tower_sync( fd_tower_t const * tower,
1030 : fd_hash_t const * bank_hash,
1031 0 : fd_compact_vote_state_update_t * tower_sync ) {
1032 0 : tower_sync->root = tower->root;
1033 0 : long ts = fd_log_wallclock();
1034 0 : tower_sync->has_timestamp = 1;
1035 0 : tower_sync->timestamp = ts;
1036 0 : tower_sync->lockouts_len = (ushort)fd_tower_votes_cnt( tower->votes );
1037 0 : tower_sync->lockouts = (fd_lockout_offset_t *)
1038 0 : fd_scratch_alloc( alignof( fd_lockout_offset_t ),
1039 0 : tower_sync->lockouts_len * sizeof( fd_lockout_offset_t ) );
1040 :
1041 0 : ulong i = 0UL;
1042 0 : ulong curr_slot = tower_sync->root;
1043 :
1044 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower->votes );
1045 0 : !fd_tower_votes_iter_done( tower->votes, iter );
1046 0 : iter = fd_tower_votes_iter_next( tower->votes, iter ) ) {
1047 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower->votes, iter );
1048 0 : FD_TEST( vote->slot >= tower_sync->root );
1049 0 : ulong offset = vote->slot - curr_slot;
1050 0 : curr_slot = vote->slot;
1051 0 : uchar conf = (uchar)vote->conf;
1052 0 : tower_sync->lockouts[i].offset = offset;
1053 0 : tower_sync->lockouts[i].confirmation_count = conf;
1054 0 : memcpy( tower_sync->hash.uc, bank_hash, sizeof( fd_hash_t ) );
1055 0 : i++;
1056 0 : }
1057 0 : }
|