Line data Source code
1 : #include <unistd.h>
2 : #include <fcntl.h>
3 : #include <sys/stat.h>
4 :
5 : #include "fd_tower.h"
6 : #include "../../ballet/ed25519/fd_ed25519.h"
7 : #include "../../flamenco/txn/fd_txn_generate.h"
8 : #include "../../flamenco/runtime/fd_system_ids.h"
9 :
10 0 : #define THRESHOLD_DEPTH (8)
11 0 : #define THRESHOLD_RATIO (2.0 / 3.0)
12 : #define SHALLOW_THRESHOLD_DEPTH (4)
13 : #define SHALLOW_THRESHOLD_RATIO (0.38)
14 0 : #define SWITCH_PCT (0.38)
15 0 : #define SERDE_KIND (1)
16 0 : #define SERDE_LAST_VOTE_KIND (3)
17 :
18 : void *
19 3 : fd_tower_new( void * shmem ) {
20 3 : if( FD_UNLIKELY( !shmem ) ) {
21 0 : FD_LOG_WARNING(( "NULL mem" ));
22 0 : return NULL;
23 0 : }
24 :
25 3 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_tower_align() ) ) ) {
26 0 : FD_LOG_WARNING(( "misaligned mem" ));
27 0 : return NULL;
28 0 : }
29 :
30 3 : return fd_tower_votes_new( shmem );
31 3 : }
32 :
33 : fd_tower_t *
34 3 : fd_tower_join( void * shtower ) {
35 :
36 3 : if( FD_UNLIKELY( !shtower ) ) {
37 0 : FD_LOG_WARNING(( "NULL tower" ));
38 0 : return NULL;
39 0 : }
40 :
41 3 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shtower, fd_tower_align() ) ) ) {
42 0 : FD_LOG_WARNING(( "misaligned tower" ));
43 0 : return NULL;
44 0 : }
45 :
46 3 : return fd_tower_votes_join( shtower );
47 3 : }
48 :
49 : void *
50 0 : fd_tower_leave( fd_tower_t * tower ) {
51 :
52 0 : if( FD_UNLIKELY( !tower ) ) {
53 0 : FD_LOG_WARNING(( "NULL tower" ));
54 0 : return NULL;
55 0 : }
56 :
57 0 : return fd_tower_votes_leave( tower );
58 0 : }
59 :
60 : void *
61 0 : fd_tower_delete( void * tower ) {
62 :
63 0 : if( FD_UNLIKELY( !tower ) ) {
64 0 : FD_LOG_WARNING(( "NULL tower" ));
65 0 : return NULL;
66 0 : }
67 :
68 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)tower, fd_tower_align() ) ) ) {
69 0 : FD_LOG_WARNING(( "misaligned tower" ));
70 0 : return NULL;
71 0 : }
72 :
73 0 : return fd_tower_votes_delete( tower );
74 0 : }
75 :
76 : static inline ulong
77 0 : expiration( fd_tower_vote_t const * vote ) {
78 0 : ulong lockout = 1UL << vote->conf;
79 0 : return vote->slot + lockout;
80 0 : }
81 :
82 : static inline ulong
83 0 : simulate_vote( fd_tower_t const * tower, ulong slot ) {
84 0 : ulong cnt = fd_tower_votes_cnt( tower );
85 0 : while( cnt ) {
86 :
87 : /* Return early if we can't pop the top tower vote, even if votes
88 : below it are expired. */
89 :
90 0 : if( FD_LIKELY( expiration( fd_tower_votes_peek_index_const( tower, cnt - 1 ) ) >= slot ) ) {
91 0 : break;
92 0 : }
93 0 : cnt--;
94 0 : }
95 0 : return cnt;
96 0 : }
97 :
98 : int
99 : fd_tower_lockout_check( fd_tower_t const * tower,
100 : fd_ghost_t const * ghost,
101 : ulong slot,
102 0 : fd_hash_t const * block_id ) {
103 0 : # if FD_TOWER_PARANOID
104 0 : FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
105 0 : # endif
106 :
107 : /* Simulate a vote to pop off all the votes that have been expired at
108 : the top of the tower. */
109 :
110 0 : ulong cnt = simulate_vote( tower, slot );
111 :
112 : /* By definition, all votes in the tower must be for the same fork, so
113 : check if the previous vote (ie. the last vote in the tower) is on
114 : the same fork as the fork we want to vote for. We do this using
115 : ghost by checking if the previous vote slot is an ancestor of the
116 : `slot`. If the previous vote slot is too old (ie. older than
117 : ghost->root), then we don't have ancestry information anymore and
118 : we just assume it is on the same fork.
119 :
120 : FIXME discuss if it is safe to assume that? */
121 :
122 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_index_const( tower, cnt - 1 );
123 0 : fd_ghost_ele_t const * root = fd_ghost_root_const( ghost );
124 :
125 0 : int lockout_check = (slot > vote->slot) && (vote->slot < root->slot || fd_ghost_is_ancestor( ghost, fd_ghost_hash( ghost, vote->slot ), block_id ));
126 : # if LOGGING
127 : FD_LOG_NOTICE(( "[%s] %d. top: (slot: %lu, conf: %lu). switch: %lu.", __func__, lockout_check, vote->slot, vote->conf, slot ));
128 : # endif
129 0 : return lockout_check;
130 0 : }
131 :
132 : int
133 : fd_tower_switch_check( fd_tower_t const * tower,
134 : fd_epoch_t const * epoch,
135 : fd_ghost_t const * ghost,
136 : ulong slot,
137 0 : fd_hash_t const * block_id ) {
138 0 : #if FD_TOWER_PARANOID
139 0 : FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
140 0 : #endif
141 :
142 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
143 0 : fd_ghost_ele_t const * root = fd_ghost_root_const( ghost );
144 :
145 0 : if( FD_UNLIKELY( vote->slot < root->slot ) ) {
146 :
147 : /* It is possible our last vote slot precedes our ghost root. This
148 : can happen, for example, when we restart from a snapshot and set
149 : the ghost root to the snapshot slot (we won't have an ancestry
150 : before the snapshot slot.)
151 :
152 : If this is the case, we assume it's ok to switch. */
153 :
154 0 : return 1;
155 0 : }
156 :
157 : /* fd_tower_switch_check is only called if latest_vote->slot and
158 : fork->slot are on different forks (determined by is_descendant), so
159 : they must not fall on the same ancestry path back to the gca.
160 :
161 : INVALID:
162 :
163 : 0
164 : \
165 : 1 <- a
166 : \
167 : 2 <- b
168 :
169 : VALID:
170 :
171 : 0
172 : / \
173 : 1 2
174 : ^ ^
175 : a b
176 :
177 : */
178 :
179 0 : # if FD_TOWER_PARANOID
180 0 : FD_TEST( !fd_ghost_is_ancestor( ghost, fd_ghost_hash( ghost, vote->slot ), block_id ) );
181 0 : # endif
182 0 : fd_hash_t const * vote_block_id = fd_ghost_hash( ghost, vote->slot );
183 0 : fd_ghost_hash_map_t const * maph = fd_ghost_hash_map_const( ghost );
184 0 : fd_ghost_ele_t const * pool = fd_ghost_pool_const( ghost );
185 0 : fd_ghost_ele_t const * gca = fd_ghost_gca( ghost, vote_block_id, block_id );
186 0 : ulong gca_idx = fd_ghost_hash_map_idx_query_const( maph, &gca->key, ULONG_MAX, pool );
187 :
188 : /* gca_child is our latest_vote slot's ancestor that is also a direct
189 : child of GCA. So we do not count it towards the stake of the
190 : different forks. */
191 :
192 0 : fd_ghost_ele_t const * gca_child = fd_ghost_query_const( ghost, vote_block_id );
193 0 : while( FD_LIKELY( gca_child->parent != gca_idx ) ) {
194 0 : gca_child = fd_ghost_pool_ele_const( pool, gca_child->parent );
195 0 : }
196 :
197 0 : ulong switch_stake = 0;
198 0 : fd_ghost_ele_t const * child = fd_ghost_child_const( ghost, gca );
199 0 : while( FD_LIKELY( child ) ) {
200 0 : if( FD_LIKELY( child != gca_child ) ) {
201 0 : switch_stake += child->weight;
202 0 : }
203 0 : child = fd_ghost_pool_ele_const( pool, child->sibling );
204 0 : }
205 :
206 0 : double switch_pct = (double)switch_stake / (double)epoch->total_stake;
207 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 ));
208 0 : return switch_pct > SWITCH_PCT;
209 0 : }
210 :
211 : int
212 : fd_tower_threshold_check( fd_tower_t const * tower,
213 : fd_epoch_t * epoch,
214 : fd_pubkey_t * vote_keys,
215 : fd_tower_t * const * vote_towers,
216 : ulong vote_cnt,
217 0 : ulong slot ) {
218 :
219 : /* First, simulate a vote, popping off everything that would be
220 : expired by voting for the current slot. */
221 :
222 0 : ulong cnt = simulate_vote( tower, slot );
223 :
224 : /* Return early if our tower is not at least THRESHOLD_DEPTH deep
225 : after simulating. */
226 :
227 0 : if( FD_UNLIKELY( cnt < THRESHOLD_DEPTH ) ) return 1;
228 :
229 : /* Get the vote slot from THRESHOLD_DEPTH back. Note THRESHOLD_DEPTH
230 : is the 8th index back _including_ the simulated vote at index 0,
231 : which is not accounted for by `cnt`, so subtracting THRESHOLD_DEPTH
232 : will conveniently index the threshold vote. */
233 :
234 0 : ulong threshold_slot = fd_tower_votes_peek_index_const( tower, cnt - THRESHOLD_DEPTH )->slot;
235 :
236 : /* Track the amount of stake that has vote slot >= threshold_slot. */
237 :
238 0 : ulong threshold_stake = 0;
239 :
240 : /* Iterate all the vote accounts. */
241 :
242 0 : for (ulong i = 0; i < vote_cnt; i++ ) {
243 0 : fd_tower_t const * vote_tower = vote_towers[i];
244 :
245 : /* If this voter has not voted, continue. */
246 :
247 0 : if( FD_UNLIKELY( fd_tower_votes_empty( vote_tower ) ) ) continue;
248 :
249 0 : ulong cnt = simulate_vote( vote_tower, 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_const( vote_tower, 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 : fd_voter_t * epoch_voters = fd_epoch_voters( epoch );
272 0 : fd_voter_t * voter = fd_epoch_voters_query( epoch_voters, vote_keys[i], NULL );
273 0 : if( FD_UNLIKELY( !voter ) ) {
274 : /* This means that the cached list of epoch voters is not in sync with the list passed
275 : through from replay. This likely means that we have crossed an epoch boundary and the
276 : epoch_voter list has not been updated.
277 :
278 : TODO: update the set of account in epoch_voter's to match the list received from replay,
279 : so that epoch_voters is correct across epoch boundaries. */
280 0 : FD_LOG_CRIT(( "[%s] voter %s was not in epoch voters", __func__,
281 0 : FD_BASE58_ENC_32_ALLOCA(&vote_keys[i]) ));
282 0 : continue;
283 0 : }
284 0 : threshold_stake += voter->stake;
285 0 : }
286 0 : }
287 :
288 0 : double threshold_pct = (double)threshold_stake / (double)epoch->total_stake;
289 : # if LOGGING
290 : FD_LOG_NOTICE(( "[%s] ok? %d. top: %lu. threshold: %lu. stake: %.0lf%%.", __func__, threshold_pct > THRESHOLD_RATIO, fd_tower_votes_peek_tail_const( tower )->slot, threshold_slot, threshold_pct * 100.0 ));
291 : # endif
292 0 : return threshold_pct > THRESHOLD_RATIO;
293 0 : }
294 :
295 : ulong
296 : fd_tower_reset_slot( fd_tower_t const * tower,
297 : fd_epoch_t const * epoch,
298 0 : fd_ghost_t const * ghost ) {
299 :
300 0 : fd_tower_vote_t const * last = fd_tower_votes_peek_tail_const( tower );
301 0 : fd_ghost_ele_t const * vote = last ? fd_ghost_query_const( ghost, fd_ghost_hash( ghost, last->slot ) ) : NULL;
302 0 : fd_ghost_ele_t const * root = fd_ghost_root_const( ghost );
303 0 : fd_ghost_ele_t const * head = fd_ghost_head( ghost, root );
304 :
305 0 : # if FD_TOWER_PARANOID
306 0 : if( FD_UNLIKELY( !vote ) ) FD_LOG_CRIT(( "[%s] missing vote %lu", __func__, last->slot ));
307 0 : if( FD_UNLIKELY( !root ) ) FD_LOG_CRIT(( "[%s] missing root", __func__ ));
308 0 : if( FD_UNLIKELY( !head ) ) FD_LOG_CRIT(( "[%s] missing head", __func__ ));
309 0 : # endif
310 :
311 : /* Case 0: reset to the ghost head (ie. heaviest leaf slot of any
312 : fork) if any of the following is true:
313 :
314 : a. haven't voted
315 : b. last vote slot < ghost root slot
316 : c. ghost root is not an ancestor of last vote
317 :
318 : TODO can c. happen in non-exceptional conditions? error out? */
319 :
320 0 : if( FD_UNLIKELY( !vote || vote->slot < root->slot || !fd_ghost_is_ancestor( ghost, &root->key, &vote->key ) ) )
321 0 : return head->slot;
322 :
323 : /* Case 1: last vote on same fork as heaviest leaf (ie. last vote slot
324 : is an ancestor of heaviest leaf ). This is the common case. */
325 :
326 0 : else if( FD_LIKELY( fd_ghost_is_ancestor( ghost, &vote->key, &head->key ) ) )
327 0 : return head->slot;
328 :
329 : /* Case 2: last vote is on different fork from heaviest leaf (ie. last
330 : vote slot is _not_ an ancestor of heaviest leaf), but we have a
331 : valid switch proof for the heaviest leaf. */
332 :
333 0 : else if( FD_LIKELY( fd_tower_switch_check( tower, epoch, ghost, head->slot, &head->key ) ) )
334 0 : return head->slot;
335 :
336 : /* Case 3: same as case 2 except we don't have a valid switch proof,
337 : but we detect last vote is now on an "invalid" fork (ie. any
338 : ancestor of our last vote slot equivocates AND has not reached 52%
339 : of stake). If we do find such an ancestor, we reset to the heaviest
340 : leaf anyways, despite it being on a different fork and not having a
341 : valid switch proof. */
342 :
343 0 : else if( FD_LIKELY( fd_ghost_invalid( ghost, vote ) ) )
344 0 : return head->slot;
345 :
346 : /* Case 4: same as case 3 except last vote's fork is not invalid. In
347 : this case we reset to the heaviest leaf starting from the subtree
348 : rooted at our last vote slot, instead of the overall heaviest leaf.
349 : This is done to ensure votes propagate (see top-level documentation
350 : in fd_tower.h for details) */
351 :
352 0 : else
353 0 : return fd_ghost_head( ghost, vote )->slot;
354 0 : }
355 :
356 : ulong
357 : fd_tower_vote_slot( fd_tower_t const * tower,
358 : fd_epoch_t * epoch,
359 : fd_pubkey_t * vote_keys,
360 : fd_tower_t * const * vote_towers,
361 : ulong vote_cnt,
362 0 : fd_ghost_t const * ghost ) {
363 :
364 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
365 0 : fd_ghost_ele_t const * root = fd_ghost_root_const( ghost );
366 0 : fd_ghost_ele_t const * head = fd_ghost_head( ghost, root );
367 :
368 : /* Vote for the ghost head if any of the following is true:
369 :
370 : 1. haven't voted
371 : 2. last vote < ghost root
372 : 3. ghost root is not an ancestory of last vote
373 :
374 : FIXME need to ensure lockout safety for case 2 and 3 */
375 :
376 0 : if( FD_UNLIKELY( !vote || vote->slot < root->slot ) ) {
377 0 : return head->slot;
378 0 : }
379 0 : fd_hash_t const * vote_block_id = fd_ghost_hash( ghost, vote->slot );
380 0 : if( FD_UNLIKELY( !fd_ghost_is_ancestor( ghost, &root->key, vote_block_id ) ) ) {
381 0 : return head->slot;
382 0 : }
383 :
384 : /* Optimize for when there is just one fork or that we already
385 : previously voted for the best fork. */
386 :
387 0 : if( FD_LIKELY( fd_ghost_is_ancestor( ghost, vote_block_id, &head->key ) ) ) {
388 :
389 : /* The ghost head is on the same fork as our last vote slot, so we
390 : can vote fork it as long as we pass the threshold check. */
391 :
392 0 : if( FD_LIKELY( head->slot > vote->slot && fd_tower_threshold_check( tower, epoch, vote_keys, vote_towers, vote_cnt, head->slot ) ) ) {
393 0 : FD_LOG_DEBUG(( "[%s] success (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
394 0 : return head->slot;
395 0 : }
396 0 : FD_LOG_DEBUG(( "[%s] failure (threshold). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
397 0 : return FD_SLOT_NULL; /* can't vote. need to wait for threshold check. */
398 0 : }
399 :
400 : /* The ghost head is on a different fork from our last vote slot, so
401 : try to switch if we pass lockout and switch threshold. */
402 :
403 0 : if( FD_UNLIKELY( fd_tower_lockout_check( tower, ghost, head->slot, &head->key ) &&
404 0 : fd_tower_switch_check( tower, epoch, ghost, head->slot, &head->key ) ) ) {
405 0 : FD_LOG_DEBUG(( "[%s] success (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
406 0 : return head->slot;
407 0 : }
408 0 : FD_LOG_DEBUG(( "[%s] failure (lockout switch). best: %lu. vote: (slot: %lu conf: %lu)", __func__, head->slot, vote->slot, vote->conf ));
409 0 : return FD_SLOT_NULL;
410 0 : }
411 :
412 : ulong
413 0 : fd_tower_vote( fd_tower_t * tower, ulong slot ) {
414 0 : FD_LOG_DEBUG(( "[%s] voting for slot %lu", __func__, slot ));
415 :
416 0 : #if FD_TOWER_PARANOID
417 0 : fd_tower_vote_t const * vote = fd_tower_votes_peek_tail_const( tower );
418 0 : if( FD_UNLIKELY( vote && slot < vote->slot ) ) FD_LOG_ERR(( "[%s] slot %lu < vote->slot %lu", __func__, slot, vote->slot )); /* caller error*/
419 0 : #endif
420 :
421 : /* Use simulate_vote to determine how many expired votes to pop. */
422 :
423 0 : ulong cnt = simulate_vote( tower, slot );
424 :
425 : /* Pop everything that got expired. */
426 :
427 0 : while( FD_LIKELY( fd_tower_votes_cnt( tower ) > cnt ) ) {
428 0 : fd_tower_votes_pop_tail( tower );
429 0 : }
430 :
431 : /* If the tower is still full after expiring, then pop and return the
432 : bottom vote slot as the new root because this vote has incremented
433 : it to max lockout. Otherwise this is a no-op and there is no new
434 : root (FD_SLOT_NULL). */
435 :
436 0 : ulong root = FD_SLOT_NULL;
437 0 : if( FD_LIKELY( fd_tower_votes_full( tower ) ) ) { /* optimize for full tower */
438 0 : root = fd_tower_votes_pop_head( tower ).slot;
439 0 : }
440 :
441 : /* Increment confirmations (double lockouts) for consecutive
442 : confirmations in prior votes. */
443 :
444 0 : ulong prev_conf = 0;
445 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
446 0 : !fd_tower_votes_iter_done_rev( tower, iter );
447 0 : iter = fd_tower_votes_iter_prev ( tower, iter ) ) {
448 0 : fd_tower_vote_t * vote = fd_tower_votes_iter_ele( tower, iter );
449 0 : if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) break;
450 0 : vote->conf++;
451 0 : }
452 :
453 : /* Add the new vote to the tower. */
454 :
455 0 : fd_tower_votes_push_tail( tower, (fd_tower_vote_t){ .slot = slot, .conf = 1 } );
456 :
457 : /* Return the new root (FD_SLOT_NULL if there is none). */
458 :
459 0 : return root;
460 0 : }
461 :
462 : ulong
463 0 : fd_tower_simulate_vote( fd_tower_t const * tower, ulong slot ) {
464 0 : # if FD_TOWER_PARANOID
465 0 : FD_TEST( !fd_tower_votes_empty( tower ) ); /* caller error */
466 0 : # endif
467 :
468 0 : return simulate_vote( tower, slot );
469 0 : }
470 :
471 : static const uchar option_some = 1; /* this is a hack to lift the lifetime of a uchar outside fd_tower_sync_serde */
472 :
473 : fd_tower_sync_serde_t *
474 0 : fd_tower_to_tower_sync( fd_tower_t const * tower, ulong root, fd_hash_t * bank_hash, fd_hash_t * block_id, long ts, fd_tower_sync_serde_t * ser ) {
475 0 : ser->root = &root;
476 0 : ser->lockouts_cnt = (ushort)fd_tower_votes_cnt( tower );
477 0 : ushort i = 0;
478 0 : ulong prev = root;
479 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
480 0 : !fd_tower_votes_iter_done( tower, iter );
481 0 : iter = fd_tower_votes_iter_next( tower, iter ) ) {
482 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
483 0 : ser->lockouts[i].offset = vote->slot - prev;
484 0 : ser->lockouts[i].confirmation_count = (uchar const *)fd_type_pun_const( &vote->conf );
485 0 : i++;
486 0 : }
487 0 : ser->hash = bank_hash;
488 0 : ser->timestamp_option = &option_some;
489 0 : ser->timestamp = &ts;
490 0 : ser->block_id = block_id;
491 0 : return ser;
492 0 : }
493 :
494 : int
495 : fd_tower_checkpt( fd_tower_t const * tower,
496 : ulong root,
497 : fd_tower_sync_serde_t * last_vote,
498 : uchar const pubkey[static 32],
499 : fd_tower_sign_fn * sign_fn,
500 : int fd,
501 : uchar * buf,
502 0 : ulong buf_max ) {
503 :
504 : /* TODO check no invalid ptrs */
505 :
506 0 : fd_tower_serde_t ser = { 0 };
507 :
508 0 : uint kind = SERDE_KIND;
509 0 : ulong threshold_depth = THRESHOLD_DEPTH;
510 0 : double threshold_size = THRESHOLD_RATIO;
511 :
512 0 : ser.kind = &kind;
513 0 : ser.threshold_depth = &threshold_depth;
514 0 : ser.threshold_size = &threshold_size;
515 :
516 0 : fd_voter_v2_serde_t * voter_v2_ser = &ser.vote_state;
517 :
518 : /* Agave defaults all fields except the actual tower votes and root
519 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/tower_vote_state.rs#L118-L128 */
520 :
521 0 : fd_pubkey_t pubkey_null = { 0 };
522 0 : voter_v2_ser->node_pubkey = &pubkey_null;
523 0 : voter_v2_ser->authorized_withdrawer = &pubkey_null;
524 0 : uchar commission = 0;
525 0 : voter_v2_ser->commission = &commission;
526 :
527 0 : ulong votes_cnt = fd_tower_votes_cnt( tower );
528 0 : voter_v2_ser->votes_cnt = &votes_cnt;
529 :
530 0 : ulong i = 0;
531 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
532 0 : !fd_tower_votes_iter_done( tower, iter );
533 0 : iter = fd_tower_votes_iter_next( tower, iter ) ) {
534 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
535 0 : voter_v2_ser->votes[i].slot = &vote->slot;
536 0 : voter_v2_ser->votes[i].confirmation_count = (uint const *)fd_type_pun_const( &vote->conf );
537 0 : i++;
538 0 : }
539 :
540 0 : uchar root_slot_option = root == ULONG_MAX;
541 0 : voter_v2_ser->root_slot_option = &root_slot_option;
542 0 : voter_v2_ser->root_slot = root_slot_option ? NULL : &root;
543 :
544 0 : ulong authorized_voters_cnt = 0;
545 0 : voter_v2_ser->authorized_voters_cnt = &authorized_voters_cnt;
546 :
547 0 : ulong start_epoch = 0;
548 0 : ulong end_epoch = 0;
549 0 : for( ulong i = 0; i < 32; i++ ) {
550 0 : voter_v2_ser->prior_voters.buf[i].pubkey = &pubkey_null;
551 0 : voter_v2_ser->prior_voters.buf[i].start_epoch = &start_epoch;
552 0 : voter_v2_ser->prior_voters.buf[i].end_epoch = &end_epoch;
553 0 : }
554 0 : ulong idx = 31;
555 0 : voter_v2_ser->prior_voters.idx = &idx;
556 0 : uchar is_empty = 0;
557 0 : voter_v2_ser->prior_voters.is_empty = &is_empty;
558 :
559 0 : ulong epoch_credits_cnt = 0;
560 0 : voter_v2_ser->epoch_credits_cnt = &epoch_credits_cnt;
561 :
562 0 : ulong slot = 0;
563 0 : long timestamp = 0;
564 0 : voter_v2_ser->last_timestamp.slot = &slot;
565 0 : voter_v2_ser->last_timestamp.timestamp = ×tamp;
566 :
567 : /* Copy the last vote (reused from the actual ) into the Tower */
568 :
569 0 : uint last_vote_kind = SERDE_LAST_VOTE_KIND;
570 0 : ser.last_vote_kind = &last_vote_kind;
571 0 : ser.last_vote = *last_vote;
572 :
573 0 : ulong last_timestamp_slot = fd_tower_votes_peek_tail_const( tower )->slot;
574 0 : long last_timestamp_timestamp = fd_log_wallclock() / (long)1e9;
575 0 : ser.last_timestamp.slot = &last_timestamp_slot;
576 0 : ser.last_timestamp.timestamp = &last_timestamp_timestamp;
577 :
578 0 : int err;
579 :
580 0 : ulong buf_sz; err = fd_tower_serialize( &ser, buf, buf_max, &buf_sz );
581 0 : if( FD_UNLIKELY( err ) ) { FD_LOG_WARNING(( "fd_tower_serialize failed" )); return -1; }
582 :
583 0 : ulong off = sizeof(uint) /* kind */ + FD_ED25519_SIG_SZ /* signature */ + sizeof(ulong) /* data_sz */;
584 0 : uchar * sig = buf + sizeof(uint);
585 0 : uchar * msg = buf + off;
586 0 : ulong msg_sz = buf_sz - off;
587 :
588 0 : sign_fn( pubkey, sig, msg, msg_sz );
589 :
590 0 : ser.signature = (fd_ed25519_sig_t const *)fd_type_pun_const( &buf );
591 0 : ser.data_sz = &msg_sz;
592 :
593 0 : ulong wsz; err = fd_io_write( fd, buf, buf_sz, buf_sz, &wsz );
594 0 : if( FD_UNLIKELY( err ) ) { FD_LOG_WARNING(( "fd_io_write failed: %s", strerror( err ) )); return -1; }
595 :
596 0 : fsync( fd );
597 :
598 0 : return 0;
599 0 : }
600 :
601 : int
602 : fd_tower_restore( fd_tower_t * tower,
603 : ulong * root,
604 : long * ts,
605 : uchar const pubkey[static 32],
606 : int fd,
607 : uchar * buf,
608 : ulong buf_max,
609 0 : ulong * buf_sz ) {
610 0 : int err = fd_io_sz( fd, buf_sz );
611 0 : if( FD_UNLIKELY( err ) ) { FD_LOG_WARNING(( "%s: %s", __func__, fd_io_strerror( err ) )); return -1; }
612 0 : if( FD_UNLIKELY( buf_max<*buf_sz ) ) { FD_LOG_WARNING(( "%s: buf_max %lu < buf_sz %lu", __func__, buf_max, *buf_sz )); return -1; }
613 :
614 0 : ulong rsz; err = fd_io_read( fd, buf, *buf_sz, *buf_sz, &rsz );
615 0 : if( FD_UNLIKELY( err<0 ) ) { FD_LOG_WARNING(( "%s: unexpected EOF", __func__ )); return -1; }
616 0 : if( FD_UNLIKELY( *buf_sz!=rsz ) ) { FD_LOG_WARNING(( "%s: read %lu bytes, expected %lu", __func__, rsz, *buf_sz )); return -1; }
617 0 : if( FD_UNLIKELY( err>0 ) ) { FD_LOG_WARNING(( "%s: %s", __func__, fd_io_strerror( err ) )); return -1; }
618 :
619 0 : fd_tower_serde_t de = { 0 };
620 0 : fd_tower_deserialize( buf, *buf_sz, &de );
621 :
622 0 : uchar * msg = (uchar *)de.node_pubkey; /* signed data region begins at this field */
623 0 : ulong msg_sz = *de.data_sz;
624 0 : uchar const * sig = *de.signature;
625 0 : fd_sha512_t sha[1];
626 0 : err = fd_ed25519_verify( msg, msg_sz, sig, pubkey, sha );
627 0 : if( FD_UNLIKELY( err!=FD_ED25519_SUCCESS ) ) { FD_LOG_WARNING(( "serialized tower failed sigverify: %s", fd_ed25519_strerror( err ) )); return -1; }
628 0 : if( FD_UNLIKELY( 0!=memcmp( de.node_pubkey->uc, pubkey, 32 ) ) ) { FD_LOG_WARNING(( "node_pubkey does not match pubkey" )); return -1; }
629 0 : if( FD_UNLIKELY( *de.kind!=SERDE_KIND ) ) { FD_LOG_WARNING(( "serialized tower generated by too old agave version (required >= 2.3.7)" )); return -1; }
630 0 : if( FD_UNLIKELY( *de.threshold_depth!=THRESHOLD_DEPTH ) ) { FD_LOG_WARNING(( "threshold_depth does not match THRESHOLD_DEPTH" )); return -1; }
631 0 : if( FD_UNLIKELY( *de.threshold_size !=THRESHOLD_RATIO ) ) { FD_LOG_WARNING(( "threshold_size does not match THRESHOLD_RATIO" )); return -1; }
632 0 : if( FD_UNLIKELY( *de.vote_state.votes_cnt > 31 ) ) { FD_LOG_WARNING(( "invalid votes_cnt %lu > 31", *de.vote_state.votes_cnt )); return -1; }
633 0 : if( FD_UNLIKELY( *de.vote_state.authorized_voters_cnt > 31 ) ) { FD_LOG_WARNING(( "invalid authorized_voters_cnt %lu > 31", *de.vote_state.authorized_voters_cnt )); return -1; }
634 0 : if( FD_UNLIKELY( de.last_vote.lockouts_cnt > 31 ) ) { FD_LOG_WARNING(( "invalid lockouts_cnt %u > 31", de.last_vote.lockouts_cnt )); return -1; }
635 :
636 0 : for( ulong i = 0; i < *de.vote_state.votes_cnt; i++ ) {
637 0 : fd_tower_votes_push_tail( tower, (fd_tower_vote_t){ .slot = *de.vote_state.votes[i].slot, .conf = *de.vote_state.votes[i].confirmation_count } );
638 0 : }
639 0 : *root = *de.vote_state.root_slot_option ? *de.vote_state.root_slot : ULONG_MAX;
640 0 : *ts = *de.last_timestamp.timestamp;
641 :
642 0 : return 0;
643 0 : }
644 :
645 : static ulong
646 3 : ser_short_vec_cnt( uchar * dst, ushort src ) {
647 3 : if ( FD_LIKELY( src < 0x80UL ) ) { *dst = (uchar) src; return 1; }
648 0 : else if( FD_LIKELY( src < 0x4000UL ) ) { *dst++ = (uchar)((src&0x7FUL)|0x80UL); *dst++ = (uchar)( src>>7); return 2; }
649 0 : else { *dst++ = (uchar)((src&0x7FUL)|0x80UL); *dst++ = (uchar)(((src>>7)&0x7FUL)|0x80UL); *dst++ = (uchar)(src>>14UL); return 3; }
650 3 : }
651 :
652 : static ulong
653 93 : ser_varint( uchar * dst, ulong src ) {
654 93 : ulong off = 0;
655 93 : while( FD_LIKELY( 1 ) ) {
656 93 : if( FD_LIKELY( src < 0x80UL ) ) {
657 93 : *(dst) = (uchar)src;
658 93 : off += 1;
659 93 : return off;
660 93 : }
661 0 : *(dst+off) = (uchar)((src&0x7FUL)|0x80UL);
662 0 : off += 1;
663 0 : src >>= 7;
664 0 : }
665 93 : }
666 :
667 : int
668 : fd_tower_serialize( fd_tower_serde_t * ser,
669 : uchar * buf,
670 : ulong buf_max,
671 3 : ulong * buf_sz ) {
672 :
673 3 : if( FD_UNLIKELY( *ser->threshold_depth!=THRESHOLD_DEPTH ) ) { FD_LOG_WARNING(( "threshold_depth does not match THRESHOLD_DEPTH" )); return -1; }
674 3 : if( FD_UNLIKELY( *ser->threshold_size !=THRESHOLD_RATIO ) ) { FD_LOG_WARNING(( "threshold_size does not match THRESHOLD_RATIO" )); return -1; }
675 3 : if( FD_UNLIKELY( *ser->vote_state.votes_cnt > 31 ) ) { FD_LOG_WARNING(( "invalid votes_cnt %lu > 31", *ser->vote_state.votes_cnt )); return -1; }
676 3 : if( FD_UNLIKELY( *ser->vote_state.authorized_voters_cnt > 31 ) ) { FD_LOG_WARNING(( "invalid authorized_voters_cnt %lu > 31", *ser->vote_state.authorized_voters_cnt )); return -1; }
677 3 : if( FD_UNLIKELY( ser->last_vote.lockouts_cnt > 31 ) ) { FD_LOG_WARNING(( "invalid lockouts_cnt %u > 31", ser->last_vote.lockouts_cnt )); return -1; }
678 :
679 639 : #define SER( T, name ) do { \
680 639 : if( FD_UNLIKELY( off+sizeof(T)>buf_max ) ) { \
681 0 : FD_LOG_WARNING(( "ser %s: overflow (off %lu > buf_max: %lu)", #name, off, buf_max )); \
682 0 : return -1; \
683 0 : } \
684 639 : if( FD_LIKELY( ser->name ) ) { \
685 639 : FD_STORE( T, buf+off, *ser->name ); \
686 639 : ser->name = (T const *)fd_type_pun_const( buf+off ); \
687 639 : } \
688 639 : off += sizeof(T); \
689 639 : } while(0)
690 :
691 6 : #define OFF( T, name ) do { \
692 6 : if( FD_UNLIKELY( off+sizeof(T)>buf_max ) ) { \
693 0 : FD_LOG_WARNING(( "ser %s: overflow (off %lu > buf_max: %lu)", #name, off, buf_max )); \
694 0 : return -1; \
695 0 : } \
696 6 : ser->name = (T const *)fd_type_pun_const( buf+off ); \
697 6 : off += sizeof(T); \
698 6 : } while(0)
699 :
700 3 : ulong off = 0;
701 :
702 : /* SavedTower::Current */
703 :
704 3 : SER( uint, kind );
705 3 : OFF( fd_ed25519_sig_t, signature );
706 3 : OFF( ulong, data_sz );
707 3 : SER( fd_pubkey_t, node_pubkey );
708 3 : SER( ulong, threshold_depth );
709 3 : SER( double, threshold_size );
710 :
711 : /* VoteState1_14_11 */
712 :
713 3 : SER( fd_pubkey_t, vote_state.node_pubkey );
714 3 : SER( fd_pubkey_t, vote_state.authorized_withdrawer );
715 3 : SER( uchar, vote_state.commission );
716 3 : SER( ulong, vote_state.votes_cnt );
717 96 : for( ulong i=0; i < fd_ulong_min( *ser->vote_state.votes_cnt, 31 ); i++ ) {
718 93 : SER( ulong, vote_state.votes[i].slot );
719 93 : SER( uint, vote_state.votes[i].confirmation_count );
720 93 : }
721 3 : SER( uchar, vote_state.root_slot_option );
722 3 : if( FD_LIKELY( *ser->vote_state.root_slot_option ) ) {
723 3 : SER( ulong, vote_state.root_slot );
724 3 : }
725 3 : SER( ulong, vote_state.authorized_voters_cnt );
726 3 : for( ulong i = 0; i < fd_ulong_min( *ser->vote_state.authorized_voters_cnt, 32 ); i++ ) {
727 0 : SER( ulong, vote_state.authorized_voters[i].epoch );
728 0 : SER( fd_pubkey_t, vote_state.authorized_voters[i].pubkey );
729 0 : }
730 99 : for( ulong i = 0; i < 32; i++ ) {
731 96 : SER( fd_pubkey_t, vote_state.prior_voters.buf[i].pubkey );
732 96 : SER( ulong, vote_state.prior_voters.buf[i].start_epoch );
733 96 : SER( ulong, vote_state.prior_voters.buf[i].end_epoch );
734 96 : }
735 3 : SER( ulong, vote_state.prior_voters.idx );
736 3 : SER( uchar, vote_state.prior_voters.is_empty );
737 3 : SER( ulong, vote_state.epoch_credits_cnt );
738 3 : for( ulong i = 0; i < fd_ulong_min( *ser->vote_state.epoch_credits_cnt, 32 ); i++ ) {
739 0 : SER( ulong, vote_state.epoch_credits[i].epoch );
740 0 : SER( ulong, vote_state.epoch_credits[i].credits );
741 0 : SER( ulong, vote_state.epoch_credits[i].prev_credits );
742 0 : }
743 3 : SER( ulong, vote_state.last_timestamp.slot );
744 3 : SER( long, vote_state.last_timestamp.timestamp );
745 :
746 : /* VoteTransaction::TowerSync */
747 :
748 3 : SER( uint, last_vote_kind );
749 3 : SER( ulong, last_vote.root );
750 3 : off += ser_short_vec_cnt( buf+off, ser->last_vote.lockouts_cnt );
751 96 : for( ulong i = 0; i < fd_ulong_min( ser->last_vote.lockouts_cnt, 31 ); i++ ) {
752 93 : off += ser_varint( buf+off, ser->last_vote.lockouts[i].offset );
753 93 : SER( uchar, last_vote.lockouts[i].confirmation_count );
754 93 : }
755 3 : SER( fd_hash_t, last_vote.hash );
756 3 : SER( uchar, last_vote.timestamp_option );
757 3 : if( FD_LIKELY( *ser->last_vote.timestamp_option ) ) {
758 3 : SER( long, last_vote.timestamp );
759 3 : }
760 3 : SER( fd_hash_t, last_vote.block_id );
761 :
762 : /* BlockTimestamp */
763 :
764 3 : SER( ulong, last_timestamp.slot );
765 3 : SER( long, last_timestamp.timestamp );
766 :
767 3 : #undef SER
768 3 : #undef OFF
769 :
770 3 : *buf_sz = off;
771 :
772 3 : return 0;
773 3 : }
774 :
775 : static ulong
776 3 : de_short_vec_cnt( ushort * dst, uchar * src ) {
777 3 : if ( FD_LIKELY( !(0x80U & src[0]) ) ) { *dst = (ushort)src[0]; return 1; }
778 0 : else if( FD_LIKELY( !(0x80U & src[1]) ) ) { *dst = (ushort)((ulong)(src[0]&0x7FUL) + (((ulong)src[1])<<7)); return 2; }
779 0 : else { *dst = (ushort)((ulong)(src[0]&0x7FUL) + (((ulong)(src[1]&0x7FUL))<<7) + (((ulong)src[2])<<14)); return 3; }
780 3 : }
781 :
782 : static ulong
783 93 : de_varint( ulong * dst, uchar * src ) {
784 93 : *dst = 0;
785 93 : ulong off = 0;
786 93 : ulong bit = 0;
787 93 : while( FD_LIKELY( bit < 64 ) ) {
788 93 : uchar byte = *(uchar const *)(src+off);
789 93 : off += 1;
790 93 : *dst |= (byte & 0x7FUL) << bit;
791 93 : if( FD_LIKELY( (byte & 0x80U) == 0U ) ) {
792 93 : if( FD_UNLIKELY( (*dst>>bit) != byte ) ) FD_LOG_CRIT(( "de_varint" ));
793 93 : if( FD_UNLIKELY( byte==0U && (bit!=0U || *dst!=0UL) ) ) FD_LOG_CRIT(( "de_varint" ));
794 93 : return off;
795 93 : }
796 0 : bit += 7;
797 0 : }
798 0 : FD_LOG_CRIT(( "de_varint" ));
799 0 : }
800 :
801 : int
802 : fd_tower_deserialize( uchar * buf,
803 : ulong buf_sz,
804 3 : fd_tower_serde_t * de ) {
805 :
806 645 : #define DE( T, name ) do { \
807 645 : if( FD_UNLIKELY( off+sizeof(T)>buf_sz ) ) { \
808 0 : FD_LOG_WARNING(( "de %s: overflow (off %lu > buf_sz: %lu)", #name, off, buf_sz )); \
809 0 : return -1; \
810 0 : } \
811 645 : de->name = (T const *)fd_type_pun_const( buf+off ); \
812 645 : off += sizeof(T); \
813 645 : } while(0)
814 :
815 3 : ulong off = 0;
816 :
817 : /* SavedTower::Current */
818 :
819 3 : DE( uint, kind );
820 3 : DE( fd_ed25519_sig_t, signature );
821 3 : DE( ulong, data_sz );
822 3 : DE( fd_pubkey_t, node_pubkey );
823 3 : DE( ulong, threshold_depth );
824 3 : DE( double, threshold_size );
825 :
826 : /* VoteState1_14_11 */
827 :
828 3 : DE( fd_pubkey_t, vote_state.node_pubkey );
829 3 : DE( fd_pubkey_t, vote_state.authorized_withdrawer );
830 3 : DE( uchar, vote_state.commission );
831 3 : DE( ulong, vote_state.votes_cnt );
832 96 : for( ulong i=0; i < fd_ulong_min( *de->vote_state.votes_cnt, 31 ); i++ ) {
833 93 : DE( ulong, vote_state.votes[i].slot );
834 93 : DE( uint, vote_state.votes[i].confirmation_count );
835 93 : }
836 3 : DE( uchar, vote_state.root_slot_option );
837 3 : if( FD_LIKELY( *de->vote_state.root_slot_option ) ) {
838 3 : DE( ulong, vote_state.root_slot );
839 3 : }
840 3 : DE( ulong, vote_state.authorized_voters_cnt );
841 3 : for( ulong i = 0; i < fd_ulong_min( *de->vote_state.authorized_voters_cnt, 32 ); i++ ) {
842 0 : DE( ulong, vote_state.authorized_voters[i].epoch );
843 0 : DE( fd_pubkey_t, vote_state.authorized_voters[i].pubkey );
844 0 : }
845 99 : for( ulong i = 0; i < 32; i++ ) {
846 96 : DE( fd_pubkey_t, vote_state.prior_voters.buf[i].pubkey );
847 96 : DE( ulong, vote_state.prior_voters.buf[i].start_epoch );
848 96 : DE( ulong, vote_state.prior_voters.buf[i].end_epoch );
849 96 : }
850 3 : DE( ulong, vote_state.prior_voters.idx );
851 3 : DE( uchar, vote_state.prior_voters.is_empty );
852 3 : DE( ulong, vote_state.epoch_credits_cnt );
853 3 : for( ulong i = 0; i < fd_ulong_min( *de->vote_state.epoch_credits_cnt, 32 ); i++ ) {
854 0 : DE( ulong, vote_state.epoch_credits[i].epoch );
855 0 : DE( ulong, vote_state.epoch_credits[i].credits );
856 0 : DE( ulong, vote_state.epoch_credits[i].prev_credits );
857 0 : }
858 3 : DE( ulong, vote_state.last_timestamp.slot );
859 3 : DE( long, vote_state.last_timestamp.timestamp );
860 :
861 : /* VoteTransaction::TowerSync */
862 :
863 3 : DE( uint, last_vote_kind );
864 3 : DE( ulong, last_vote.root );
865 3 : off += de_short_vec_cnt( &de->last_vote.lockouts_cnt, buf+off );
866 96 : for( ulong i = 0; i < fd_ulong_min( de->last_vote.lockouts_cnt, 31 ); i++ ) {
867 93 : off += de_varint( &de->last_vote.lockouts[i].offset, buf+off );
868 93 : DE( uchar, last_vote.lockouts[i].confirmation_count );
869 93 : }
870 3 : DE( fd_hash_t, last_vote.hash );
871 3 : DE( uchar, last_vote.timestamp_option );
872 3 : if( FD_LIKELY( *de->last_vote.timestamp_option ) ) {
873 3 : DE( long, last_vote.timestamp );
874 3 : }
875 3 : DE( fd_hash_t, last_vote.block_id );
876 :
877 : /* BlockTimestamp */
878 :
879 3 : DE( ulong, last_timestamp.slot );
880 3 : DE( long, last_timestamp.timestamp );
881 :
882 3 : #undef DE
883 :
884 3 : return 0;
885 3 : }
886 :
887 : void
888 : fd_tower_to_vote_txn( fd_tower_t const * tower,
889 : ulong root,
890 : fd_lockout_offset_t * lockouts_scratch,
891 : fd_hash_t const * bank_hash,
892 : fd_hash_t const * recent_blockhash,
893 : fd_pubkey_t const * validator_identity,
894 : fd_pubkey_t const * vote_authority,
895 : fd_pubkey_t const * vote_acc,
896 0 : fd_txn_p_t * vote_txn ) {
897 :
898 0 : fd_compact_vote_state_update_t tower_sync;
899 0 : tower_sync.root = fd_ulong_if( root == ULONG_MAX, 0UL, root );
900 0 : tower_sync.lockouts_len = (ushort)fd_tower_votes_cnt( tower );
901 0 : tower_sync.lockouts = lockouts_scratch;
902 0 : tower_sync.timestamp = fd_log_wallclock() / (long)1e9; /* seconds */
903 0 : tower_sync.has_timestamp = 1;
904 :
905 0 : ulong prev = tower_sync.root;
906 0 : ulong i = 0UL;
907 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
908 0 : !fd_tower_votes_iter_done( tower, iter );
909 0 : iter = fd_tower_votes_iter_next( tower, iter ) ) {
910 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
911 0 : tower_sync.lockouts[i].offset = vote->slot - prev;
912 0 : tower_sync.lockouts[i].confirmation_count = (uchar)vote->conf;
913 0 : prev = vote->slot;
914 0 : i++;
915 0 : }
916 0 : memcpy( tower_sync.hash.uc, bank_hash, sizeof(fd_hash_t) );
917 :
918 0 : uchar * txn_out = vote_txn->payload;
919 0 : uchar * txn_meta_out = vote_txn->_;
920 :
921 0 : int same_addr = !memcmp( validator_identity, vote_authority, sizeof(fd_pubkey_t) );
922 0 : if( FD_LIKELY( same_addr ) ) {
923 :
924 : /* 0: validator identity
925 : 1: vote account address
926 : 2: vote program */
927 :
928 0 : fd_txn_accounts_t accts;
929 0 : accts.signature_cnt = 1;
930 0 : accts.readonly_signed_cnt = 0;
931 0 : accts.readonly_unsigned_cnt = 1;
932 0 : accts.acct_cnt = 3;
933 0 : accts.signers_w = validator_identity;
934 0 : accts.signers_r = NULL;
935 0 : accts.non_signers_w = vote_acc;
936 0 : accts.non_signers_r = &fd_solana_vote_program_id;
937 0 : FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, accts.signature_cnt, &accts, recent_blockhash->uc ) );
938 0 : } else {
939 :
940 : /* 0: validator identity
941 : 1: vote authority
942 : 2: vote account address
943 : 3: vote program */
944 :
945 0 : fd_txn_accounts_t accts;
946 0 : accts.signature_cnt = 2;
947 0 : accts.readonly_signed_cnt = 1;
948 0 : accts.readonly_unsigned_cnt = 1;
949 0 : accts.acct_cnt = 4;
950 0 : accts.signers_w = validator_identity;
951 0 : accts.signers_r = vote_authority;
952 0 : accts.non_signers_w = vote_acc;
953 0 : accts.non_signers_r = &fd_solana_vote_program_id;
954 0 : FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, accts.signature_cnt, &accts, recent_blockhash->uc ) );
955 0 : }
956 :
957 : /* Add the vote instruction to the transaction. */
958 :
959 0 : fd_vote_instruction_t vote_ix;
960 0 : uchar vote_ix_buf[FD_TXN_MTU];
961 0 : vote_ix.discriminant = fd_vote_instruction_enum_compact_update_vote_state;
962 0 : vote_ix.inner.compact_update_vote_state = tower_sync;
963 0 : fd_bincode_encode_ctx_t encode = { .data = vote_ix_buf, .dataend = ( vote_ix_buf + FD_TXN_MTU ) };
964 0 : fd_vote_instruction_encode( &vote_ix, &encode );
965 0 : uchar program_id;
966 0 : uchar ix_accs[2];
967 0 : if( FD_LIKELY( same_addr ) ) {
968 0 : ix_accs[0] = 1; /* vote account address */
969 0 : ix_accs[1] = 0; /* vote authority */
970 0 : program_id = 2; /* vote program */
971 0 : } else {
972 0 : ix_accs[0] = 2; /* vote account address */
973 0 : ix_accs[1] = 1; /* vote authority */
974 0 : program_id = 3; /* vote program */
975 0 : }
976 0 : ushort vote_ix_sz = (ushort)fd_vote_instruction_size( &vote_ix );
977 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 );
978 0 : }
979 :
980 : int
981 0 : fd_tower_verify( fd_tower_t const * tower ) {
982 0 : fd_tower_vote_t const * prev = NULL;
983 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init( tower );
984 0 : !fd_tower_votes_iter_done( tower, iter );
985 0 : iter = fd_tower_votes_iter_next( tower, iter ) ) {
986 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
987 0 : if( FD_LIKELY( prev && !( vote->slot < prev->slot && vote->conf < prev->conf ) ) ) {
988 0 : FD_LOG_WARNING(( "[%s] invariant violation: vote %lu %lu. prev %lu %lu", __func__, vote->slot, vote->conf, prev->slot, prev->conf ));
989 0 : return -1;
990 0 : }
991 0 : prev = vote;
992 0 : }
993 0 : return 0;
994 0 : }
995 :
996 : #include <stdio.h>
997 :
998 : void
999 0 : fd_tower_print( fd_tower_t const * tower, ulong root ) {
1000 0 : FD_LOG_NOTICE( ( "\n\n[Tower]" ) );
1001 0 : ulong max_slot = 0;
1002 :
1003 : /* Determine spacing. */
1004 :
1005 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
1006 0 : !fd_tower_votes_iter_done_rev( tower, iter );
1007 0 : iter = fd_tower_votes_iter_prev ( tower, iter ) ) {
1008 :
1009 0 : max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
1010 0 : }
1011 :
1012 : /* Calculate the number of digits in the maximum slot value. */
1013 :
1014 0 : int digit_cnt = 0;
1015 0 : unsigned long rem = max_slot;
1016 0 : do {
1017 0 : rem /= 10;
1018 0 : ++digit_cnt;
1019 0 : } while( rem > 0 );
1020 :
1021 : /* Print the table header */
1022 :
1023 0 : printf( "slot%*s | %s\n", digit_cnt - (int)strlen("slot"), "", "confirmation count" );
1024 :
1025 : /* Print the divider line */
1026 :
1027 0 : for( int i = 0; i < digit_cnt; i++ ) {
1028 0 : printf( "-" );
1029 0 : }
1030 0 : printf( " | " );
1031 0 : for( ulong i = 0; i < strlen( "confirmation count" ); i++ ) {
1032 0 : printf( "-" );
1033 0 : }
1034 0 : printf( "\n" );
1035 :
1036 : /* Print each record in the table */
1037 :
1038 0 : for( fd_tower_votes_iter_t iter = fd_tower_votes_iter_init_rev( tower );
1039 0 : !fd_tower_votes_iter_done_rev( tower, iter );
1040 0 : iter = fd_tower_votes_iter_prev ( tower, iter ) ) {
1041 :
1042 0 : fd_tower_vote_t const * vote = fd_tower_votes_iter_ele_const( tower, iter );
1043 0 : printf( "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf );
1044 0 : max_slot = fd_ulong_max( max_slot, fd_tower_votes_iter_ele_const( tower, iter )->slot );
1045 0 : }
1046 0 : printf( "%*lu | root\n", digit_cnt, root );
1047 0 : printf( "\n" );
1048 0 : }
1049 :
1050 : void
1051 : fd_tower_from_vote_acc_data( uchar const * data,
1052 0 : fd_tower_t * tower_out ) {
1053 0 : # if FD_TOWER_PARANOID
1054 0 : FD_TEST( fd_tower_votes_empty( tower_out ) );
1055 0 : # endif
1056 :
1057 0 : fd_voter_state_t const * state = (fd_voter_state_t const *)fd_type_pun_const( data );
1058 :
1059 : /* Push all the votes onto the tower. */
1060 0 : for( ulong i = 0; i < fd_voter_state_cnt( state ); i++ ) {
1061 0 : fd_tower_vote_t vote = { 0 };
1062 0 : if( FD_UNLIKELY( state->kind == fd_vote_state_versioned_enum_v0_23_5 ) ) {
1063 0 : vote.slot = state->v0_23_5.votes[i].slot;
1064 0 : vote.conf = state->v0_23_5.votes[i].conf;
1065 0 : } else if( FD_UNLIKELY( state->kind == fd_vote_state_versioned_enum_v1_14_11 ) ) {
1066 0 : vote.slot = state->v1_14_11.votes[i].slot;
1067 0 : vote.conf = state->v1_14_11.votes[i].conf;
1068 0 : } else if ( FD_UNLIKELY( state->kind == fd_vote_state_versioned_enum_current ) ) {
1069 0 : vote.slot = state->votes[i].slot;
1070 0 : vote.conf = state->votes[i].conf;
1071 0 : } else {
1072 0 : FD_LOG_CRIT(( "[%s] unknown vote state version. discriminant %u", __func__, state->kind ));
1073 0 : }
1074 0 : fd_tower_votes_push_tail( tower_out, vote );
1075 0 : }
1076 0 : }
|