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_spad_t * runtime_spad ) {
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 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
238 0 : void * mem = fd_spad_alloc( runtime_spad, fd_tower_align(), fd_tower_footprint() );
239 0 : fd_tower_t * voter_tower = fd_tower_join( fd_tower_new( mem ) );
240 0 : fd_tower_from_vote_acc( voter_tower, funk, txn, &voter->rec );
241 :
242 : /* If this voter has not voted, continue. */
243 :
244 0 : if( FD_UNLIKELY( fd_tower_votes_empty( voter_tower ) ) ) continue;
245 :
246 0 : ulong cnt = simulate_vote( voter_tower, slot );
247 :
248 : /* Continue if their tower is empty after simulating. */
249 :
250 0 : if( FD_UNLIKELY( !cnt ) ) continue;
251 :
252 : /* Get their latest vote. */
253 :
254 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_index( voter_tower, cnt - 1 );
255 :
256 : /* Count their stake towards the threshold check if their latest
257 : vote slot >= our threshold slot.
258 :
259 : Because we are iterating vote accounts on the same fork that we
260 : we want to vote for, we know these slots must all occur along
261 : the same fork ancestry.
262 :
263 : Therefore, if their latest vote slot >= our threshold slot, we
264 : know that vote must be for the threshold slot itself or one of
265 : threshold slot's descendants. */
266 :
267 0 : if( FD_LIKELY( vote->slot >= threshold_slot ) ) {
268 0 : threshold_stake += voter->stake;
269 0 : }
270 0 : } FD_SPAD_FRAME_END;
271 0 : }
272 :
273 0 : double threshold_pct = (double)threshold_stake / (double)epoch->total_stake;
274 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 ));
275 0 : return threshold_pct > THRESHOLD_PCT;
276 0 : }
277 :
278 : ulong
279 : fd_tower_reset_slot( fd_tower_t const * tower,
280 : fd_epoch_t const * epoch,
281 0 : fd_ghost_t const * ghost ) {
282 :
283 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
284 0 : fd_ghost_node_t const * root = fd_ghost_root( ghost );
285 0 : fd_ghost_node_t const * head = fd_ghost_head( ghost, root );
286 :
287 : /* Reset to the ghost head if any of the following is true:
288 : 1. haven't voted
289 : 2. last vote < ghost root
290 : 3. ghost root is not an ancestory of last vote */
291 :
292 0 : if( FD_UNLIKELY( !vote || vote->slot < root->slot ||
293 0 : !fd_ghost_is_ancestor( ghost, root->slot, vote->slot ) ) ) {
294 0 : return head->slot;
295 0 : }
296 :
297 : /* Find the ghost node keyed by our last vote slot. It is invariant
298 : that this node must always be found after doing the above check.
299 : Otherwise ghost and tower contain implementation bugs and/or are
300 : corrupt. */
301 :
302 0 : fd_ghost_node_t const * vote_node = fd_ghost_query( ghost, vote->slot );
303 0 : #if FD_TOWER_USE_HANDHOLDING
304 0 : if( FD_UNLIKELY( !vote_node ) ) {
305 0 : fd_ghost_print( ghost, epoch, root );
306 0 : FD_LOG_ERR(( "[%s] invariant violation: unable to find last tower vote slot %lu in ghost.", __func__, vote->slot ));
307 0 : }
308 0 : #endif
309 :
310 : /* Starting from the node keyed by the last vote slot, greedy traverse
311 : for the head. */
312 :
313 0 : return fd_ghost_head( ghost, vote_node )->slot;
314 0 : }
315 :
316 : ulong
317 : fd_tower_vote_slot( fd_tower_t * tower,
318 : fd_epoch_t const * epoch,
319 : fd_funk_t * funk,
320 : fd_funk_txn_t const * txn,
321 : fd_ghost_t const * ghost,
322 0 : fd_spad_t * runtime_spad ) {
323 :
324 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
325 0 : fd_ghost_node_t const * root = fd_ghost_root( ghost );
326 0 : fd_ghost_node_t const * head = fd_ghost_head( ghost, root );
327 :
328 : /* Vote for the ghost head if any of the following is true:
329 :
330 : 1. haven't voted
331 : 2. last vote < ghost root
332 : 3. ghost root is not an ancestory of last vote
333 :
334 : FIXME need to ensure lockout safety for case 2 and 3 */
335 :
336 0 : if( FD_UNLIKELY( !vote || vote->slot < root->slot ||
337 0 : !fd_ghost_is_ancestor( ghost, root->slot, vote->slot ) ) ) {
338 0 : return head->slot;
339 0 : }
340 :
341 : /* Optimize for when there is just one fork or that we already
342 : previously voted for the best fork. */
343 :
344 0 : if( FD_LIKELY( fd_ghost_is_ancestor( ghost, vote->slot, head->slot ) ) ) {
345 :
346 : /* The ghost head is on the same fork as our last vote slot, so we
347 : can vote fork it as long as we pass the threshold check. */
348 :
349 0 : if( FD_LIKELY( fd_tower_threshold_check( tower, epoch, funk, txn, head->slot, runtime_spad ) ) ) {
350 0 : FD_LOG_DEBUG(( "[%s] success (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
351 0 : return head->slot;
352 0 : }
353 0 : FD_LOG_DEBUG(( "[%s] failure (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
354 0 : return FD_SLOT_NULL; /* can't vote. need to wait for threshold check. */
355 0 : }
356 :
357 : /* The ghost head is on a different fork from our last vote slot, so
358 : try to switch if we pass lockout and switch threshold. */
359 :
360 0 : if( FD_UNLIKELY( fd_tower_lockout_check( tower, ghost, head->slot ) &&
361 0 : fd_tower_switch_check( tower, epoch, ghost, head->slot ) ) ) {
362 0 : FD_LOG_DEBUG(( "[%s] success (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
363 0 : return head->slot;
364 0 : }
365 0 : FD_LOG_DEBUG(( "[%s] failure (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
366 0 : return FD_SLOT_NULL;
367 0 : }
368 :
369 : ulong
370 0 : fd_tower_vote( fd_tower_t * tower, ulong slot ) {
371 0 : FD_LOG_DEBUG(( "[%s] voting for slot %lu", __func__, slot ));
372 :
373 0 : #if FD_TOWER_USE_HANDHOLDING
374 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
375 0 : if( FD_UNLIKELY( vote && slot < vote->slot ) ) FD_LOG_ERR(( "[%s] slot %lu < vote->slot %lu", __func__, slot, vote->slot )); /* caller error*/
376 0 : #endif
377 :
378 : /* Use simulate_vote to determine how many expired votes to pop. */
379 :
380 0 : ulong cnt = simulate_vote( tower, slot );
381 :
382 : /* Pop everything that got expired. */
383 :
384 0 : while( fd_tower_votes_cnt( tower ) > cnt ) {
385 0 : fd_tower_votes_pop_tail( tower );
386 0 : }
387 :
388 : /* If the tower is still full after expiring, then pop and return the
389 : bottom vote slot as the new root because this vote has incremented
390 : it to max lockout. Otherwise this is a no-op and there is no new
391 : root (FD_SLOT_NULL). */
392 :
393 0 : ulong root = FD_SLOT_NULL;
394 0 : if( FD_LIKELY( fd_tower_votes_full( tower ) ) ) { /* optimize for full tower */
395 0 : root = fd_tower_votes_pop_head( tower ).slot;
396 0 : }
397 :
398 : /* Increment confirmations (double lockouts) for consecutive
399 : confirmations in prior votes. */
400 :
401 0 : ulong prev_conf = 0;
402 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
403 0 : !fd_tower_votes_iter_done_rev( tower, iter );
404 0 : iter = fd_tower_votes_iter_prev( tower, iter ) ) {
405 0 : fd_tower_vote_t * vote = fd_tower_votes_iter_ele( tower, iter );
406 0 : if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) {
407 0 : break;
408 0 : }
409 0 : vote->conf++;
410 0 : }
411 :
412 : /* Add the new vote to the tower. */
413 :
414 0 : fd_tower_votes_push_tail( tower, (fd_tower_vote_t){ .slot = slot, .conf = 1 } );
415 :
416 : /* Return the new root (FD_SLOT_NULL if there is none). */
417 :
418 0 : return root;
419 0 : }
420 :
421 : ulong
422 0 : fd_tower_simulate_vote( fd_tower_t const * tower, ulong slot ) {
423 0 : #if FD_TOWER_USE_HANDHOLDING
424 0 : FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
425 0 : #endif
426 :
427 0 : return simulate_vote( tower, slot );
428 0 : }
429 :
430 : void
431 : fd_tower_from_vote_acc( fd_tower_t * tower,
432 : fd_funk_t * funk,
433 : fd_funk_txn_t const * txn,
434 0 : fd_funk_rec_key_t const * vote_acc ) {
435 0 : #if FD_TOWER_USE_HANDHOLDING
436 0 : if( FD_UNLIKELY( !fd_tower_votes_empty( tower ) ) ) FD_LOG_ERR(( "[%s] cannot write to non-empty tower", __func__ ));
437 0 : #endif
438 :
439 0 : fd_voter_state_t const * state = fd_voter_state( funk, txn, vote_acc );
440 0 : if( FD_UNLIKELY(!state ) ) return;
441 :
442 0 : fd_tower_vote_t vote = { 0 };
443 0 : ulong sz = sizeof(fd_voter_vote_old_t);
444 0 : for( ulong i = 0; i < fd_voter_state_cnt( state ); i++ ) {
445 0 : if( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_v0_23_5 ) ) {
446 0 : memcpy( (uchar *)&vote, (uchar *)(state->v0_23_5.votes + i), sz );
447 0 : } else if ( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_v1_14_11 ) ) {
448 0 : memcpy( (uchar *)&vote, (uchar *)(state->v1_14_11.votes + i), sz );
449 0 : } else if ( FD_UNLIKELY( state->discriminant == fd_vote_state_versioned_enum_current ) ) {
450 0 : memcpy( (uchar *)&vote, (uchar *)(state->votes + i) + sizeof(uchar) /* latency */, sz );
451 0 : } else {
452 0 : FD_LOG_ERR(( "[%s] unknown state->discriminant %u", __func__, state->discriminant ));
453 0 : }
454 0 : fd_tower_votes_push_tail( tower, vote );
455 0 : }
456 0 : }
457 :
458 : void
459 : fd_tower_to_vote_txn( fd_tower_t const * tower,
460 : ulong root,
461 : fd_hash_t const * bank_hash,
462 : fd_hash_t const * recent_blockhash,
463 : fd_pubkey_t const * validator_identity,
464 : fd_pubkey_t const * vote_authority,
465 : fd_pubkey_t const * vote_acc,
466 : fd_txn_p_t * vote_txn,
467 0 : fd_spad_t * runtime_spad ) {
468 :
469 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
470 :
471 0 : fd_compact_vote_state_update_t tower_sync[1] = { 0 };
472 :
473 0 : tower_sync->root = root;
474 0 : long ts = fd_log_wallclock();
475 0 : tower_sync->has_timestamp = 1;
476 0 : tower_sync->timestamp = ts;
477 0 : tower_sync->lockouts_len = (ushort)fd_tower_votes_cnt( tower );
478 0 : tower_sync->lockouts = (fd_lockout_offset_t *)fd_spad_alloc( runtime_spad, alignof(fd_lockout_offset_t),tower_sync->lockouts_len * sizeof(fd_lockout_offset_t) );
479 :
480 0 : ulong i = 0UL;
481 0 : ulong curr_slot = tower_sync->root;
482 :
483 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
484 0 : !fd_tower_votes_iter_done( tower, iter );
485 0 : iter = fd_tower_votes_iter_next( tower, iter ) ) {
486 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
487 0 : FD_TEST( vote->slot >= tower_sync->root );
488 0 : ulong offset = vote->slot - curr_slot;
489 0 : curr_slot = vote->slot;
490 0 : uchar conf = (uchar)vote->conf;
491 0 : tower_sync->lockouts[i].offset = offset;
492 0 : tower_sync->lockouts[i].confirmation_count = conf;
493 0 : memcpy( tower_sync->hash.uc, bank_hash, sizeof(fd_hash_t) );
494 0 : i++;
495 0 : }
496 :
497 0 : uchar * txn_out = vote_txn->payload;
498 0 : uchar * txn_meta_out = vote_txn->_;
499 :
500 0 : int same_addr = !memcmp( validator_identity, vote_authority, sizeof(fd_pubkey_t) );
501 0 : if( FD_LIKELY( same_addr ) ) {
502 :
503 : /* 0: validator identity
504 : 1: vote account address
505 : 2: vote program */
506 :
507 0 : fd_txn_accounts_t accts;
508 0 : accts.signature_cnt = 1;
509 0 : accts.readonly_signed_cnt = 0;
510 0 : accts.readonly_unsigned_cnt = 1;
511 0 : accts.acct_cnt = 3;
512 0 : accts.signers_w = validator_identity;
513 0 : accts.signers_r = NULL;
514 0 : accts.non_signers_w = vote_acc;
515 0 : accts.non_signers_r = &fd_solana_vote_program_id;
516 0 : FD_TEST( fd_txn_base_generate( txn_meta_out,
517 0 : txn_out,
518 0 : accts.signature_cnt,
519 0 : &accts,
520 0 : recent_blockhash->uc ) );
521 0 : } else {
522 :
523 : /* 0: validator identity
524 : 1: vote authority
525 : 2: vote account address
526 : 3: vote program */
527 :
528 0 : fd_txn_accounts_t accts;
529 0 : accts.signature_cnt = 2;
530 0 : accts.readonly_signed_cnt = 1;
531 0 : accts.readonly_unsigned_cnt = 1;
532 0 : accts.acct_cnt = 4;
533 0 : accts.signers_w = validator_identity;
534 0 : accts.signers_r = vote_authority;
535 0 : accts.non_signers_w = vote_acc;
536 0 : accts.non_signers_r = &fd_solana_vote_program_id;
537 0 : FD_TEST( fd_txn_base_generate( txn_meta_out,
538 0 : txn_out,
539 0 : accts.signature_cnt,
540 0 : &accts,
541 0 : recent_blockhash->uc ) );
542 0 : }
543 :
544 : /* Add the vote instruction to the transaction. */
545 :
546 0 : fd_vote_instruction_t vote_ix;
547 0 : uchar vote_ix_buf[FD_TXN_MTU];
548 0 : vote_ix.discriminant = fd_vote_instruction_enum_compact_update_vote_state;
549 0 : vote_ix.inner.compact_update_vote_state = *tower_sync;
550 0 : fd_bincode_encode_ctx_t encode = { .data = vote_ix_buf, .dataend = ( vote_ix_buf + FD_TXN_MTU ) };
551 0 : fd_vote_instruction_encode( &vote_ix, &encode );
552 0 : ushort vote_ix_size = (ushort)fd_vote_instruction_size( &vote_ix );
553 :
554 0 : uchar ix_accs[2];
555 0 : uchar program_id;
556 0 : if( FD_LIKELY( same_addr ) ) {
557 0 : ix_accs[0] = 1; /* vote account address */
558 0 : ix_accs[1] = 0; /* vote authority */
559 0 : program_id = 2; /* vote program */
560 0 : } else {
561 0 : ix_accs[0] = 2; /* vote account address */
562 0 : ix_accs[1] = 1; /* vote authority */
563 0 : program_id = 3; /* vote program */
564 0 : }
565 0 : vote_txn->payload_sz = fd_txn_add_instr( txn_meta_out, txn_out, program_id, ix_accs, 2, vote_ix_buf, vote_ix_size );
566 :
567 0 : } FD_SPAD_FRAME_END;
568 0 : }
569 :
570 : int
571 0 : fd_tower_verify( fd_tower_t const * tower ) {
572 0 : fd_tower_vote_t const * prev = NULL;
573 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
574 0 : !fd_tower_votes_iter_done( tower, iter );
575 0 : iter = fd_tower_votes_iter_next( tower, iter ) ) {
576 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
577 0 : if( FD_LIKELY( prev && !( vote->slot < prev->slot && vote->conf < prev->conf ) ) ) {
578 0 : FD_LOG_WARNING(( "[%s] invariant violation: vote %lu %lu. prev %lu %lu", __func__, vote->slot, vote->conf, prev->slot, prev->conf ));
579 0 : return -1;
580 0 : }
581 0 : prev = vote;
582 0 : }
583 0 : return 0;
584 0 : }
585 :
586 : #include <stdio.h>
587 :
588 : void
589 0 : fd_tower_print( fd_tower_t const * tower, ulong root ) {
590 0 : FD_LOG_NOTICE( ( "\n\n[Tower]" ) );
591 0 : ulong max_slot = 0;
592 :
593 : /* Determine spacing. */
594 :
595 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
596 0 : !fd_tower_votes_iter_done_rev( tower, iter );
597 0 : iter = fd_tower_votes_iter_prev( tower, iter ) ) {
598 :
599 0 : max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
600 0 : }
601 :
602 : /* Calculate the number of digits in the maximum slot value. */
603 :
604 0 : int digit_cnt = 0;
605 0 : unsigned long rem = max_slot;
606 0 : do {
607 0 : rem /= 10;
608 0 : ++digit_cnt;
609 0 : } while( rem > 0 );
610 :
611 : /* Print the table header */
612 :
613 0 : printf( "slot%*s | %s\n", digit_cnt - (int)strlen("slot"), "", "confirmation count" );
614 :
615 : /* Print the divider line */
616 :
617 0 : for( int i = 0; i < digit_cnt; i++ ) {
618 0 : printf( "-" );
619 0 : }
620 0 : printf( " | " );
621 0 : for( ulong i = 0; i < strlen( "confirmation count" ); i++ ) {
622 0 : printf( "-" );
623 0 : }
624 0 : printf( "\n" );
625 :
626 : /* Print each record in the table */
627 :
628 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
629 0 : !fd_tower_votes_iter_done_rev( tower, iter );
630 0 : iter = fd_tower_votes_iter_prev( tower, iter ) ) {
631 :
632 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
633 0 : printf( "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf );
634 0 : max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
635 0 : }
636 0 : printf( "%*lu | root\n", digit_cnt, root );
637 0 : printf( "\n" );
638 0 : }
|