Line data Source code
1 : #include <stdio.h>
2 : #include <string.h>
3 :
4 : #include "fd_tower.h"
5 : #include "../../flamenco/txn/fd_txn_generate.h"
6 : #include "../../flamenco/runtime/fd_system_ids.h"
7 :
8 0 : #define THRESHOLD_DEPTH (8)
9 0 : #define THRESHOLD_RATIO (2.0 / 3.0)
10 : #define SWITCH_RATIO (0.38)
11 :
12 : static fd_vote_acc_vote_t const *
13 0 : v4_off( fd_vote_acc_t const * voter ) {
14 0 : return (fd_vote_acc_vote_t const *)( voter->v4.bls_pubkey_compressed + voter->v4.has_bls_pubkey_compressed * sizeof(voter->v4.bls_pubkey_compressed) + sizeof(ulong) );
15 0 : }
16 :
17 : /* expiration calculates the expiration slot of vote given a slot and
18 : confirmation count. */
19 :
20 : static inline ulong
21 0 : expiration_slot( fd_tower_t const * vote ) {
22 0 : ulong lockout = 1UL << vote->conf;
23 0 : return vote->slot + lockout;
24 0 : }
25 :
26 : /* simulate_vote simulates voting for slot, popping all votes from the
27 : top that would be consecutively expired by voting for slot. */
28 :
29 : static ulong
30 : simulate_vote( fd_tower_t const * tower,
31 0 : ulong slot ) {
32 0 : ulong cnt = fd_tower_cnt( tower );
33 0 : while( cnt ) {
34 0 : fd_tower_t const * top_vote = fd_tower_peek_index_const( tower, cnt - 1 );
35 0 : if( FD_LIKELY( expiration_slot( top_vote ) >= slot ) ) break; /* expire only if consecutive */
36 0 : cnt--;
37 0 : }
38 0 : return cnt;
39 0 : }
40 :
41 : /* push_vote pushes a new vote for slot onto the tower. Pops and
42 : returns the new root (bottom of the tower) if it reaches max lockout
43 : as a result of the new vote. Otherwise, returns ULONG_MAX.
44 :
45 : Max lockout is equivalent to 1 << FD_TOWER_VOTE_MAX + 1 (which
46 : implies confirmation count is FD_TOWER_VOTE_MAX + 1). As a result,
47 : fd_tower_vote also maintains the invariant that the tower contains at
48 : most FD_TOWER_VOTE_MAX votes, because (in addition to vote expiry)
49 : there will always be a pop before reaching FD_TOWER_VOTE_MAX + 1. */
50 :
51 : static ulong
52 : push_vote( fd_tower_t * tower,
53 0 : ulong slot ) {
54 :
55 : /* Sanity check: slot should always be greater than previous vote slot in tower. */
56 :
57 0 : fd_tower_t const * vote = fd_tower_peek_tail_const( tower );
58 0 : if( FD_UNLIKELY( vote && slot <= vote->slot ) ) FD_LOG_CRIT(( "[%s] slot %lu <= vote->slot %lu", __func__, slot, vote->slot ));
59 :
60 : /* Use simulate_vote to determine how many expired votes to pop. */
61 :
62 0 : ulong cnt = simulate_vote( tower, slot );
63 :
64 : /* Pop everything that got expired. */
65 :
66 0 : while( FD_LIKELY( fd_tower_cnt( tower ) > cnt ) ) {
67 0 : fd_tower_pop_tail( tower );
68 0 : }
69 :
70 : /* If the tower is still full after expiring, then pop and return the
71 : bottom vote slot as the new root because this vote has incremented
72 : it to max lockout. Otherwise this is a no-op and there is no new
73 : root (ULONG_MAX). */
74 :
75 0 : ulong root = ULONG_MAX;
76 0 : if( FD_LIKELY( fd_tower_full( tower ) ) ) { /* optimize for full tower */
77 0 : root = fd_tower_pop_head( tower ).slot;
78 0 : }
79 :
80 : /* Increment confirmations (double lockouts) for consecutive
81 : confirmations in prior votes. */
82 :
83 0 : ulong prev_conf = 0;
84 0 : for( fd_tower_iter_t iter = fd_tower_iter_init_rev( tower );
85 0 : !fd_tower_iter_done_rev( tower, iter );
86 0 : iter = fd_tower_iter_prev ( tower, iter ) ) {
87 0 : fd_tower_t * vote = fd_tower_iter_ele( tower, iter );
88 0 : if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) break;
89 0 : vote->conf++;
90 0 : }
91 :
92 : /* Add the new vote to the tower. */
93 :
94 0 : fd_tower_push_tail( tower, (fd_tower_t){ .slot = slot, .conf = 1 } );
95 :
96 : /* Return the new root (FD_SLOT_NULL if there is none). */
97 :
98 0 : return root;
99 0 : }
100 :
101 : /* lockout_check checks if we are locked out from voting for slot.
102 : Returns 1 if we can vote for slot without violating lockout, 0
103 : otherwise.
104 :
105 : After voting for a slot n, we are locked out for 2^k slots, where k
106 : is the confirmation count of that vote. Once locked out, we cannot
107 : vote for a different fork until that previously-voted fork expires at
108 : slot n+2^k. This implies the earliest slot in which we can switch
109 : from the previously-voted fork is (n+2^k)+1. We use `ghost` to
110 : determine whether `slot` is on the same or different fork as previous
111 : vote slots.
112 :
113 : In the case of the tower, every vote has its own expiration slot
114 : depending on confirmations. The confirmation count is the max number
115 : of consecutive votes that have been pushed on top of the vote, and
116 : not necessarily its current height in the tower.
117 :
118 : For example, the following is a diagram of a tower pushing and
119 : popping with each vote:
120 :
121 :
122 : slot | confirmation count
123 : -----|-------------------
124 : 4 | 1 <- vote
125 : 3 | 2
126 : 2 | 3
127 : 1 | 4
128 :
129 :
130 : slot | confirmation count
131 : -----|-------------------
132 : 9 | 1 <- vote
133 : 2 | 3
134 : 1 | 4
135 :
136 :
137 : slot | confirmation count
138 : -----|-------------------
139 : 10 | 1 <- vote
140 : 9 | 2
141 : 2 | 3
142 : 1 | 4
143 :
144 :
145 : slot | confirmation count
146 : -----|-------------------
147 : 11 | 1 <- vote
148 : 10 | 2
149 : 9 | 3
150 : 2 | 4
151 : 1 | 5
152 :
153 :
154 : slot | confirmation count
155 : -----|-------------------
156 : 18 | 1 <- vote
157 : 2 | 4
158 : 1 | 5
159 :
160 :
161 : In the final tower, note the gap in confirmation counts between slot
162 : 18 and slot 2, even though slot 18 is directly above slot 2. */
163 :
164 : static int
165 : lockout_check( fd_tower_t const * tower,
166 : fd_tower_blocks_t * forks,
167 0 : ulong slot ) {
168 :
169 0 : if( FD_UNLIKELY( fd_tower_empty( tower ) ) ) return 1; /* always not locked out if we haven't voted. */
170 0 : if( FD_UNLIKELY( slot <= fd_tower_peek_tail_const( tower )->slot ) ) return 0; /* always locked out from voting for slot <= last vote slot */
171 :
172 : /* Simulate a vote to pop off all the votes that would be expired by
173 : voting for slot. Then check if the newly top-of-tower vote is on
174 : the same fork as slot (if so this implies we can vote for it). */
175 :
176 0 : ulong cnt = simulate_vote( tower, slot ); /* pop off votes that would be expired */
177 0 : if( FD_UNLIKELY( !cnt ) ) return 1; /* tower is empty after popping expired votes */
178 :
179 0 : fd_tower_t const * vote = fd_tower_peek_index_const( tower, cnt - 1 ); /* newly top-of-tower */
180 0 : int lockout = fd_tower_blocks_is_slot_descendant( forks, vote->slot, slot ); /* check if on same fork */
181 0 : FD_LOG_INFO(( "[%s] lockout? %d. last_vote_slot: %lu. slot: %lu", __func__, lockout, vote->slot, slot ));
182 0 : return lockout;
183 0 : }
184 :
185 : /* switch_check checks if we can switch to the fork of `slot`. Returns
186 : 1 if we can switch, 0 otherwise. Assumes tower is non-empty.
187 :
188 : There are two forks of interest: our last vote fork ("vote fork") and
189 : the fork we want to switch to ("switch fork"). The switch fork is on
190 : the fork of `slot`.
191 :
192 : In order to switch, FD_TOWER_SWITCH_PCT of stake must have voted for
193 : a slot that satisfies the following conditions: the
194 : GCA(slot, last_vote) is an ancestor of the switch_slot
195 :
196 : Recall from the lockout check a validator is locked out from voting
197 : for our last vote slot when their last vote slot is on a different
198 : fork, and that vote's expiration slot > our last vote slot.
199 :
200 : The following pseudocode describes the algorithm:
201 :
202 : ```
203 : for every fork f in the fork tree, take the most recently executed
204 : slot `s` (the leaf of the fork).
205 :
206 : Take the greatest common ancestor of the `s` and the our last vote
207 : slot. If the switch_slot is a descendant of this GCA, then votes for
208 : `s` can count towards the switch threshold.
209 :
210 : query banks(`s`) for vote accounts in `s`
211 : for all vote accounts v in `s`
212 : if v's locked out[1] from voting for our latest vote slot
213 : add v's stake to switch stake
214 :
215 : return switch stake >= FD_TOWER_SWITCH_PCT
216 : ```
217 :
218 : The switch check is used to safeguard optimistic confirmation.
219 : Specifically: FD_TOWER_OPT_CONF_PCT + FD_TOWER_SWITCH_PCT >= 1. */
220 :
221 : static int
222 : switch_check( fd_tower_t const * tower,
223 : fd_tower_blocks_t * blocks,
224 : fd_tower_leaves_t * leaves,
225 : fd_tower_lockos_t * lockos,
226 : fd_tower_stakes_t * stakes,
227 : ulong total_stake,
228 0 : ulong switch_slot ) {
229 :
230 0 : ulong switch_stake = 0;
231 0 : ulong last_vote_slot = fd_tower_peek_tail_const( tower )->slot;
232 0 : ulong root_slot = fd_tower_peek_head_const( tower )->slot;
233 0 : for ( fd_tower_leaves_dlist_iter_t iter = fd_tower_leaves_dlist_iter_fwd_init( leaves->dlist, leaves->pool );
234 0 : !fd_tower_leaves_dlist_iter_done( iter, leaves->dlist, leaves->pool );
235 0 : iter = fd_tower_leaves_dlist_iter_fwd_next( iter, leaves->dlist, leaves->pool ) ) {
236 :
237 : /* Iterate over all the leaves of all forks */
238 :
239 0 : fd_tower_leaf_t * leaf = fd_tower_leaves_dlist_iter_ele( iter, leaves->dlist, leaves->pool );
240 0 : ulong candidate_slot = leaf->slot;
241 0 : ulong lca = fd_tower_blocks_lowest_common_ancestor( blocks, candidate_slot, last_vote_slot );
242 0 : if( FD_UNLIKELY( lca==ULONG_MAX ) ) continue; /* unlikely but this leaf is an already pruned minority fork */
243 :
244 0 : if( FD_UNLIKELY( fd_tower_blocks_is_slot_descendant( blocks, lca, switch_slot ) ) ) {
245 :
246 : /* This candidate slot may be considered for the switch proof, if
247 : it passes the following conditions:
248 :
249 : https://github.com/anza-xyz/agave/blob/c7b97bc77addacf03b229c51b47c18650d909576/core/src/consensus.rs#L1117
250 :
251 : Now for this candidate slot, look at the lockouts that were
252 : created at the time that we processed the bank for this
253 : candidate slot. */
254 :
255 0 : for( fd_tower_lockos_slot_t const * slot = fd_tower_lockos_slot_map_ele_query_const( lockos->slot_map, &candidate_slot, NULL, lockos->slot_pool );
256 0 : slot;
257 0 : slot = fd_tower_lockos_slot_map_ele_next_const ( slot, NULL, lockos->slot_pool ) ) {
258 0 : ulong interval_end = slot->interval_end;
259 0 : ulong key = fd_tower_lockos_interval_key( candidate_slot, interval_end );
260 :
261 : /* Intervals are keyed by the end of the interval. If the end of
262 : the interval is < the last vote slot, then these vote
263 : accounts with this particular lockout are NOT locked out from
264 : voting for the last vote slot, which means we can skip this
265 : set of intervals. */
266 :
267 0 : if( FD_LIKELY( interval_end < last_vote_slot ) ) continue;
268 :
269 : /* At this point we can actually query for the intervals by
270 : end interval to get the vote accounts. */
271 :
272 0 : for( fd_tower_lockos_interval_t const * interval = fd_tower_lockos_interval_map_ele_query_const( lockos->interval_map, &key, NULL, lockos->interval_pool );
273 0 : interval;
274 0 : interval = fd_tower_lockos_interval_map_ele_next_const( interval, NULL, lockos->interval_pool ) ) {
275 0 : ulong vote_slot = interval->start;
276 0 : fd_hash_t const * vote_acc = &interval->addr;
277 :
278 0 : if( FD_UNLIKELY( !fd_tower_blocks_is_slot_descendant( blocks, vote_slot, last_vote_slot ) && vote_slot > root_slot ) ) {
279 0 : fd_tower_stakes_vtr_xid_t key = { .addr = *vote_acc, .slot = switch_slot };
280 0 : fd_tower_stakes_vtr_t const * voter_stake = fd_tower_stakes_vtr_map_ele_query_const( stakes->vtr_map, &key, NULL, stakes->vtr_pool );
281 0 : if( FD_UNLIKELY( !voter_stake ) ) {
282 0 : FD_BASE58_ENCODE_32_BYTES( vote_acc->key, vote_acc_b58 );
283 0 : FD_LOG_CRIT(( "missing voter stake for vote account %s on slot %lu. Is this an error?", vote_acc_b58, switch_slot ));
284 0 : }
285 0 : ulong voter_idx = fd_tower_stakes_vtr_pool_idx( stakes->vtr_pool, voter_stake );
286 0 : if( FD_UNLIKELY( fd_used_acc_scratch_test( stakes->used_acc_scratch, voter_idx ) ) ) continue; /* exclude already counted voters */
287 0 : fd_used_acc_scratch_insert( stakes->used_acc_scratch, voter_idx );
288 0 : switch_stake += voter_stake->stake;
289 0 : if( FD_LIKELY( (double)switch_stake >= (double)total_stake * SWITCH_RATIO ) ) {
290 0 : fd_used_acc_scratch_null( stakes->used_acc_scratch );
291 0 : FD_LOG_INFO(( "[%s] switch? 1. last_vote_slot: %lu. switch_slot: %lu. pct: %.0lf%%", __func__, last_vote_slot, switch_slot, (double)switch_stake / (double)total_stake * 100.0 ));
292 0 : return 1;
293 0 : }
294 0 : }
295 0 : }
296 0 : }
297 0 : }
298 0 : }
299 0 : fd_used_acc_scratch_null( stakes->used_acc_scratch );
300 0 : FD_LOG_INFO(( "[%s] switch? 0. last_vote_slot: %lu. switch_slot: %lu. pct: %.0lf%%", __func__, last_vote_slot, switch_slot, (double)switch_stake / (double)total_stake * 100.0 ));
301 0 : return 0;
302 0 : }
303 :
304 : /* threshold_check checks if we pass the threshold required to vote for
305 : `slot`. Returns 1 if we pass the threshold check, 0 otherwise.
306 :
307 : The following psuedocode describes the algorithm:
308 :
309 : ```
310 : simulate that we have voted for `slot`
311 :
312 : for all vote accounts in the current epoch
313 :
314 : simulate that the vote account has voted for `slot`
315 :
316 : pop all votes expired by that simulated vote
317 :
318 : if the validator's latest tower vote after expiry >= our threshold
319 : slot ie. our vote from THRESHOLD_DEPTH back also after simulating,
320 : then add validator's stake to threshold_stake.
321 :
322 : return threshold_stake >= FD_TOWER_THRESHOLD_RATIO
323 : ```
324 :
325 : The threshold check simulates voting for the current slot to expire
326 : stale votes. This is to prevent validators that haven't voted in a
327 : long time from counting towards the threshold stake. */
328 :
329 : static int
330 : threshold_check( fd_tower_t const * tower,
331 : fd_tower_voters_t const * accts,
332 : ulong total_stake,
333 0 : ulong slot ) {
334 :
335 0 : uchar __attribute__((aligned(FD_TOWER_ALIGN))) scratch[ FD_TOWER_FOOTPRINT ];
336 0 : fd_tower_t * scratch_tower = fd_tower_join( fd_tower_new( scratch ) );
337 :
338 : /* First, simulate a vote on our tower, popping off everything that
339 : would be expired by voting for slot. */
340 :
341 0 : ulong cnt = simulate_vote( tower, slot );
342 :
343 : /* We can always vote if our tower is not at least THRESHOLD_DEPTH
344 : deep after simulating. */
345 :
346 0 : if( FD_UNLIKELY( cnt < THRESHOLD_DEPTH ) ) return 1;
347 :
348 : /* Get the vote slot from THRESHOLD_DEPTH back. Note THRESHOLD_DEPTH
349 : is the 8th index back _including_ the simulated vote at index 0. */
350 :
351 0 : ulong threshold_slot = fd_tower_peek_index_const( tower, cnt - THRESHOLD_DEPTH )->slot;
352 0 : ulong threshold_stake = 0;
353 0 : for( fd_tower_voters_iter_t iter = fd_tower_voters_iter_init( accts );
354 0 : !fd_tower_voters_iter_done( accts, iter );
355 0 : iter = fd_tower_voters_iter_next( accts, iter ) ) {
356 0 : fd_tower_voters_t const * acct = fd_tower_voters_iter_ele_const( accts, iter );
357 :
358 0 : fd_tower_remove_all( scratch_tower );
359 0 : fd_tower_from_vote_acc( scratch_tower, acct->data );
360 :
361 0 : ulong cnt = simulate_vote( scratch_tower, slot ); /* expire votes */
362 0 : if( FD_UNLIKELY( !cnt ) ) continue; /* no votes left after expiry */
363 :
364 : /* Count their stake towards the threshold check if their last vote
365 : slot >= our threshold slot.
366 :
367 : We know these votes are for our own fork because towers are
368 : sourced from vote _accounts_, not vote _transactions_.
369 :
370 : Because we are iterating vote accounts on the same fork that we
371 : we want to vote for, we know these slots must all occur along the
372 : same fork ancestry.
373 :
374 : Therefore, if their latest vote slot >= our threshold slot, we
375 : know that vote must be for the threshold slot itself or one of
376 : threshold slot's descendants. */
377 :
378 0 : ulong last_vote = fd_tower_peek_index_const( scratch_tower, cnt - 1 )->slot;
379 0 : if( FD_LIKELY( last_vote >= threshold_slot ) ) threshold_stake += acct->stake;
380 0 : }
381 :
382 0 : double threshold_pct = (double)threshold_stake / (double)total_stake;
383 0 : int threshold = threshold_pct > THRESHOLD_RATIO;
384 0 : FD_LOG_INFO(( "[%s] threshold? %d. top: %lu. threshold: %lu. pct: %.0lf%%.", __func__, threshold, fd_tower_peek_tail_const( tower )->slot, threshold_slot, threshold_pct * 100.0 ));
385 0 : return threshold;
386 0 : }
387 :
388 : static int
389 : propagated_check( fd_notar_t * notar,
390 0 : ulong slot ) {
391 :
392 0 : fd_notar_slot_t * notar_slot = fd_notar_slot_query( notar->slot_map, slot, NULL );
393 0 : if( FD_UNLIKELY( !notar_slot ) ) return 1;
394 :
395 0 : if( FD_LIKELY( notar_slot->is_leader ) ) return 1; /* can always vote for slot in which we're leader */
396 0 : if( FD_LIKELY( notar_slot->prev_leader_slot==ULONG_MAX ) ) return 1; /* haven't been leader yet */
397 :
398 0 : fd_notar_slot_t * prev_leader_notar_slot = fd_notar_slot_query( notar->slot_map, notar_slot->prev_leader_slot, NULL );
399 0 : if( FD_LIKELY( !prev_leader_notar_slot ) ) return 1; /* already pruned rooted */
400 :
401 0 : return prev_leader_notar_slot->is_propagated;
402 0 : }
403 :
404 : fd_tower_out_t
405 : fd_tower_vote_and_reset( fd_tower_t * tower,
406 : fd_tower_blocks_t * blocks,
407 : fd_tower_leaves_t * leaves,
408 : fd_tower_lockos_t * lockos,
409 : fd_tower_stakes_t * stakes,
410 : fd_tower_voters_t * voters,
411 : fd_ghost_t * ghost,
412 0 : fd_notar_t * notar ) {
413 :
414 0 : uchar flags = 0;
415 0 : fd_ghost_blk_t const * best_blk = fd_ghost_best( ghost, fd_ghost_root( ghost ) );
416 0 : fd_ghost_blk_t const * reset_blk = NULL;
417 0 : fd_ghost_blk_t const * vote_blk = NULL;
418 :
419 : /* Case 0: if we haven't voted yet then we can always vote and reset
420 : to ghost_best.
421 :
422 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L933-L935 */
423 :
424 0 : if( FD_UNLIKELY( fd_tower_empty( tower ) ) ) {
425 0 : fd_tower_blk_t * fork = fd_tower_blocks_query( blocks, best_blk->slot );
426 0 : fork->voted = 1;
427 0 : fork->voted_block_id = best_blk->id;
428 0 : return (fd_tower_out_t){
429 0 : .flags = flags,
430 0 : .reset_slot = best_blk->slot,
431 0 : .reset_block_id = best_blk->id,
432 0 : .vote_slot = best_blk->slot,
433 0 : .vote_block_id = best_blk->id,
434 0 : .root_slot = push_vote( tower, best_blk->slot )
435 0 : };
436 0 : }
437 :
438 0 : ulong prev_vote_slot = fd_tower_peek_tail_const( tower )->slot;
439 0 : fd_tower_blk_t * prev_vote_fork = fd_tower_blocks_query( blocks, prev_vote_slot );
440 :
441 :
442 0 : if( FD_UNLIKELY( !prev_vote_fork->voted ) ) {
443 :
444 : /* It's possible prev_vote_fork->voted is not set even though we
445 : popped it from the top of our tower. This can happen when there
446 : are multiple nodes operating with the same vote account.
447 :
448 : In a typical setup involving a primary staked node and backup
449 : unstaked node, the two nodes' towers will usually be identical
450 : but occassionally diverge when one node observes a minority fork
451 : the other doesn't. As a result, one node might be locked out
452 : from voting for a fork that the other node is not.
453 :
454 : This becomes a problem in our primary-backup setup when the
455 : unstaked node is locked out but the staked node is not. The
456 : staked node ultimately lands the vote into the on-chain vote
457 : account, so it's possible when the unstaked node reads back their
458 : on-chain vote account to diff against their local tower, there
459 : are votes in there they themselves did not vote for due to
460 : lockout (fd_tower_reconcile).
461 :
462 : As a result, `voted_block_id` will not be set for slots in their
463 : tower, which normally would be an invariant violation because the
464 : node must have set this value when they voted for the slot (and
465 : pushed it to their tower).
466 :
467 : So here we manually set the voted_block_id to replayed_block_id
468 : if not already set. We know we must have replayed it, because to
469 : observe the on-chain tower you must have replayed all the slots
470 : in the tower. */
471 :
472 : /* FIXME this needs to be thought through more carefully for set-identity. */
473 :
474 0 : prev_vote_fork->voted = 1;
475 0 : prev_vote_fork->voted_block_id = prev_vote_fork->replayed_block_id;
476 0 : }
477 :
478 0 : fd_hash_t * prev_vote_block_id = &prev_vote_fork->voted_block_id;
479 0 : fd_ghost_blk_t * prev_vote_blk = fd_ghost_query( ghost, prev_vote_block_id );
480 :
481 : /* Case 1: if an ancestor of our prev vote (including prev vote
482 : itself) is an unconfirmed duplicate, then our prev vote was on a
483 : duplicate fork.
484 :
485 : There are two subcases to check. */
486 :
487 0 : int invalid_ancestor = !!fd_ghost_invalid_ancestor( ghost, prev_vote_blk );
488 0 : if( FD_UNLIKELY( invalid_ancestor ) ) { /* do we have an invalid ancestor? */
489 :
490 : /* Case 1a: ghost_best is an ancestor of prev vote. This means
491 : ghost_best is rolling back to an ancestor that precedes the
492 : duplicate ancestor on the same fork as our prev vote. In this
493 : case, we can't vote on our ancestor, but we do reset to that
494 : ancestor.
495 :
496 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1016-L1019 */
497 :
498 0 : int ancestor_rollback = prev_vote_blk != best_blk && !!fd_ghost_ancestor( ghost, prev_vote_blk, &best_blk->id );
499 0 : if( FD_LIKELY( ancestor_rollback ) ) {
500 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_ANCESTOR_ROLLBACK );
501 0 : reset_blk = best_blk;
502 0 : }
503 :
504 : /* Case 1b: ghost_best is not an ancestor, but prev_vote is a
505 : duplicate and we've confirmed its duplicate sibling. In this
506 : case, we allow switching to ghost_best without a switch proof.
507 :
508 : Example: slot 5 is a duplicate. We first receive, replay and
509 : vote for block 5, so that is our prev vote. We later receive
510 : block 5' and observe that it is duplicate confirmed. ghost_best
511 : now returns block 5' and we both vote and reset to block 5'
512 : regardless of the switch check.
513 :
514 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1021-L1024 */
515 :
516 0 : int sibling_confirmed = prev_vote_fork->confirmed && 0!=memcmp( &prev_vote_fork->voted_block_id, &prev_vote_fork->confirmed_block_id, sizeof(fd_hash_t) );
517 0 : if( FD_LIKELY( sibling_confirmed ) ) {
518 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SIBLING_CONFIRMED );
519 0 : reset_blk = best_blk;
520 0 : vote_blk = best_blk;
521 0 : }
522 :
523 : /* At this point our prev vote was on a duplicate fork but didn't
524 : match either of the above subcases.
525 :
526 : In this case, we have to pass the switch check to reset to a
527 : different fork from prev vote (same as non-duplicate case). */
528 0 : }
529 :
530 : /* Case 2: if our prev vote slot is an ancestor of the best slot, then
531 : they are on the same fork and we can both reset to it. We can also
532 : vote for it if we pass the can_vote checks.
533 :
534 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1057 */
535 :
536 0 : else if( FD_LIKELY( best_blk->slot == prev_vote_slot || fd_tower_blocks_is_slot_ancestor( blocks, best_blk->slot, prev_vote_slot ) ) ) {
537 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SAME_FORK );
538 0 : reset_blk = best_blk;
539 0 : vote_blk = best_blk;
540 0 : }
541 :
542 : /* Case 3: if our prev vote is not an ancestor of the best block, then
543 : it is on a different fork. If we pass the switch check, we can
544 : reset to it. If we additionally pass the lockout check, we can
545 : also vote for it.
546 :
547 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1208-L1215
548 :
549 : Note also Agave uses the best blk's total stake for checking the
550 : threshold.
551 :
552 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/fork_choice.rs#L443-L445 */
553 :
554 0 : else if( FD_LIKELY( switch_check( tower, blocks, leaves, lockos, stakes, best_blk->total_stake, best_blk->slot ) ) ) {
555 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_PASS );
556 0 : reset_blk = best_blk;
557 0 : vote_blk = best_blk;
558 0 : }
559 :
560 : /* Case 4: same as case 3 but we didn't pass the switch check. In
561 : this case we reset to either ghost_best or ghost_deepest beginning
562 : from our prev vote blk.
563 :
564 : We must reset to a block beginning from our prev vote fork to
565 : ensure votes get a chance to propagate. Because in order for votes
566 : to land, someone needs to build a block on that fork.
567 :
568 : We reset to ghost_best or ghost_deepest depending on whether our
569 : prev vote is valid. When it's invalid we use ghost_deepest instead
570 : of ghost_best, because ghost_best won't be able to return a valid
571 : block beginning from our prev_vote because by definition the entire
572 : subtree will be invalid.
573 :
574 : When our prev vote fork is not a duplicate, we want to propagate
575 : votes that might allow others to switch to our fork. In addition,
576 : if our prev vote fork is a duplicate, we want to propagate votes
577 : that might "duplicate confirm" that block (reach 52% of stake).
578 :
579 : See top-level documentation in fd_tower.h for more details on vote
580 : propagation. */
581 :
582 0 : else {
583 :
584 : /* Case 4a: failed switch check and last vote's fork is invalid.
585 :
586 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/heaviest_subtree_fork_choice.rs#L1187 */
587 :
588 0 : if( FD_UNLIKELY( invalid_ancestor ) ) {
589 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_FAIL );
590 0 : reset_blk = fd_ghost_deepest( ghost, prev_vote_blk );
591 0 : }
592 :
593 : /* Case 4b: failed switch check and last vote's fork is valid.
594 :
595 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/fork_choice.rs#L200 */
596 :
597 0 : else {
598 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_FAIL );
599 0 : reset_blk = fd_ghost_best( ghost, prev_vote_blk );
600 0 : }
601 0 : }
602 :
603 : /* If there is a block to vote for, there are a few additional checks
604 : to make sure we can actually vote for it.
605 :
606 : Specifically, we need to make sure we're not locked out, pass the
607 : threshold check and that our previous leader block has propagated
608 : (reached the prop threshold according to fd_notar).
609 :
610 : https://github.com/firedancer-io/agave/blob/master/core/src/consensus/fork_choice.rs#L382-L385
611 :
612 : Agave uses the total stake on the fork being threshold checked
613 : (vote_blk) for determining whether it meets the stake threshold. */
614 :
615 0 : if( FD_LIKELY( vote_blk ) ) {
616 0 : if ( FD_UNLIKELY( !lockout_check( tower, blocks, vote_blk->slot ) ) ) {
617 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_LOCKOUT_FAIL );
618 0 : vote_blk = NULL;
619 0 : }
620 0 : else if( FD_UNLIKELY( !threshold_check( tower, voters, vote_blk->total_stake, vote_blk->slot ) ) ) {
621 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_THRESHOLD_FAIL );
622 0 : vote_blk = NULL;
623 0 : }
624 0 : else if( FD_UNLIKELY( !propagated_check( notar, vote_blk->slot ) ) ) {
625 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_PROPAGATED_FAIL );
626 0 : vote_blk = NULL;
627 0 : }
628 0 : }
629 :
630 0 : FD_TEST( reset_blk ); /* always a reset_blk */
631 0 : fd_tower_out_t out = {
632 0 : .flags = flags,
633 0 : .reset_slot = reset_blk->slot,
634 0 : .reset_block_id = reset_blk->id,
635 0 : .vote_slot = ULONG_MAX,
636 0 : .root_slot = ULONG_MAX
637 0 : };
638 :
639 : /* Finally, if our vote passed all the checks, we actually push the
640 : vote onto the tower. */
641 :
642 0 : if( FD_LIKELY( vote_blk ) ) {
643 0 : out.vote_slot = vote_blk->slot;
644 0 : out.vote_block_id = vote_blk->id;
645 0 : out.root_slot = push_vote( tower, vote_blk->slot );
646 :
647 : /* Query our tower fork for this slot we're voting for. Note this
648 : can never be NULL because we record tower forks as we replay, and
649 : we should never be voting on something we haven't replayed. */
650 :
651 0 : fd_tower_blk_t * fork = fd_tower_blocks_query( blocks, vote_blk->slot );
652 0 : fork->voted = 1;
653 0 : fork->voted_block_id = vote_blk->id;
654 :
655 : /* Query the root slot's block id from tower forks. This block id
656 : may not necessarily be confirmed, because confirmation requires
657 : votes on the block itself (vs. block and its descendants).
658 :
659 : So if we have a confirmed block id, we return that. Otherwise
660 : we return our own vote block id for that slot, which we assume
661 : is the cluster converged on by the time we're rooting it.
662 :
663 : The only way it is possible for us to root the wrong version of
664 : a block (ie. not the one the cluster confirmed) is if there is
665 : mass equivocation (>2/3 of threshold check stake has voted for
666 : two versions of a block). This exceeds the equivocation safety
667 : threshold and we would eventually detect this via a bank hash
668 : mismatch and error out. */
669 :
670 0 : if( FD_LIKELY( out.root_slot!=ULONG_MAX ) ) {
671 0 : fd_tower_blk_t * root_fork = fd_tower_blocks_query( blocks, out.root_slot );
672 0 : out.root_block_id = *fd_ptr_if( root_fork->confirmed, &root_fork->confirmed_block_id, &root_fork->voted_block_id );
673 0 : }
674 0 : }
675 :
676 0 : FD_BASE58_ENCODE_32_BYTES( out.reset_block_id.uc, reset_block_id );
677 0 : FD_BASE58_ENCODE_32_BYTES( out.vote_block_id.uc, vote_block_id );
678 0 : FD_BASE58_ENCODE_32_BYTES( out.root_block_id.uc, root_block_id );
679 0 : FD_LOG_INFO(( "[%s] flags: %d. reset_slot: %lu (%s). vote_slot: %lu (%s). root_slot: %lu (%s).", __func__, out.flags, out.reset_slot, reset_block_id, out.vote_slot, vote_block_id, out.root_slot, root_block_id ));
680 0 : return out;
681 0 : }
682 :
683 : void
684 : fd_tower_reconcile( fd_tower_t * tower,
685 : ulong root,
686 0 : uchar const * vote_account_data ) {
687 0 : ulong on_chain_vote = fd_vote_acc_vote_slot( vote_account_data );
688 0 : ulong on_chain_root = fd_vote_acc_root_slot( vote_account_data );
689 :
690 0 : fd_tower_vote_t const * last_vote = fd_tower_peek_tail_const( tower );
691 0 : ulong last_vote_slot = last_vote ? last_vote->slot : ULONG_MAX;
692 :
693 0 : if( FD_UNLIKELY( ( on_chain_vote==ULONG_MAX && last_vote_slot==ULONG_MAX ) ) ) return;
694 0 : if( FD_LIKELY ( ( on_chain_vote!=ULONG_MAX && last_vote_slot!=ULONG_MAX
695 0 : && on_chain_vote <= last_vote_slot ) ) ) return;
696 :
697 : /* At this point our local tower is too old, and we need to replace it
698 : with our on-chain tower. However, it's possible our local root is
699 : newer than the on-chain root (even though the tower is older). The
700 : most likely reason this happens is because we just booted from a
701 : snapshot and the snapshot slot > on-chain root.
702 :
703 : So we need to filter out the stale votes < snapshot slot. This
704 : mirrors the Agave logic:
705 : https://github.com/firedancer-io/agave/blob/master/core/src/replay_stage.rs#L3690-L3719 */
706 :
707 0 : if( FD_LIKELY( on_chain_root == ULONG_MAX || root > on_chain_root ) ) {
708 0 : fd_tower_remove_all( tower );
709 0 : fd_vote_acc_t const * voter = (fd_vote_acc_t const *)fd_type_pun_const( vote_account_data );
710 0 : uint kind = fd_uint_load_4_fast( vote_account_data ); /* skip node_pubkey */
711 0 : for( ulong i=0; i<fd_vote_acc_vote_cnt( vote_account_data ); i++ ) {
712 0 : switch( kind ) {
713 0 : case FD_VOTE_ACC_V4: fd_tower_push_tail( tower, (fd_tower_vote_t){ .slot = v4_off( voter )[i].slot, .conf = v4_off( voter )[i].conf } ); break;
714 0 : case FD_VOTE_ACC_V3: fd_tower_push_tail( tower, (fd_tower_vote_t){ .slot = voter->v3.votes[i].slot, .conf = voter->v3.votes[i].conf } ); break;
715 0 : case FD_VOTE_ACC_V2: fd_tower_push_tail( tower, (fd_tower_vote_t){ .slot = voter->v2.votes[i].slot, .conf = voter->v2.votes[i].conf } ); break;
716 0 : default: FD_LOG_ERR(( "unknown kind: %u", kind ));
717 0 : }
718 0 : }
719 :
720 : /* Fast forward our tower to tower_root by retaining only votes >
721 : local tower root. */
722 :
723 0 : while( FD_LIKELY( !fd_tower_empty( tower ) ) ) {
724 0 : fd_tower_t const * vote = fd_tower_peek_head_const( tower );
725 0 : if( FD_LIKELY( vote->slot > root ) ) break;
726 0 : fd_tower_pop_head( tower );
727 0 : }
728 0 : }
729 0 : }
730 :
731 : void
732 : fd_tower_from_vote_acc( fd_tower_t * tower,
733 0 : uchar const * vote_acc ) {
734 0 : fd_vote_acc_t const * voter = (fd_vote_acc_t const *)fd_type_pun_const( vote_acc );
735 0 : uint kind = fd_uint_load_4_fast( vote_acc ); /* skip node_pubkey */
736 0 : for( ulong i=0; i<fd_vote_acc_vote_cnt( vote_acc ); i++ ) {
737 0 : switch( kind ) {
738 0 : case FD_VOTE_ACC_V4: fd_tower_push_tail( tower, (fd_tower_vote_t){ .slot = v4_off( voter )[i].slot, .conf = v4_off( voter )[i].conf } ); break;
739 0 : case FD_VOTE_ACC_V3: fd_tower_push_tail( tower, (fd_tower_vote_t){ .slot = voter->v3.votes[i].slot, .conf = voter->v3.votes[i].conf } ); break;
740 0 : case FD_VOTE_ACC_V2: fd_tower_push_tail( tower, (fd_tower_vote_t){ .slot = voter->v2.votes[i].slot, .conf = voter->v2.votes[i].conf } ); break;
741 0 : default: FD_LOG_ERR(( "unsupported voter account version: %u", kind ));
742 0 : }
743 0 : }
744 0 : }
745 :
746 : ulong
747 : fd_tower_with_lat_from_vote_acc( fd_vote_acc_vote_t tower[ static FD_TOWER_VOTE_MAX ],
748 0 : uchar const * vote_acc ) {
749 0 : fd_vote_acc_t const * voter = (fd_vote_acc_t const *)fd_type_pun_const( vote_acc );
750 0 : uint kind = fd_uint_load_4_fast( vote_acc ); /* skip node_pubkey */
751 0 : for( ulong i=0; i<fd_vote_acc_vote_cnt( vote_acc ); i++ ) {
752 0 : switch( kind ) {
753 0 : case FD_VOTE_ACC_V4: tower[ i ] = (fd_vote_acc_vote_t){ .latency = v4_off( voter )[i].latency, .slot = v4_off( voter )[i].slot, .conf = v4_off( voter )[i].conf }; break;
754 0 : case FD_VOTE_ACC_V3: tower[ i ] = (fd_vote_acc_vote_t){ .latency = voter->v3.votes[i].latency, .slot = voter->v3.votes[i].slot, .conf = voter->v3.votes[i].conf }; break;
755 0 : case FD_VOTE_ACC_V2: tower[ i ] = (fd_vote_acc_vote_t){ .latency = UCHAR_MAX, .slot = voter->v2.votes[i].slot, .conf = voter->v2.votes[i].conf }; break;
756 0 : default: FD_LOG_ERR(( "unsupported voter account version: %u", kind ));
757 0 : }
758 0 : }
759 :
760 0 : return fd_vote_acc_vote_cnt( vote_acc );
761 0 : }
762 :
763 : void
764 : fd_tower_to_vote_txn( fd_tower_t const * tower,
765 : ulong root,
766 : fd_hash_t const * bank_hash,
767 : fd_hash_t const * block_id,
768 : fd_hash_t const * recent_blockhash,
769 : fd_pubkey_t const * validator_identity,
770 : fd_pubkey_t const * vote_authority,
771 : fd_pubkey_t const * vote_acc,
772 0 : fd_txn_p_t * vote_txn ) {
773 :
774 0 : FD_TEST( fd_tower_cnt( tower )<=FD_TOWER_VOTE_MAX );
775 0 : fd_compact_tower_sync_serde_t tower_sync_serde = {
776 0 : .root = fd_ulong_if( root == ULONG_MAX, 0UL, root ),
777 0 : .lockouts_cnt = (ushort)fd_tower_cnt( tower ),
778 : /* .lockouts populated below */
779 0 : .hash = *bank_hash,
780 0 : .timestamp_option = 1,
781 0 : .timestamp = fd_log_wallclock() / (long)1e9, /* seconds */
782 0 : .block_id = *block_id
783 0 : };
784 :
785 0 : ulong i = 0UL;
786 0 : ulong prev = tower_sync_serde.root;
787 0 : for( fd_tower_iter_t iter = fd_tower_iter_init( tower );
788 0 : !fd_tower_iter_done( tower, iter );
789 0 : iter = fd_tower_iter_next( tower, iter ) ) {
790 0 : fd_tower_t const * vote = fd_tower_iter_ele_const( tower, iter );
791 0 : tower_sync_serde.lockouts[i].offset = vote->slot - prev;
792 0 : tower_sync_serde.lockouts[i].confirmation_count = (uchar)vote->conf;
793 0 : prev = vote->slot;
794 0 : i++;
795 0 : }
796 :
797 0 : uchar * txn_out = vote_txn->payload;
798 0 : uchar * txn_meta_out = vote_txn->_;
799 :
800 0 : int same_addr = !memcmp( validator_identity, vote_authority, sizeof(fd_pubkey_t) );
801 0 : if( FD_LIKELY( same_addr ) ) {
802 :
803 : /* 0: validator identity
804 : 1: vote account address
805 : 2: vote program */
806 :
807 0 : fd_txn_accounts_t votes;
808 0 : votes.signature_cnt = 1;
809 0 : votes.readonly_signed_cnt = 0;
810 0 : votes.readonly_unsigned_cnt = 1;
811 0 : votes.acct_cnt = 3;
812 0 : votes.signers_w = validator_identity;
813 0 : votes.signers_r = NULL;
814 0 : votes.non_signers_w = vote_acc;
815 0 : votes.non_signers_r = &fd_solana_vote_program_id;
816 0 : FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, votes.signature_cnt, &votes, recent_blockhash->uc ) );
817 :
818 0 : } else {
819 :
820 : /* 0: validator identity
821 : 1: vote authority
822 : 2: vote account address
823 : 3: vote program */
824 :
825 0 : fd_txn_accounts_t votes;
826 0 : votes.signature_cnt = 2;
827 0 : votes.readonly_signed_cnt = 1;
828 0 : votes.readonly_unsigned_cnt = 1;
829 0 : votes.acct_cnt = 4;
830 0 : votes.signers_w = validator_identity;
831 0 : votes.signers_r = vote_authority;
832 0 : votes.non_signers_w = vote_acc;
833 0 : votes.non_signers_r = &fd_solana_vote_program_id;
834 0 : FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, votes.signature_cnt, &votes, recent_blockhash->uc ) );
835 0 : }
836 :
837 : /* Add the vote instruction to the transaction. */
838 :
839 0 : uchar vote_ix_buf[FD_TXN_MTU];
840 0 : ulong vote_ix_sz = 0;
841 0 : FD_STORE( uint, vote_ix_buf, FD_VOTE_IX_KIND_TOWER_SYNC );
842 0 : FD_TEST( 0==fd_compact_tower_sync_ser( &tower_sync_serde, vote_ix_buf + sizeof(uint), FD_TXN_MTU - sizeof(uint), &vote_ix_sz ) ); // cannot fail if fd_tower_cnt( tower ) <= FD_TOWER_VOTE_MAX
843 0 : vote_ix_sz += sizeof(uint);
844 0 : uchar program_id;
845 0 : uchar ix_accs[2];
846 0 : if( FD_LIKELY( same_addr ) ) {
847 0 : ix_accs[0] = 1; /* vote account address */
848 0 : ix_accs[1] = 0; /* vote authority */
849 0 : program_id = 2; /* vote program */
850 0 : } else {
851 0 : ix_accs[0] = 2; /* vote account address */
852 0 : ix_accs[1] = 1; /* vote authority */
853 0 : program_id = 3; /* vote program */
854 0 : }
855 0 : vote_txn->payload_sz = fd_txn_add_instr( txn_meta_out, txn_out, program_id, ix_accs, 2, vote_ix_buf, vote_ix_sz );
856 0 : }
857 :
858 : int
859 0 : fd_tower_verify( fd_tower_t const * tower ) {
860 0 : if( FD_UNLIKELY( fd_tower_cnt( tower )>=FD_TOWER_VOTE_MAX ) ) {
861 0 : FD_LOG_WARNING(( "[%s] invariant violation: cnt %lu >= FD_TOWER_VOTE_MAX %lu", __func__, fd_tower_cnt( tower ), (ulong)FD_TOWER_VOTE_MAX ));
862 0 : return -1;
863 0 : }
864 :
865 0 : fd_tower_t const * prev = NULL;
866 0 : for( fd_tower_iter_t iter = fd_tower_iter_init( tower );
867 0 : !fd_tower_iter_done( tower, iter );
868 0 : iter = fd_tower_iter_next( tower, iter ) ) {
869 0 : fd_tower_t const * vote = fd_tower_iter_ele_const( tower, iter );
870 0 : if( FD_UNLIKELY( prev && ( vote->slot < prev->slot || vote->conf < prev->conf ) ) ) {
871 0 : FD_LOG_WARNING(( "[%s] invariant violation: vote (slot:%lu conf:%lu) prev (slot:%lu conf:%lu)", __func__, vote->slot, vote->conf, prev->slot, prev->conf ));
872 0 : return -1;
873 0 : }
874 0 : prev = vote;
875 0 : }
876 0 : return 0;
877 0 : }
878 :
879 : static void
880 0 : to_cstr( fd_tower_t const * tower, ulong root, char * s, ulong len ) {
881 0 : ulong off = 0;
882 0 : int n;
883 :
884 0 : n = snprintf( s + off, len - off, "[Tower]\n\n" );
885 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
886 0 : off += (ulong)n;
887 :
888 0 : if( FD_UNLIKELY( fd_tower_empty( tower ) ) ) return;
889 :
890 0 : ulong max_slot = 0;
891 :
892 : /* Determine spacing. */
893 :
894 0 : for( fd_tower_iter_t iter = fd_tower_iter_init_rev( tower );
895 0 : !fd_tower_iter_done_rev( tower, iter );
896 0 : iter = fd_tower_iter_prev ( tower, iter ) ) {
897 0 : max_slot = fd_ulong_max( max_slot, fd_tower_iter_ele_const( tower, iter )->slot );
898 0 : }
899 :
900 : /* Calculate the number of digits in the maximum slot value. */
901 :
902 :
903 0 : int digit_cnt = (int)fd_ulong_base10_dig_cnt( max_slot );
904 :
905 : /* Print the column headers. */
906 :
907 0 : if( off < len ) {
908 0 : n = snprintf( s + off, len - off, "slot%*s | %s\n", digit_cnt - (int)strlen("slot"), "", "confirmation count" );
909 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
910 0 : off += (ulong)n;
911 0 : }
912 :
913 : /* Print the divider line. */
914 :
915 0 : for( int i = 0; i < digit_cnt && off < len; i++ ) {
916 0 : s[off++] = '-';
917 0 : }
918 0 : if( off < len ) {
919 0 : n = snprintf( s + off, len - off, " | " );
920 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
921 0 : off += (ulong)n;
922 0 : }
923 0 : for( ulong i = 0; i < strlen( "confirmation count" ) && off < len; i++ ) {
924 0 : s[off++] = '-';
925 0 : }
926 0 : if( off < len ) {
927 0 : s[off++] = '\n';
928 0 : }
929 :
930 : /* Print each vote as a table. */
931 :
932 0 : for( fd_tower_iter_t iter = fd_tower_iter_init_rev( tower );
933 0 : !fd_tower_iter_done_rev( tower, iter );
934 0 : iter = fd_tower_iter_prev ( tower, iter ) ) {
935 0 : fd_tower_t const * vote = fd_tower_iter_ele_const( tower, iter );
936 0 : if( off < len ) {
937 0 : n = snprintf( s + off, len - off, "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf );
938 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
939 0 : off += (ulong)n;
940 0 : }
941 0 : }
942 :
943 0 : if( FD_UNLIKELY( root == ULONG_MAX ) ) {
944 0 : if( off < len ) {
945 0 : n = snprintf( s + off, len - off, "%*s | root\n", digit_cnt, "NULL" );
946 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
947 0 : off += (ulong)n;
948 0 : }
949 0 : } else {
950 0 : if( off < len ) {
951 0 : n = snprintf( s + off, len - off, "%*lu | root\n", digit_cnt, root );
952 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
953 0 : off += (ulong)n;
954 0 : }
955 0 : }
956 :
957 : /* Ensure null termination */
958 0 : if( off < len ) {
959 0 : s[off] = '\0';
960 0 : } else {
961 0 : s[len - 1] = '\0';
962 0 : }
963 0 : }
964 :
965 : char *
966 : fd_tower_to_cstr( fd_tower_t const * tower,
967 : ulong root,
968 0 : char * cstr ) {
969 0 : to_cstr( tower, root, cstr, FD_TOWER_CSTR_MIN );
970 0 : return cstr;
971 0 : }
|