Line data Source code
1 : #include "fd_tower.h"
2 :
3 0 : #define THRESHOLD_DEPTH (8)
4 0 : #define THRESHOLD_PCT (2.0 / 3.0)
5 : #define SHALLOW_THRESHOLD_DEPTH (4)
6 : #define SHALLOW_THRESHOLD_PCT (0.38)
7 0 : #define SWITCH_PCT (0.38)
8 :
9 : void *
10 0 : fd_tower_new( void * shmem ) {
11 0 : if( FD_UNLIKELY( !shmem ) ) {
12 0 : FD_LOG_WARNING(( "NULL mem" ));
13 0 : return NULL;
14 0 : }
15 :
16 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_tower_align() ) ) ) {
17 0 : FD_LOG_WARNING(( "misaligned mem" ));
18 0 : return NULL;
19 0 : }
20 :
21 0 : return fd_tower_votes_new( shmem );
22 0 : }
23 :
24 : fd_tower_t *
25 0 : fd_tower_join( void * shtower ) {
26 :
27 0 : if( FD_UNLIKELY( !shtower ) ) {
28 0 : FD_LOG_WARNING(( "NULL tower" ));
29 0 : return NULL;
30 0 : }
31 :
32 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shtower, fd_tower_align() ) ) ) {
33 0 : FD_LOG_WARNING(( "misaligned tower" ));
34 0 : return NULL;
35 0 : }
36 :
37 0 : return fd_tower_votes_join( shtower );
38 0 : }
39 :
40 : void *
41 0 : fd_tower_leave( fd_tower_t * tower ) {
42 :
43 0 : if( FD_UNLIKELY( !tower ) ) {
44 0 : FD_LOG_WARNING(( "NULL tower" ));
45 0 : return NULL;
46 0 : }
47 :
48 0 : return fd_tower_votes_leave( tower );
49 0 : }
50 :
51 : void *
52 0 : fd_tower_delete( void * tower ) {
53 :
54 0 : if( FD_UNLIKELY( !tower ) ) {
55 0 : FD_LOG_WARNING(( "NULL tower" ));
56 0 : return NULL;
57 0 : }
58 :
59 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)tower, fd_tower_align() ) ) ) {
60 0 : FD_LOG_WARNING(( "misaligned tower" ));
61 0 : return NULL;
62 0 : }
63 :
64 0 : return fd_tower_votes_delete( tower );
65 0 : }
66 :
67 : static inline ulong
68 0 : expiration( fd_tower_vote_t const * vote ) {
69 0 : ulong lockout = 1UL << vote->conf;
70 0 : return vote->slot + lockout;
71 0 : }
72 :
73 : static inline ulong
74 0 : simulate_vote( fd_tower_t const * tower, ulong slot ) {
75 0 : ulong cnt = fd_tower_votes_cnt( tower );
76 0 : while( cnt ) {
77 :
78 : /* Return early if we can't pop the top tower vote, even if votes
79 : below it are expired. */
80 :
81 0 : if( FD_LIKELY( expiration( fd_tower_votes_peek_index_const( tower, cnt - 1 ) ) >= slot ) ) {
82 0 : break;
83 0 : }
84 0 : cnt--;
85 0 : }
86 0 : return cnt;
87 0 : }
88 :
89 : int
90 : fd_tower_lockout_check( fd_tower_t const * tower,
91 : fd_ghost_t const * ghost,
92 0 : ulong slot ) {
93 0 : #if FD_TOWER_USE_HANDHOLDING
94 0 : FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
95 0 : #endif
96 :
97 : /* Simulate a vote to pop off all the votes that have been expired at
98 : the top of the tower. */
99 :
100 0 : ulong cnt = simulate_vote( tower, slot );
101 :
102 : /* By definition, all votes in the tower must be for the same fork, so
103 : check if the previous vote (ie. the last vote in the tower) is on
104 : the same fork as the fork we want to vote for. We do this using
105 : ghost by checking if the previous vote slot is an ancestor of the
106 : `slot`. If the previous vote slot is too old (ie. older than
107 : ghost->root), then we don't have ancestry information anymore and
108 : we just assume it is on the same fork.
109 :
110 : FIXME discuss if it is safe to assume that? */
111 :
112 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_index_const( tower, cnt - 1 );
113 0 : fd_ghost_node_t const * root = fd_ghost_root( ghost );
114 :
115 0 : int lockout_check = vote->slot < root->slot ||
116 0 : fd_ghost_is_ancestor( ghost, vote->slot, slot );
117 0 : FD_LOG_NOTICE(( "[fd_tower_lockout_check] ok? %d. top: (slot: %lu, conf: %lu). switch: %lu.", lockout_check, vote->slot, vote->conf, slot ));
118 0 : return lockout_check;
119 0 : }
120 :
121 : int
122 : fd_tower_switch_check( fd_tower_t const * tower,
123 : fd_epoch_t const * epoch,
124 : fd_ghost_t const * ghost,
125 0 : ulong slot ) {
126 0 : #if FD_TOWER_USE_HANDHOLDING
127 0 : FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
128 0 : #endif
129 :
130 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
131 0 : fd_ghost_node_t const * root = fd_ghost_root( ghost );
132 :
133 0 : if( FD_UNLIKELY( vote->slot < root->slot ) ) {
134 :
135 : /* It is possible our last vote slot precedes our ghost root. This
136 : can happen, for example, when we restart from a snapshot and set
137 : the ghost root to the snapshot slot (we won't have an ancestry
138 : before the snapshot slot.)
139 :
140 : If this is the case, we assume it's ok to switch. */
141 :
142 0 : return 1;
143 0 : }
144 :
145 : /* fd_tower_switch_check is only called if latest_vote->slot and
146 : fork->slot are on different forks (determined by is_descendant), so
147 : they must not fall on the same ancestry path back to the gca.
148 :
149 : INVALID:
150 :
151 : 0
152 : \
153 : 1 <- a
154 : \
155 : 2 <- b
156 :
157 : VALID:
158 :
159 : 0
160 : / \
161 : 1 2
162 : ^ ^
163 : a b
164 :
165 : */
166 :
167 0 : #if FD_TOWER_USE_HANDHOLDING
168 0 : FD_TEST( !fd_ghost_is_ancestor( ghost, vote->slot, slot ) );
169 0 : #endif
170 :
171 0 : fd_ghost_node_map_t const * node_map = fd_ghost_node_map_const( ghost );
172 0 : fd_ghost_node_t const * node_pool = fd_ghost_node_pool_const( ghost );
173 0 : fd_ghost_node_t const * gca = fd_ghost_gca( ghost, vote->slot, slot );
174 0 : ulong gca_idx = fd_ghost_node_map_idx_query_const( node_map, &gca->slot, ULONG_MAX, node_pool );
175 :
176 : /* gca_child is our latest_vote slot's ancestor that is also a direct
177 : child of GCA. So we do not count it towards the stake of the
178 : different forks. */
179 :
180 0 : fd_ghost_node_t const * gca_child = fd_ghost_query( ghost, vote->slot );
181 0 : while( gca_child->parent_idx != gca_idx ) {
182 0 : gca_child = fd_ghost_node_pool_ele_const( node_pool, gca_child->parent_idx );
183 0 : }
184 :
185 0 : ulong switch_stake = 0;
186 0 : fd_ghost_node_t const * child = fd_ghost_child( ghost, gca );
187 0 : while( FD_LIKELY( child ) ) {
188 0 : if( FD_LIKELY( child != gca_child ) ) {
189 0 : switch_stake += child->weight;
190 0 : }
191 0 : child = fd_ghost_node_pool_ele_const( node_pool, child->sibling_idx );
192 0 : }
193 :
194 0 : double switch_pct = (double)switch_stake / (double)epoch->total_stake;
195 0 : FD_LOG_DEBUG(( "[%s] ok? %d. top: %lu. switch: %lu. switch stake: %.0lf%%.", __func__, switch_pct > SWITCH_PCT, fd_tower_votes_peek_tail_const( tower )->slot, slot, switch_pct * 100.0 ));
196 0 : return switch_pct > SWITCH_PCT;
197 0 : }
198 :
199 : int
200 : fd_tower_threshold_check( fd_tower_t const * tower,
201 : fd_epoch_t const * epoch,
202 : fd_funk_t * funk,
203 : fd_funk_txn_t const * txn,
204 : ulong slot,
205 0 : fd_tower_t * scratch ) {
206 :
207 : /* First, simulate a vote, popping off everything that would be
208 : expired by voting for the current slot. */
209 :
210 0 : ulong cnt = simulate_vote( tower, slot );
211 :
212 : /* Return early if our tower is not at least THRESHOLD_DEPTH deep
213 : after simulating. */
214 :
215 0 : if( FD_UNLIKELY( cnt < THRESHOLD_DEPTH ) ) return 1;
216 :
217 : /* Get the vote slot from THRESHOLD_DEPTH back. Note THRESHOLD_DEPTH
218 : is the 8th index back _including_ the simulated vote at index 0,
219 : which is not accounted for by `cnt`, so subtracting THRESHOLD_DEPTH
220 : will conveniently index the threshold vote. */
221 :
222 0 : ulong threshold_slot = fd_tower_votes_peek_index_const( tower, cnt - THRESHOLD_DEPTH )->slot;
223 :
224 : /* Track the amount of stake that has vote slot >= threshold_slot. */
225 :
226 0 : ulong threshold_stake = 0;
227 :
228 : /* Iterate all the vote accounts. */
229 :
230 0 : fd_voter_t const * epoch_voters = fd_epoch_voters_const( epoch );
231 0 : for (ulong i = 0; i < fd_epoch_voters_slot_cnt( epoch_voters ); i++ ) {
232 0 : if( FD_LIKELY( fd_epoch_voters_key_inval( epoch_voters[i].key ) ) ) continue /* most slots are empty */;
233 :
234 0 : fd_voter_t const * voter = &epoch_voters[i];
235 :
236 : /* Convert the landed_votes into tower's vote_slots interface. */
237 :
238 0 : fd_tower_votes_remove_all( scratch );
239 0 : int err = fd_tower_from_vote_acc( scratch, funk, txn, &voter->rec );
240 0 : if( FD_UNLIKELY( err ) ) {
241 0 : FD_LOG_WARNING(( "[%s] failed to read vote account %s", __func__, FD_BASE58_ENC_32_ALLOCA(&voter->key) ));
242 0 : continue;
243 0 : }
244 :
245 : /* If this voter has not voted, continue. */
246 :
247 0 : if( FD_UNLIKELY( fd_tower_votes_empty( scratch ) ) ) continue;
248 :
249 0 : ulong cnt = simulate_vote( scratch, slot );
250 :
251 : /* Continue if their tower is empty after simulating. */
252 :
253 0 : if( FD_UNLIKELY( !cnt ) ) continue;
254 :
255 : /* Get their latest vote. */
256 :
257 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_index( scratch, cnt - 1 );
258 :
259 : /* Count their stake towards the threshold check if their latest
260 : vote slot >= our threshold slot.
261 :
262 : Because we are iterating vote accounts on the same fork that we
263 : we want to vote for, we know these slots must all occur along
264 : the same fork ancestry.
265 :
266 : Therefore, if their latest vote slot >= our threshold slot, we
267 : know that vote must be for the threshold slot itself or one of
268 : threshold slot's descendants. */
269 :
270 0 : if( FD_LIKELY( vote->slot >= threshold_slot ) ) {
271 0 : threshold_stake += voter->stake;
272 0 : }
273 0 : }
274 :
275 0 : double threshold_pct = (double)threshold_stake / (double)epoch->total_stake;
276 0 : FD_LOG_NOTICE(( "[%s] ok? %d. top: %lu. threshold: %lu. stake: %.0lf%%.", __func__, threshold_pct > THRESHOLD_PCT, fd_tower_votes_peek_tail_const( tower )->slot, threshold_slot, threshold_pct * 100.0 ));
277 0 : return threshold_pct > THRESHOLD_PCT;
278 0 : }
279 :
280 : ulong
281 : fd_tower_reset_slot( fd_tower_t const * tower,
282 : fd_epoch_t const * epoch,
283 0 : fd_ghost_t const * ghost ) {
284 :
285 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
286 0 : fd_ghost_node_t const * root = fd_ghost_root( ghost );
287 0 : fd_ghost_node_t const * head = fd_ghost_head( ghost, root );
288 :
289 : /* Reset to the ghost head if any of the following is true:
290 : 1. haven't voted
291 : 2. last vote < ghost root
292 : 3. ghost root is not an ancestory of last vote */
293 :
294 0 : if( FD_UNLIKELY( !vote || vote->slot < root->slot ||
295 0 : !fd_ghost_is_ancestor( ghost, root->slot, vote->slot ) ) ) {
296 0 : return head->slot;
297 0 : }
298 :
299 : /* Find the ghost node keyed by our last vote slot. It is invariant
300 : that this node must always be found after doing the above check.
301 : Otherwise ghost and tower contain implementation bugs and/or are
302 : corrupt. */
303 :
304 0 : fd_ghost_node_t const * vote_node = fd_ghost_query( ghost, vote->slot );
305 0 : #if FD_TOWER_USE_HANDHOLDING
306 0 : if( FD_UNLIKELY( !vote_node ) ) {
307 0 : fd_ghost_print( ghost, epoch, root );
308 0 : FD_LOG_ERR(( "[%s] invariant violation: unable to find last tower vote slot %lu in ghost.", __func__, vote->slot ));
309 0 : }
310 0 : #endif
311 :
312 : /* Starting from the node keyed by the last vote slot, greedy traverse
313 : for the head. */
314 :
315 0 : return fd_ghost_head( ghost, vote_node )->slot;
316 0 : }
317 :
318 : ulong
319 : fd_tower_vote_slot( fd_tower_t * tower,
320 : fd_epoch_t const * epoch,
321 : fd_funk_t * funk,
322 : fd_funk_txn_t const * txn,
323 : fd_ghost_t const * ghost,
324 0 : fd_tower_t * scratch ) {
325 :
326 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
327 0 : fd_ghost_node_t const * root = fd_ghost_root( ghost );
328 0 : fd_ghost_node_t const * head = fd_ghost_head( ghost, root );
329 :
330 : /* Vote for the ghost head if any of the following is true:
331 :
332 : 1. haven't voted
333 : 2. last vote < ghost root
334 : 3. ghost root is not an ancestory of last vote
335 :
336 : FIXME need to ensure lockout safety for case 2 and 3 */
337 :
338 0 : if( FD_UNLIKELY( !vote || vote->slot < root->slot ||
339 0 : !fd_ghost_is_ancestor( ghost, root->slot, vote->slot ) ) ) {
340 0 : return head->slot;
341 0 : }
342 :
343 : /* Optimize for when there is just one fork or that we already
344 : previously voted for the best fork. */
345 :
346 0 : if( FD_LIKELY( fd_ghost_is_ancestor( ghost, vote->slot, head->slot ) ) ) {
347 :
348 : /* The ghost head is on the same fork as our last vote slot, so we
349 : can vote fork it as long as we pass the threshold check. */
350 :
351 0 : if( FD_LIKELY( fd_tower_threshold_check( tower, epoch, funk, txn, head->slot, scratch ) ) ) {
352 0 : FD_LOG_DEBUG(( "[%s] success (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
353 0 : return head->slot;
354 0 : }
355 0 : FD_LOG_DEBUG(( "[%s] failure (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
356 0 : return FD_SLOT_NULL; /* can't vote. need to wait for threshold check. */
357 0 : }
358 :
359 : /* The ghost head is on a different fork from our last vote slot, so
360 : try to switch if we pass lockout and switch threshold. */
361 :
362 0 : if( FD_UNLIKELY( fd_tower_lockout_check( tower, ghost, head->slot ) &&
363 0 : fd_tower_switch_check( tower, epoch, ghost, head->slot ) ) ) {
364 0 : FD_LOG_DEBUG(( "[%s] success (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
365 0 : return head->slot;
366 0 : }
367 0 : FD_LOG_DEBUG(( "[%s] failure (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
368 0 : return FD_SLOT_NULL;
369 0 : }
370 :
371 : ulong
372 0 : fd_tower_vote( fd_tower_t * tower, ulong slot ) {
373 0 : FD_LOG_DEBUG(( "[%s] voting for slot %lu", __func__, slot ));
374 :
375 0 : #if FD_TOWER_USE_HANDHOLDING
376 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
377 0 : if( FD_UNLIKELY( vote && slot < vote->slot ) ) FD_LOG_ERR(( "[%s] slot %lu < vote->slot %lu", __func__, slot, vote->slot )); /* caller error*/
378 0 : #endif
379 :
380 : /* Use simulate_vote to determine how many expired votes to pop. */
381 :
382 0 : ulong cnt = simulate_vote( tower, slot );
383 :
384 : /* Pop everything that got expired. */
385 :
386 0 : while( fd_tower_votes_cnt( tower ) > cnt ) {
387 0 : fd_tower_votes_pop_tail( tower );
388 0 : }
389 :
390 : /* If the tower is still full after expiring, then pop and return the
391 : bottom vote slot as the new root because this vote has incremented
392 : it to max lockout. Otherwise this is a no-op and there is no new
393 : root (FD_SLOT_NULL). */
394 :
395 0 : ulong root = FD_SLOT_NULL;
396 0 : if( FD_LIKELY( fd_tower_votes_full( tower ) ) ) { /* optimize for full tower */
397 0 : root = fd_tower_votes_pop_head( tower ).slot;
398 0 : }
399 :
400 : /* Increment confirmations (double lockouts) for consecutive
401 : confirmations in prior votes. */
402 :
403 0 : ulong prev_conf = 0;
404 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
405 0 : !fd_tower_votes_iter_done_rev( tower, iter );
406 0 : iter = fd_tower_votes_iter_prev( tower, iter ) ) {
407 0 : fd_tower_vote_t * vote = fd_tower_votes_iter_ele( tower, iter );
408 0 : if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) {
409 0 : break;
410 0 : }
411 0 : vote->conf++;
412 0 : }
413 :
414 : /* Add the new vote to the tower. */
415 :
416 0 : fd_tower_votes_push_tail( tower, (fd_tower_vote_t){ .slot = slot, .conf = 1 } );
417 :
418 : /* Return the new root (FD_SLOT_NULL if there is none). */
419 :
420 0 : return root;
421 0 : }
422 :
423 : ulong
424 0 : fd_tower_simulate_vote( fd_tower_t const * tower, ulong slot ) {
425 0 : #if FD_TOWER_USE_HANDHOLDING
426 0 : FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
427 0 : #endif
428 :
429 0 : return simulate_vote( tower, slot );
430 0 : }
431 :
432 : int
433 : fd_tower_from_vote_acc( fd_tower_t * tower,
434 : fd_funk_t * funk,
435 : fd_funk_txn_t const * txn,
436 0 : fd_funk_rec_key_t const * vote_acc ) {
437 0 : # if FD_TOWER_USE_HANDHOLDING
438 0 : FD_TEST( fd_tower_votes_empty( tower ) );
439 0 : # endif
440 :
441 0 : for(;;) {
442 :
443 : /* Speculatively query the record and parse the voter state. If the
444 : record is missing or the voter state fails to parse, then return
445 : early (tower will be empty). */
446 :
447 0 : fd_funk_rec_query_t query;
448 0 : fd_funk_rec_t const * rec = fd_funk_rec_query_try_global( funk, txn, vote_acc, NULL, &query );
449 0 : if( FD_UNLIKELY( !rec ) ) return -1; /* record not found */
450 0 : fd_voter_state_t const * state = fd_voter_state( funk, rec );
451 0 : if( FD_UNLIKELY( !state ) ) return -1; /* unable to parse voter state */
452 :
453 : /* Speculatively query the cnt. */
454 :
455 0 : ulong cnt = fd_voter_state_cnt( state ); /* TODO remove once Funk reads are safe */
456 0 : if( FD_UNLIKELY( fd_funk_rec_query_test( &query ) != FD_FUNK_SUCCESS ) ) continue;
457 0 : if( FD_UNLIKELY( cnt > 31UL ) ) FD_LOG_ERR(( "[%s] funk vote account corruption. cnt %lu > 31", __func__, cnt ));
458 :
459 : /* Speculatively read the votes out of the state and push them onto
460 : the tower. If there is a conflicting operation during this read,
461 : rollback the tower. */
462 :
463 0 : fd_tower_vote_t vote = { 0 };
464 0 : ulong sz = sizeof(fd_voter_vote_old_t);
465 0 : for( ulong i = 0; i < cnt; i++ ) {
466 0 : if( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_v0_23_5 ) ) {
467 0 : memcpy( (uchar *)&vote, (uchar *)(state->v0_23_5.votes + i), sz );
468 0 : } else if ( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_v1_14_11 ) ) {
469 0 : memcpy( (uchar *)&vote, (uchar *)(state->v1_14_11.votes + i), sz );
470 0 : } else if ( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_current ) ) {
471 0 : memcpy( (uchar *)&vote, (uchar *)(state->votes + i) + sizeof(uchar) /* latency */, sz );
472 0 : } else {
473 0 : FD_LOG_ERR(( "[%s] unknown state->discriminant %u", __func__, state->discriminant ));
474 0 : }
475 0 : fd_tower_votes_push_tail( tower, vote );
476 0 : }
477 :
478 0 : if( FD_LIKELY( fd_funk_rec_query_test( &query ) == FD_FUNK_SUCCESS ) ) return 0;
479 0 : else fd_tower_votes_remove_all( tower ); /* reset the tower and try again */
480 0 : }
481 0 : }
482 :
483 : void
484 : fd_tower_to_vote_txn( fd_tower_t const * tower,
485 : ulong root,
486 : fd_lockout_offset_t * lockouts_scratch,
487 : fd_hash_t const * bank_hash,
488 : fd_hash_t const * recent_blockhash,
489 : fd_pubkey_t const * validator_identity,
490 : fd_pubkey_t const * vote_authority,
491 : fd_pubkey_t const * vote_acc,
492 0 : fd_txn_p_t * vote_txn ) {
493 :
494 0 : fd_compact_vote_state_update_t tower_sync;
495 0 : tower_sync.root = root;
496 0 : tower_sync.lockouts_len = (ushort)fd_tower_votes_cnt( tower );
497 0 : tower_sync.lockouts = lockouts_scratch;
498 0 : tower_sync.timestamp = fd_log_wallclock();
499 0 : tower_sync.has_timestamp = 1;
500 :
501 0 : ulong prev = tower_sync.root;
502 0 : ulong i = 0UL;
503 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
504 0 : !fd_tower_votes_iter_done( tower, iter );
505 0 : iter = fd_tower_votes_iter_next( tower, iter ) ) {
506 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
507 0 : tower_sync.lockouts[i].offset = vote->slot - prev;
508 0 : tower_sync.lockouts[i].confirmation_count = (uchar)vote->conf;
509 0 : prev = vote->slot;
510 0 : i++;
511 0 : }
512 0 : memcpy( tower_sync.hash.uc, bank_hash, sizeof(fd_hash_t) );
513 :
514 0 : uchar * txn_out = vote_txn->payload;
515 0 : uchar * txn_meta_out = vote_txn->_;
516 :
517 0 : int same_addr = !memcmp( validator_identity, vote_authority, sizeof(fd_pubkey_t) );
518 0 : if( FD_LIKELY( same_addr ) ) {
519 :
520 : /* 0: validator identity
521 : 1: vote account address
522 : 2: vote program */
523 :
524 0 : fd_txn_accounts_t accts;
525 0 : accts.signature_cnt = 1;
526 0 : accts.readonly_signed_cnt = 0;
527 0 : accts.readonly_unsigned_cnt = 1;
528 0 : accts.acct_cnt = 3;
529 0 : accts.signers_w = validator_identity;
530 0 : accts.signers_r = NULL;
531 0 : accts.non_signers_w = vote_acc;
532 0 : accts.non_signers_r = &fd_solana_vote_program_id;
533 0 : FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, accts.signature_cnt, &accts, recent_blockhash->uc ) );
534 0 : } else {
535 :
536 : /* 0: validator identity
537 : 1: vote authority
538 : 2: vote account address
539 : 3: vote program */
540 :
541 0 : fd_txn_accounts_t accts;
542 0 : accts.signature_cnt = 2;
543 0 : accts.readonly_signed_cnt = 1;
544 0 : accts.readonly_unsigned_cnt = 1;
545 0 : accts.acct_cnt = 4;
546 0 : accts.signers_w = validator_identity;
547 0 : accts.signers_r = vote_authority;
548 0 : accts.non_signers_w = vote_acc;
549 0 : accts.non_signers_r = &fd_solana_vote_program_id;
550 0 : FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, accts.signature_cnt, &accts, recent_blockhash->uc ) );
551 0 : }
552 :
553 : /* Add the vote instruction to the transaction. */
554 :
555 0 : fd_vote_instruction_t vote_ix;
556 0 : uchar vote_ix_buf[FD_TXN_MTU];
557 0 : vote_ix.discriminant = fd_vote_instruction_enum_compact_update_vote_state;
558 0 : vote_ix.inner.compact_update_vote_state = tower_sync;
559 0 : fd_bincode_encode_ctx_t encode = { .data = vote_ix_buf, .dataend = ( vote_ix_buf + FD_TXN_MTU ) };
560 0 : fd_vote_instruction_encode( &vote_ix, &encode );
561 0 : uchar program_id;
562 0 : uchar ix_accs[2];
563 0 : if( FD_LIKELY( same_addr ) ) {
564 0 : ix_accs[0] = 1; /* vote account address */
565 0 : ix_accs[1] = 0; /* vote authority */
566 0 : program_id = 2; /* vote program */
567 0 : } else {
568 0 : ix_accs[0] = 2; /* vote account address */
569 0 : ix_accs[1] = 1; /* vote authority */
570 0 : program_id = 3; /* vote program */
571 0 : }
572 0 : ushort vote_ix_sz = (ushort)fd_vote_instruction_size( &vote_ix );
573 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 );
574 0 : }
575 :
576 : int
577 0 : fd_tower_verify( fd_tower_t const * tower ) {
578 0 : fd_tower_vote_t const * prev = NULL;
579 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
580 0 : !fd_tower_votes_iter_done( tower, iter );
581 0 : iter = fd_tower_votes_iter_next( tower, iter ) ) {
582 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
583 0 : if( FD_LIKELY( prev && !( vote->slot < prev->slot && vote->conf < prev->conf ) ) ) {
584 0 : FD_LOG_WARNING(( "[%s] invariant violation: vote %lu %lu. prev %lu %lu", __func__, vote->slot, vote->conf, prev->slot, prev->conf ));
585 0 : return -1;
586 0 : }
587 0 : prev = vote;
588 0 : }
589 0 : return 0;
590 0 : }
591 :
592 : #include <stdio.h>
593 :
594 : void
595 0 : fd_tower_print( fd_tower_t const * tower, ulong root ) {
596 0 : FD_LOG_NOTICE( ( "\n\n[Tower]" ) );
597 0 : ulong max_slot = 0;
598 :
599 : /* Determine spacing. */
600 :
601 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
602 0 : !fd_tower_votes_iter_done_rev( tower, iter );
603 0 : iter = fd_tower_votes_iter_prev( tower, iter ) ) {
604 :
605 0 : max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
606 0 : }
607 :
608 : /* Calculate the number of digits in the maximum slot value. */
609 :
610 0 : int digit_cnt = 0;
611 0 : unsigned long rem = max_slot;
612 0 : do {
613 0 : rem /= 10;
614 0 : ++digit_cnt;
615 0 : } while( rem > 0 );
616 :
617 : /* Print the table header */
618 :
619 0 : printf( "slot%*s | %s\n", digit_cnt - (int)strlen("slot"), "", "confirmation count" );
620 :
621 : /* Print the divider line */
622 :
623 0 : for( int i = 0; i < digit_cnt; i++ ) {
624 0 : printf( "-" );
625 0 : }
626 0 : printf( " | " );
627 0 : for( ulong i = 0; i < strlen( "confirmation count" ); i++ ) {
628 0 : printf( "-" );
629 0 : }
630 0 : printf( "\n" );
631 :
632 : /* Print each record in the table */
633 :
634 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
635 0 : !fd_tower_votes_iter_done_rev( tower, iter );
636 0 : iter = fd_tower_votes_iter_prev( tower, iter ) ) {
637 :
638 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
639 0 : printf( "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf );
640 0 : max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
641 0 : }
642 0 : printf( "%*lu | root\n", digit_cnt, root );
643 0 : printf( "\n" );
644 0 : }
|