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 3 : fd_tower_new( void * shmem ) {
11 3 : if( FD_UNLIKELY( !shmem ) ) {
12 0 : FD_LOG_WARNING(( "NULL mem" ));
13 0 : return NULL;
14 0 : }
15 :
16 3 : 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 3 : return fd_tower_votes_new( shmem );
22 3 : }
23 :
24 : fd_tower_t *
25 3 : fd_tower_join( void * shtower ) {
26 :
27 3 : if( FD_UNLIKELY( !shtower ) ) {
28 0 : FD_LOG_WARNING(( "NULL tower" ));
29 0 : return NULL;
30 0 : }
31 :
32 3 : 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 3 : return fd_tower_votes_join( shtower );
38 3 : }
39 :
40 : void *
41 3 : fd_tower_leave( fd_tower_t * tower ) {
42 :
43 3 : if( FD_UNLIKELY( !tower ) ) {
44 0 : FD_LOG_WARNING(( "NULL tower" ));
45 0 : return NULL;
46 0 : }
47 :
48 3 : return fd_tower_votes_leave( tower );
49 3 : }
50 :
51 : void *
52 3 : fd_tower_delete( void * tower ) {
53 :
54 3 : if( FD_UNLIKELY( !tower ) ) {
55 0 : FD_LOG_WARNING(( "NULL tower" ));
56 0 : return NULL;
57 0 : }
58 :
59 3 : 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 3 : return fd_tower_votes_delete( tower );
65 3 : }
66 :
67 : static inline ulong
68 105 : expiration( fd_tower_vote_t const * vote ) {
69 105 : ulong lockout = 1UL << vote->conf;
70 105 : return vote->slot + lockout;
71 105 : }
72 :
73 : static inline ulong
74 102 : simulate_vote( fd_tower_t const * tower, ulong slot ) {
75 102 : ulong cnt = fd_tower_votes_cnt( tower );
76 108 : 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 105 : if( FD_LIKELY( expiration( fd_tower_votes_peek_index_const( tower, cnt - 1 ) ) >= slot ) ) {
82 99 : break;
83 99 : }
84 6 : cnt--;
85 6 : }
86 102 : return cnt;
87 102 : }
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_ele_t const * root = fd_ghost_root_const( 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_ele_t const * root = fd_ghost_root_const( 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_map_t const * map = fd_ghost_map_const( ghost );
172 0 : fd_ghost_ele_t const * pool = fd_ghost_pool_const( ghost );
173 0 : fd_ghost_ele_t const * gca = fd_ghost_gca( ghost, vote->slot, slot );
174 0 : ulong gca_idx = fd_ghost_map_idx_query_const( map, &gca->slot, ULONG_MAX, 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_ele_t const * gca_child = fd_ghost_query_const( ghost, vote->slot );
181 0 : while( FD_LIKELY( gca_child->parent != gca_idx ) ) {
182 0 : gca_child = fd_ghost_pool_ele_const( pool, gca_child->parent );
183 0 : }
184 :
185 0 : ulong switch_stake = 0;
186 0 : fd_ghost_ele_t const * child = fd_ghost_child_const( 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_pool_ele_const( pool, child->sibling );
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 0 : fd_ghost_t const * ghost ) {
283 :
284 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
285 0 : fd_ghost_ele_t const * root = fd_ghost_root_const( ghost );
286 0 : fd_ghost_ele_t const * head = fd_ghost_head( ghost, root );
287 :
288 : /* Reset to the ghost head if any of the following is true:
289 : 1. haven't voted
290 : 2. last vote < ghost root
291 : 3. ghost root is not an ancestory of last vote */
292 :
293 0 : if( FD_UNLIKELY( !vote || vote->slot < root->slot ||
294 0 : !fd_ghost_is_ancestor( ghost, root->slot, vote->slot ) ) ) {
295 0 : return head->slot;
296 0 : }
297 :
298 : /* Find the ghost node keyed by our last vote slot. It is invariant
299 : that this node must always be found after doing the above check.
300 : Otherwise ghost and tower contain implementation bugs and/or are
301 : corrupt. */
302 :
303 0 : fd_ghost_ele_t const * vote_node = fd_ghost_query_const( ghost, vote->slot );
304 0 : #if FD_TOWER_USE_HANDHOLDING
305 0 : if( FD_UNLIKELY( !vote_node ) ) {
306 0 : fd_ghost_print( ghost, 0, root );
307 0 : FD_LOG_ERR(( "[%s] invariant violation: unable to find last tower vote slot %lu in ghost.", __func__, vote->slot ));
308 0 : }
309 0 : #endif
310 :
311 : /* Starting from the node keyed by the last vote slot, greedy traverse
312 : for the head. */
313 :
314 0 : return fd_ghost_head( ghost, vote_node )->slot;
315 0 : }
316 :
317 : ulong
318 : fd_tower_vote_slot( fd_tower_t * tower,
319 : fd_epoch_t const * epoch,
320 : fd_funk_t * funk,
321 : fd_funk_txn_t const * txn,
322 : fd_ghost_t const * ghost,
323 0 : fd_tower_t * scratch ) {
324 :
325 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
326 0 : fd_ghost_ele_t const * root = fd_ghost_root_const( ghost );
327 0 : fd_ghost_ele_t const * head = fd_ghost_head( ghost, root );
328 :
329 : /* Vote for the ghost head if any of the following is true:
330 :
331 : 1. haven't voted
332 : 2. last vote < ghost root
333 : 3. ghost root is not an ancestory of last vote
334 :
335 : FIXME need to ensure lockout safety for case 2 and 3 */
336 :
337 0 : if( FD_UNLIKELY( !vote || vote->slot < root->slot ||
338 0 : !fd_ghost_is_ancestor( ghost, root->slot, vote->slot ) ) ) {
339 0 : return head->slot;
340 0 : }
341 :
342 : /* Optimize for when there is just one fork or that we already
343 : previously voted for the best fork. */
344 :
345 0 : if( FD_LIKELY( fd_ghost_is_ancestor( ghost, vote->slot, head->slot ) ) ) {
346 :
347 : /* The ghost head is on the same fork as our last vote slot, so we
348 : can vote fork it as long as we pass the threshold check. */
349 :
350 0 : if( FD_LIKELY( fd_tower_threshold_check( tower, epoch, funk, txn, head->slot, scratch ) ) ) {
351 0 : FD_LOG_DEBUG(( "[%s] success (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
352 0 : return head->slot;
353 0 : }
354 0 : FD_LOG_DEBUG(( "[%s] failure (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
355 0 : return FD_SLOT_NULL; /* can't vote. need to wait for threshold check. */
356 0 : }
357 :
358 : /* The ghost head is on a different fork from our last vote slot, so
359 : try to switch if we pass lockout and switch threshold. */
360 :
361 0 : if( FD_UNLIKELY( fd_tower_lockout_check( tower, ghost, head->slot ) &&
362 0 : fd_tower_switch_check( tower, epoch, ghost, head->slot ) ) ) {
363 0 : FD_LOG_DEBUG(( "[%s] success (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
364 0 : return head->slot;
365 0 : }
366 0 : FD_LOG_DEBUG(( "[%s] failure (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
367 0 : return FD_SLOT_NULL;
368 0 : }
369 :
370 : ulong
371 99 : fd_tower_vote( fd_tower_t * tower, ulong slot ) {
372 99 : FD_LOG_DEBUG(( "[%s] voting for slot %lu", __func__, slot ));
373 :
374 99 : #if FD_TOWER_USE_HANDHOLDING
375 99 : fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
376 99 : if( FD_UNLIKELY( vote && slot < vote->slot ) ) FD_LOG_ERR(( "[%s] slot %lu < vote->slot %lu", __func__, slot, vote->slot )); /* caller error*/
377 99 : #endif
378 :
379 : /* Use simulate_vote to determine how many expired votes to pop. */
380 :
381 99 : ulong cnt = simulate_vote( tower, slot );
382 :
383 : /* Pop everything that got expired. */
384 :
385 102 : while( fd_tower_votes_cnt( tower ) > cnt ) {
386 3 : fd_tower_votes_pop_tail( tower );
387 3 : }
388 :
389 : /* If the tower is still full after expiring, then pop and return the
390 : bottom vote slot as the new root because this vote has incremented
391 : it to max lockout. Otherwise this is a no-op and there is no new
392 : root (FD_SLOT_NULL). */
393 :
394 99 : ulong root = FD_SLOT_NULL;
395 99 : if( FD_LIKELY( fd_tower_votes_full( tower ) ) ) { /* optimize for full tower */
396 3 : root = fd_tower_votes_pop_head( tower ).slot;
397 3 : }
398 :
399 : /* Increment confirmations (double lockouts) for consecutive
400 : confirmations in prior votes. */
401 :
402 99 : ulong prev_conf = 0;
403 99 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
404 1584 : !fd_tower_votes_iter_done_rev( tower, iter );
405 1488 : iter = fd_tower_votes_iter_prev( tower, iter ) ) {
406 1488 : fd_tower_vote_t * vote = fd_tower_votes_iter_ele( tower, iter );
407 1488 : if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) {
408 3 : break;
409 3 : }
410 1485 : vote->conf++;
411 1485 : }
412 :
413 : /* Add the new vote to the tower. */
414 :
415 99 : fd_tower_votes_push_tail( tower, (fd_tower_vote_t){ .slot = slot, .conf = 1 } );
416 :
417 : /* Return the new root (FD_SLOT_NULL if there is none). */
418 :
419 99 : return root;
420 99 : }
421 :
422 : ulong
423 3 : fd_tower_simulate_vote( fd_tower_t const * tower, ulong slot ) {
424 3 : #if FD_TOWER_USE_HANDHOLDING
425 3 : FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
426 3 : #endif
427 :
428 3 : return simulate_vote( tower, slot );
429 3 : }
430 :
431 : int
432 : fd_tower_from_vote_acc( fd_tower_t * tower,
433 : fd_funk_t * funk,
434 : fd_funk_txn_t const * txn,
435 0 : fd_funk_rec_key_t const * vote_acc ) {
436 0 : # if FD_TOWER_USE_HANDHOLDING
437 0 : FD_TEST( fd_tower_votes_empty( tower ) );
438 0 : # endif
439 :
440 0 : for(;;) {
441 :
442 : /* Speculatively query the record and parse the voter state. If the
443 : record is missing or the voter state fails to parse, then return
444 : early (tower will be empty). */
445 :
446 0 : fd_funk_rec_query_t query;
447 0 : fd_funk_rec_t const * rec = fd_funk_rec_query_try_global( funk, txn, vote_acc, NULL, &query );
448 0 : if( FD_UNLIKELY( !rec ) ) return -1; /* record not found */
449 0 : fd_voter_state_t const * state = fd_voter_state( funk, rec );
450 0 : if( FD_UNLIKELY( !state ) ) return -1; /* unable to parse voter state */
451 :
452 : /* Speculatively query the cnt. */
453 :
454 0 : ulong cnt = fd_voter_state_cnt( state ); /* TODO remove once Funk reads are safe */
455 0 : if( FD_UNLIKELY( fd_funk_rec_query_test( &query ) != FD_FUNK_SUCCESS ) ) continue;
456 0 : if( FD_UNLIKELY( cnt > 31UL ) ) FD_LOG_ERR(( "[%s] funk vote account corruption. cnt %lu > 31", __func__, cnt ));
457 :
458 : /* Speculatively read the votes out of the state and push them onto
459 : the tower. If there is a conflicting operation during this read,
460 : rollback the tower. */
461 :
462 0 : fd_tower_vote_t vote = { 0 };
463 0 : ulong sz = sizeof(fd_voter_vote_old_t);
464 0 : for( ulong i = 0; i < cnt; i++ ) {
465 0 : if( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_v0_23_5 ) ) {
466 0 : memcpy( (uchar *)&vote, (uchar *)(state->v0_23_5.votes + i), sz );
467 0 : } else if ( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_v1_14_11 ) ) {
468 0 : memcpy( (uchar *)&vote, (uchar *)(state->v1_14_11.votes + i), sz );
469 0 : } else if ( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_current ) ) {
470 0 : memcpy( (uchar *)&vote, (uchar *)(state->votes + i) + sizeof(uchar) /* latency */, sz );
471 0 : } else {
472 0 : FD_LOG_ERR(( "[%s] unknown state->discriminant %u", __func__, state->discriminant ));
473 0 : }
474 0 : fd_tower_votes_push_tail( tower, vote );
475 0 : }
476 :
477 0 : if( FD_LIKELY( fd_funk_rec_query_test( &query ) == FD_FUNK_SUCCESS ) ) return 0;
478 0 : else fd_tower_votes_remove_all( tower ); /* reset the tower and try again */
479 0 : }
480 0 : }
481 :
482 : void
483 : fd_tower_to_vote_txn( fd_tower_t const * tower,
484 : ulong root,
485 : fd_lockout_offset_t * lockouts_scratch,
486 : fd_hash_t const * bank_hash,
487 : fd_hash_t const * recent_blockhash,
488 : fd_pubkey_t const * validator_identity,
489 : fd_pubkey_t const * vote_authority,
490 : fd_pubkey_t const * vote_acc,
491 0 : fd_txn_p_t * vote_txn ) {
492 :
493 0 : fd_compact_vote_state_update_t tower_sync;
494 0 : tower_sync.root = root;
495 0 : tower_sync.lockouts_len = (ushort)fd_tower_votes_cnt( tower );
496 0 : tower_sync.lockouts = lockouts_scratch;
497 0 : tower_sync.timestamp = fd_log_wallclock();
498 0 : tower_sync.has_timestamp = 1;
499 :
500 0 : ulong prev = tower_sync.root;
501 0 : ulong i = 0UL;
502 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
503 0 : !fd_tower_votes_iter_done( tower, iter );
504 0 : iter = fd_tower_votes_iter_next( tower, iter ) ) {
505 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
506 0 : tower_sync.lockouts[i].offset = vote->slot - prev;
507 0 : tower_sync.lockouts[i].confirmation_count = (uchar)vote->conf;
508 0 : prev = vote->slot;
509 0 : i++;
510 0 : }
511 0 : memcpy( tower_sync.hash.uc, bank_hash, sizeof(fd_hash_t) );
512 :
513 0 : uchar * txn_out = vote_txn->payload;
514 0 : uchar * txn_meta_out = vote_txn->_;
515 :
516 0 : int same_addr = !memcmp( validator_identity, vote_authority, sizeof(fd_pubkey_t) );
517 0 : if( FD_LIKELY( same_addr ) ) {
518 :
519 : /* 0: validator identity
520 : 1: vote account address
521 : 2: vote program */
522 :
523 0 : fd_txn_accounts_t accts;
524 0 : accts.signature_cnt = 1;
525 0 : accts.readonly_signed_cnt = 0;
526 0 : accts.readonly_unsigned_cnt = 1;
527 0 : accts.acct_cnt = 3;
528 0 : accts.signers_w = validator_identity;
529 0 : accts.signers_r = NULL;
530 0 : accts.non_signers_w = vote_acc;
531 0 : accts.non_signers_r = &fd_solana_vote_program_id;
532 0 : FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, accts.signature_cnt, &accts, recent_blockhash->uc ) );
533 0 : } else {
534 :
535 : /* 0: validator identity
536 : 1: vote authority
537 : 2: vote account address
538 : 3: vote program */
539 :
540 0 : fd_txn_accounts_t accts;
541 0 : accts.signature_cnt = 2;
542 0 : accts.readonly_signed_cnt = 1;
543 0 : accts.readonly_unsigned_cnt = 1;
544 0 : accts.acct_cnt = 4;
545 0 : accts.signers_w = validator_identity;
546 0 : accts.signers_r = vote_authority;
547 0 : accts.non_signers_w = vote_acc;
548 0 : accts.non_signers_r = &fd_solana_vote_program_id;
549 0 : FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, accts.signature_cnt, &accts, recent_blockhash->uc ) );
550 0 : }
551 :
552 : /* Add the vote instruction to the transaction. */
553 :
554 0 : fd_vote_instruction_t vote_ix;
555 0 : uchar vote_ix_buf[FD_TXN_MTU];
556 0 : vote_ix.discriminant = fd_vote_instruction_enum_compact_update_vote_state;
557 0 : vote_ix.inner.compact_update_vote_state = tower_sync;
558 0 : fd_bincode_encode_ctx_t encode = { .data = vote_ix_buf, .dataend = ( vote_ix_buf + FD_TXN_MTU ) };
559 0 : fd_vote_instruction_encode( &vote_ix, &encode );
560 0 : uchar program_id;
561 0 : uchar ix_accs[2];
562 0 : if( FD_LIKELY( same_addr ) ) {
563 0 : ix_accs[0] = 1; /* vote account address */
564 0 : ix_accs[1] = 0; /* vote authority */
565 0 : program_id = 2; /* vote program */
566 0 : } else {
567 0 : ix_accs[0] = 2; /* vote account address */
568 0 : ix_accs[1] = 1; /* vote authority */
569 0 : program_id = 3; /* vote program */
570 0 : }
571 0 : ushort vote_ix_sz = (ushort)fd_vote_instruction_size( &vote_ix );
572 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 );
573 0 : }
574 :
575 : int
576 0 : fd_tower_verify( fd_tower_t const * tower ) {
577 0 : fd_tower_vote_t const * prev = NULL;
578 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
579 0 : !fd_tower_votes_iter_done( tower, iter );
580 0 : iter = fd_tower_votes_iter_next( tower, iter ) ) {
581 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
582 0 : if( FD_LIKELY( prev && !( vote->slot < prev->slot && vote->conf < prev->conf ) ) ) {
583 0 : FD_LOG_WARNING(( "[%s] invariant violation: vote %lu %lu. prev %lu %lu", __func__, vote->slot, vote->conf, prev->slot, prev->conf ));
584 0 : return -1;
585 0 : }
586 0 : prev = vote;
587 0 : }
588 0 : return 0;
589 0 : }
590 :
591 : #include <stdio.h>
592 :
593 : void
594 0 : fd_tower_print( fd_tower_t const * tower, ulong root ) {
595 0 : FD_LOG_NOTICE( ( "\n\n[Tower]" ) );
596 0 : ulong max_slot = 0;
597 :
598 : /* Determine spacing. */
599 :
600 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
601 0 : !fd_tower_votes_iter_done_rev( tower, iter );
602 0 : iter = fd_tower_votes_iter_prev( tower, iter ) ) {
603 :
604 0 : max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
605 0 : }
606 :
607 : /* Calculate the number of digits in the maximum slot value. */
608 :
609 0 : int digit_cnt = 0;
610 0 : unsigned long rem = max_slot;
611 0 : do {
612 0 : rem /= 10;
613 0 : ++digit_cnt;
614 0 : } while( rem > 0 );
615 :
616 : /* Print the table header */
617 :
618 0 : printf( "slot%*s | %s\n", digit_cnt - (int)strlen("slot"), "", "confirmation count" );
619 :
620 : /* Print the divider line */
621 :
622 0 : for( int i = 0; i < digit_cnt; i++ ) {
623 0 : printf( "-" );
624 0 : }
625 0 : printf( " | " );
626 0 : for( ulong i = 0; i < strlen( "confirmation count" ); i++ ) {
627 0 : printf( "-" );
628 0 : }
629 0 : printf( "\n" );
630 :
631 : /* Print each record in the table */
632 :
633 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
634 0 : !fd_tower_votes_iter_done_rev( tower, iter );
635 0 : iter = fd_tower_votes_iter_prev( tower, iter ) ) {
636 :
637 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
638 0 : printf( "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf );
639 0 : max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
640 0 : }
641 0 : printf( "%*lu | root\n", digit_cnt, root );
642 : printf( "\n" );
643 0 : }
|