Line data Source code
1 : #include <stdio.h>
2 : #include <string.h>
3 :
4 : #include "fd_tower.h"
5 : #include "../../flamenco/txn/fd_txn_generate.h"
6 : #include "../../flamenco/runtime/fd_system_ids.h"
7 : #include "../../flamenco/runtime/program/vote/fd_vote_state_versioned.h"
8 :
9 : /* Pool and map_chain for fd_tower_blk_t. */
10 :
11 : #define POOL_NAME blk_pool
12 0 : #define POOL_T fd_tower_blk_t
13 : #define POOL_IDX_T uint
14 : #include "../../util/tmpl/fd_pool.c"
15 :
16 : #define MAP_NAME blk_map
17 0 : #define MAP_ELE_T fd_tower_blk_t
18 : #define MAP_KEY_T ulong
19 0 : #define MAP_KEY slot
20 0 : #define MAP_PREV prev
21 0 : #define MAP_NEXT next
22 0 : #define MAP_IDX_T uint
23 0 : #define MAP_KEY_EQ(k0,k1) (*(k0)==*(k1))
24 0 : #define MAP_KEY_HASH(key,seed) ((*(key))^(seed))
25 : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
26 : #include "../../util/tmpl/fd_map_chain.c"
27 :
28 : /* lockout_interval tracks a map of lockout intervals.
29 :
30 : We need to track a list of lockout intervals per validator per slot.
31 : Intervals are inclusive. Example:
32 :
33 : After executing slot 33, validator A votes for slot 32, has a tower
34 :
35 : vote | confirmation count | lockout interval
36 : ----- | -------------------|------------------
37 : 32 | 1 | [32, 33]
38 : 2 | 3 | [2, 6]
39 : 1 | 4 | [1, 9]
40 :
41 : The lockout interval is the interval of slots that the validator is
42 : locked out from voting for if they want to switch off that vote. For
43 : example if validator A wants to switch off fork 1, they have to wait
44 : until slot 9.
45 :
46 : Agave tracks a similar structure.
47 :
48 : key: for an interval [vote, vote+lockout] for validator A,
49 : it is stored like:
50 : vote+lockout -> (vote, validator A) -> (2, validator B) -> (any other vote, any other validator)
51 :
52 : Since a validator can have up to 31 entries in the tower, and we have
53 : a max_vote_accounts, we can pool the interval objects to be
54 : 31*max_vote_accounts entries PER bank / executed slot. A compact
55 : per-bank list of unique interval ends lets switch checks and pruning
56 : enumerate the corresponding map keys. Keys use the per-bank slot
57 : header pool index instead of repeating the full fork slot. */
58 :
59 : struct lockout_interval_key {
60 : uint interval_end;
61 : uint slot_conf_pubkey;
62 : };
63 : typedef struct lockout_interval_key lockout_interval_key_t;
64 :
65 : struct lockout_interval {
66 : lockout_interval_key_t key;
67 : uint next; /* reserved for fd_map_chain and fd_pool */
68 : uint end_next; /* next unique-end interval for this fork slot */
69 : };
70 : typedef struct lockout_interval lockout_interval_t;
71 :
72 : FD_STATIC_ASSERT( sizeof(lockout_interval_key_t)==8UL, lockout_interval_key );
73 : FD_STATIC_ASSERT( sizeof(lockout_interval_t )==16UL, lockout_interval );
74 :
75 0 : #define LOCKOUT_INTERVAL_SLOT_IDX_BITS (13U)
76 0 : #define LOCKOUT_INTERVAL_CONF_BITS (5U)
77 : #define LOCKOUT_INTERVAL_PUBKEY_IDX_BITS (14U)
78 0 : #define LOCKOUT_INTERVAL_SLOT_IDX_MASK ((1U<<LOCKOUT_INTERVAL_SLOT_IDX_BITS)-1U)
79 0 : #define LOCKOUT_INTERVAL_CONF_MASK ((1U<<LOCKOUT_INTERVAL_CONF_BITS)-1U)
80 0 : #define LOCKOUT_INTERVAL_CONF_SHIFT (LOCKOUT_INTERVAL_SLOT_IDX_BITS)
81 0 : #define LOCKOUT_INTERVAL_PUBKEY_IDX_SHIFT (LOCKOUT_INTERVAL_CONF_SHIFT+LOCKOUT_INTERVAL_CONF_BITS)
82 :
83 : FD_STATIC_ASSERT( LOCKOUT_INTERVAL_SLOT_IDX_BITS+
84 : LOCKOUT_INTERVAL_CONF_BITS+
85 : LOCKOUT_INTERVAL_PUBKEY_IDX_BITS==32U, lockout_interval_encoding );
86 :
87 : FD_FN_PURE static inline uint
88 0 : lockout_interval_key_slot_idx( lockout_interval_key_t const * key ) {
89 0 : return key->slot_conf_pubkey & LOCKOUT_INTERVAL_SLOT_IDX_MASK;
90 0 : }
91 :
92 : #define MAP_NAME lockout_interval_map
93 0 : #define MAP_ELE_T lockout_interval_t
94 : #define MAP_KEY_T lockout_interval_key_t
95 0 : #define MAP_KEY_EQ(k0,k1) ((lockout_interval_key_slot_idx( (k0) )==lockout_interval_key_slot_idx( (k1) )) & ((k0)->interval_end==(k1)->interval_end))
96 0 : #define MAP_KEY_HASH(key,seed) (fd_ulong_hash( ((((ulong)lockout_interval_key_slot_idx( (key) ))<<32) | (ulong)(key)->interval_end) ^ (seed) ))
97 : #define MAP_MULTI 1
98 0 : #define MAP_KEY key
99 0 : #define MAP_NEXT next
100 0 : #define MAP_IDX_T uint
101 : #include "../../util/tmpl/fd_map_chain.c"
102 :
103 : #define POOL_NAME lockout_interval_pool
104 0 : #define POOL_T lockout_interval_t
105 0 : #define POOL_NEXT next
106 : #define POOL_IDX_T uint
107 : #define POOL_LAZY 1
108 : #include "../../util/tmpl/fd_pool.c"
109 :
110 : /* lockout_slot owns the list of unique interval ends for one fork slot. */
111 :
112 : struct lockout_slot {
113 : uint slot; /* fork slot */
114 : uint next; /* map/pool pointer for other lockout_slot_t */
115 : uint end_head; /* start of unique interval ends linked list */
116 : };
117 : typedef struct lockout_slot lockout_slot_t;
118 :
119 : FD_STATIC_ASSERT( sizeof(lockout_slot_t)==12UL, lockout_slot );
120 :
121 : #define POOL_NAME lockout_slot_pool
122 0 : #define POOL_T lockout_slot_t
123 0 : #define POOL_NEXT next
124 : #define POOL_IDX_T uint
125 : #define POOL_LAZY 1
126 : #include "../../util/tmpl/fd_pool.c"
127 :
128 : #define MAP_NAME lockout_slot_map
129 : #define MAP_ELE_T lockout_slot_t
130 : #define MAP_KEY_T uint
131 0 : #define MAP_KEY slot
132 0 : #define MAP_KEY_EQ(k0,k1) (*(k0)==*(k1))
133 0 : #define MAP_KEY_HASH(key,seed) (fd_ulong_hash( (ulong)*(key) ^ (seed) ))
134 0 : #define MAP_NEXT next
135 0 : #define MAP_IDX_T uint
136 : #include "../../util/tmpl/fd_map_chain.c"
137 :
138 : /* lockout_pubkey_ref dedups pubkeys across lockout intervals. We know
139 : in the worst case there can be 2 * vtr_max pubkeys across all lockout
140 : intervals across all live banks since there are vtr_max unique
141 : pubkeys per epoch and a running validator can process up to 2 epochs
142 : at a time. */
143 :
144 : struct lockout_pubkey_ref {
145 : fd_pubkey_t addr;
146 : uint next; /* reserved for fd_map_chain and fd_pool */
147 : uint ref_cnt; /* number of normal lockout intervals referencing addr */
148 : };
149 : typedef struct lockout_pubkey_ref lockout_pubkey_ref_t;
150 :
151 : FD_STATIC_ASSERT( sizeof(lockout_pubkey_ref_t)==40UL, lockout_pubkey_ref );
152 :
153 : #define POOL_NAME lockout_pubkey_pool
154 0 : #define POOL_T lockout_pubkey_ref_t
155 0 : #define POOL_NEXT next
156 : #define POOL_IDX_T uint
157 : #define POOL_LAZY 1
158 : #include "../../util/tmpl/fd_pool.c"
159 :
160 : #define MAP_NAME lockout_pubkey_map
161 : #define MAP_ELE_T lockout_pubkey_ref_t
162 : #define MAP_KEY_T fd_pubkey_t
163 0 : #define MAP_KEY addr
164 0 : #define MAP_KEY_EQ(k0,k1) (!memcmp( (k0), (k1), sizeof(fd_pubkey_t) ))
165 0 : #define MAP_KEY_HASH(key,seed) (fd_ulong_hash( (key)->ul[0] ^ (seed) ))
166 0 : #define MAP_NEXT next
167 0 : #define MAP_IDX_T uint
168 : #include "../../util/tmpl/fd_map_chain.c"
169 :
170 : FD_FN_CONST static inline lockout_interval_key_t
171 : lockout_interval_key( ulong slot_idx,
172 : ulong interval_end,
173 : uint conf,
174 0 : ulong pubkey_idx ) {
175 0 : return (lockout_interval_key_t) {
176 0 : .interval_end = (uint)interval_end,
177 0 : .slot_conf_pubkey = (uint)slot_idx |
178 0 : (conf << LOCKOUT_INTERVAL_CONF_SHIFT) |
179 0 : ((uint)pubkey_idx << LOCKOUT_INTERVAL_PUBKEY_IDX_SHIFT),
180 0 : };
181 0 : }
182 :
183 : FD_FN_PURE static inline uint
184 0 : lockout_interval_key_conf( lockout_interval_key_t const * key ) {
185 0 : return (key->slot_conf_pubkey >> LOCKOUT_INTERVAL_CONF_SHIFT) & LOCKOUT_INTERVAL_CONF_MASK;
186 0 : }
187 :
188 : FD_FN_PURE static inline uint
189 0 : lockout_interval_key_pubkey_idx( lockout_interval_key_t const * key ) {
190 0 : return key->slot_conf_pubkey >> LOCKOUT_INTERVAL_PUBKEY_IDX_SHIFT;
191 0 : }
192 :
193 : FD_FN_PURE static inline uint
194 0 : lockout_interval_start( lockout_interval_t const * interval ) {
195 0 : return interval->key.interval_end - (1U << lockout_interval_key_conf( &interval->key ));
196 0 : }
197 :
198 0 : #define THRESHOLD_DEPTH (8)
199 0 : #define THRESHOLD_RATIO (2.0 / 3.0)
200 : #define SWITCH_RATIO (0.38)
201 :
202 : ulong
203 0 : fd_tower_align( void ) {
204 0 : return 128UL;
205 0 : }
206 :
207 : static int
208 : fd_tower_max_valid( ulong blk_max,
209 0 : ulong vtr_max ) {
210 : /* Lockout intervals use 13-bit slot-header and 14-bit pubkey pool indices. */
211 0 : if( FD_UNLIKELY( blk_max>(1UL<<LOCKOUT_INTERVAL_SLOT_IDX_BITS) || vtr_max>(1UL<<(LOCKOUT_INTERVAL_PUBKEY_IDX_BITS-1U)) ) ) return 0;
212 0 : if( FD_UNLIKELY( blk_max && vtr_max>UINT_MAX/blk_max ) ) return 0;
213 :
214 0 : ulong pair_max = blk_max * vtr_max;
215 0 : if( FD_UNLIKELY( pair_max>UINT_MAX/FD_TOWER_LOCKOS_MAX ) ) return 0;
216 0 : return 1;
217 0 : }
218 :
219 : ulong
220 : fd_tower_footprint( ulong blk_max,
221 0 : ulong vtr_max ) {
222 0 : if( FD_UNLIKELY( !fd_tower_max_valid( blk_max, vtr_max ) ) ) return 0UL;
223 :
224 0 : ulong lck_interval_max = FD_TOWER_LOCKOS_MAX*blk_max*vtr_max;
225 0 : ulong lck_map_chain_est = lockout_interval_map_chain_cnt_est( lck_interval_max );
226 0 : ulong lck_slot_chains = lockout_slot_map_chain_cnt_est( blk_max );
227 0 : ulong lck_pubkey_max = 2UL * vtr_max;
228 0 : ulong lck_pubkey_chains = lockout_pubkey_map_chain_cnt_est( lck_pubkey_max );
229 :
230 0 : ulong stk_vtr_chain_cnt = fd_tower_stakes_vtr_map_chain_cnt_est( vtr_max * blk_max );
231 0 : int stk_lg_slot_cnt = fd_ulong_find_msb( fd_ulong_pow2_up( blk_max ) ) + 1;
232 :
233 0 : ulong l = FD_LAYOUT_INIT;
234 0 : l = FD_LAYOUT_APPEND( l, 128UL, sizeof(fd_tower_t) );
235 0 : l = FD_LAYOUT_APPEND( l, fd_tower_vote_align(), fd_tower_vote_footprint() );
236 0 : l = FD_LAYOUT_APPEND( l, blk_pool_align(), blk_pool_footprint ( blk_max ) );
237 0 : l = FD_LAYOUT_APPEND( l, blk_map_align(), blk_map_footprint ( blk_map_chain_cnt_est( blk_max ) ) );
238 0 : l = FD_LAYOUT_APPEND( l, fd_tower_vtr_align(), fd_tower_vtr_footprint ( vtr_max ) );
239 0 : for( ulong i = 0; i < vtr_max; i++ ) {
240 0 : l = FD_LAYOUT_APPEND( l, fd_tower_vote_align(), fd_tower_vote_footprint() );
241 0 : }
242 : /* lockos */
243 0 : l = FD_LAYOUT_APPEND( l, lockout_interval_pool_align(), lockout_interval_pool_footprint( lck_interval_max ) );
244 0 : l = FD_LAYOUT_APPEND( l, lockout_interval_map_align(), lockout_interval_map_footprint ( lck_map_chain_est ) );
245 0 : l = FD_LAYOUT_APPEND( l, lockout_slot_pool_align(), lockout_slot_pool_footprint ( blk_max ) );
246 0 : l = FD_LAYOUT_APPEND( l, lockout_slot_map_align(), lockout_slot_map_footprint ( lck_slot_chains ) );
247 0 : l = FD_LAYOUT_APPEND( l, lockout_pubkey_pool_align(), lockout_pubkey_pool_footprint ( lck_pubkey_max ) );
248 0 : l = FD_LAYOUT_APPEND( l, lockout_pubkey_map_align(), lockout_pubkey_map_footprint ( lck_pubkey_chains ) );
249 : /* stakes */
250 0 : l = FD_LAYOUT_APPEND( l, fd_tower_stakes_vtr_map_align(), fd_tower_stakes_vtr_map_footprint ( stk_vtr_chain_cnt ) );
251 0 : l = FD_LAYOUT_APPEND( l, fd_tower_stakes_vtr_pool_align(), fd_tower_stakes_vtr_pool_footprint( vtr_max * blk_max ) );
252 0 : l = FD_LAYOUT_APPEND( l, fd_tower_stakes_slot_align(), fd_tower_stakes_slot_footprint( stk_lg_slot_cnt ) );
253 0 : l = FD_LAYOUT_APPEND( l, fd_used_acc_scratch_align(), fd_used_acc_scratch_footprint( vtr_max * blk_max ) );
254 0 : return FD_LAYOUT_FINI( l, fd_tower_align() );
255 0 : }
256 :
257 : void *
258 : fd_tower_new( void * shmem,
259 : ulong blk_max,
260 : ulong vtr_max,
261 0 : ulong seed ) {
262 :
263 0 : if( FD_UNLIKELY( !shmem ) ) {
264 0 : FD_LOG_WARNING(( "NULL mem" ));
265 0 : return NULL;
266 0 : }
267 :
268 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_tower_align() ) ) ) {
269 0 : FD_LOG_WARNING(( "misaligned mem" ));
270 0 : return NULL;
271 0 : }
272 :
273 0 : ulong footprint = fd_tower_footprint( blk_max, vtr_max );
274 0 : if( FD_UNLIKELY( !footprint ) ) {
275 0 : FD_LOG_WARNING(( "bad blk_max (%lu) or vtr_max (%lu)", blk_max, vtr_max ));
276 0 : return NULL;
277 0 : }
278 :
279 0 : ulong lck_interval_max = FD_TOWER_LOCKOS_MAX*blk_max*vtr_max;
280 0 : ulong lck_map_chain_est = lockout_interval_map_chain_cnt_est( lck_interval_max );
281 0 : ulong lck_slot_chains = lockout_slot_map_chain_cnt_est( blk_max );
282 0 : ulong lck_pubkey_max = 2UL * vtr_max;
283 0 : ulong lck_pubkey_chains = lockout_pubkey_map_chain_cnt_est( lck_pubkey_max );
284 :
285 0 : ulong stk_vtr_chain_cnt = fd_tower_stakes_vtr_map_chain_cnt_est( vtr_max * blk_max );
286 0 : int stk_lg_slot_cnt = fd_ulong_find_msb( fd_ulong_pow2_up( blk_max ) ) + 1;
287 :
288 0 : FD_SCRATCH_ALLOC_INIT( l, shmem );
289 0 : fd_tower_t * tower = FD_SCRATCH_ALLOC_APPEND( l, 128UL, sizeof(fd_tower_t) );
290 0 : void * votes = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_vote_align(), fd_tower_vote_footprint() );
291 0 : void * blk_pool = FD_SCRATCH_ALLOC_APPEND( l, blk_pool_align(), blk_pool_footprint ( blk_max ) );
292 0 : void * blk_map = FD_SCRATCH_ALLOC_APPEND( l, blk_map_align(), blk_map_footprint ( blk_map_chain_cnt_est( blk_max ) ) );
293 0 : void * vtrs = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_vtr_align(), fd_tower_vtr_footprint ( vtr_max ) );
294 0 : void * towers[ vtr_max ];
295 0 : for( ulong i = 0; i < vtr_max; i++ ) {
296 0 : towers[i] = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_vote_align(), fd_tower_vote_footprint() );
297 0 : }
298 0 : void * lck_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, lockout_interval_pool_align(), lockout_interval_pool_footprint( lck_interval_max ) );
299 0 : void * lck_map_mem = FD_SCRATCH_ALLOC_APPEND( l, lockout_interval_map_align(), lockout_interval_map_footprint ( lck_map_chain_est ) );
300 0 : void * lck_slot_pool = FD_SCRATCH_ALLOC_APPEND( l, lockout_slot_pool_align(), lockout_slot_pool_footprint ( blk_max ) );
301 0 : void * lck_slot_map = FD_SCRATCH_ALLOC_APPEND( l, lockout_slot_map_align(), lockout_slot_map_footprint ( lck_slot_chains ) );
302 0 : void * lck_pk_pool = FD_SCRATCH_ALLOC_APPEND( l, lockout_pubkey_pool_align(), lockout_pubkey_pool_footprint ( lck_pubkey_max ) );
303 0 : void * lck_pk_map = FD_SCRATCH_ALLOC_APPEND( l, lockout_pubkey_map_align(), lockout_pubkey_map_footprint ( lck_pubkey_chains ) );
304 0 : void * stk_vtr_map = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_stakes_vtr_map_align(), fd_tower_stakes_vtr_map_footprint ( stk_vtr_chain_cnt ) );
305 0 : void * stk_vtr_pool = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_stakes_vtr_pool_align(), fd_tower_stakes_vtr_pool_footprint( vtr_max * blk_max ) );
306 0 : void * stk_slot_map = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_stakes_slot_align(), fd_tower_stakes_slot_footprint( stk_lg_slot_cnt ) );
307 0 : void * stk_used_acc = FD_SCRATCH_ALLOC_APPEND( l, fd_used_acc_scratch_align(), fd_used_acc_scratch_footprint( vtr_max * blk_max ) );
308 0 : FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_tower_align() ) == (ulong)shmem + footprint );
309 :
310 0 : tower->root = ULONG_MAX;
311 0 : tower->blk_max = blk_max;
312 0 : tower->vtr_max = vtr_max;
313 0 : tower->votes = fd_tower_vote_new( votes );
314 0 : tower->blk_pool = blk_pool_new( blk_pool, blk_max );
315 0 : tower->blk_map = blk_map_new( blk_map, blk_map_chain_cnt_est( blk_max ), seed );
316 0 : tower->vtrs = fd_tower_vtr_new( vtrs, vtr_max );
317 0 : for( ulong i = 0; i < vtr_max; i++ ) {
318 0 : fd_tower_vtr_join( tower->vtrs )[i].votes = fd_tower_vote_new( towers[i] );
319 0 : }
320 :
321 0 : tower->wait_to_vote_slot = 0UL;
322 :
323 0 : tower->lck_pool = lockout_interval_pool_new( lck_pool_mem, lck_interval_max );
324 0 : tower->lck_map = lockout_interval_map_new ( lck_map_mem, lck_map_chain_est, seed );
325 0 : tower->lck_slot_pool = lockout_slot_pool_new ( lck_slot_pool, blk_max );
326 0 : tower->lck_slot_map = lockout_slot_map_new ( lck_slot_map, lck_slot_chains, seed );
327 0 : tower->lck_pubkey_pool = lockout_pubkey_pool_new ( lck_pk_pool, lck_pubkey_max );
328 0 : tower->lck_pubkey_map = lockout_pubkey_map_new ( lck_pk_map, lck_pubkey_chains, seed );
329 0 : tower->stk_vtr_map = fd_tower_stakes_vtr_map_new ( stk_vtr_map, stk_vtr_chain_cnt, seed );
330 0 : tower->stk_vtr_pool = fd_tower_stakes_vtr_pool_new( stk_vtr_pool, vtr_max * blk_max );
331 0 : tower->stk_slot_map = fd_tower_stakes_slot_new ( stk_slot_map, stk_lg_slot_cnt, seed );
332 0 : tower->stk_used_acc = fd_used_acc_scratch_new ( stk_used_acc, vtr_max * blk_max );
333 :
334 0 : return shmem;
335 0 : }
336 :
337 : fd_tower_t *
338 0 : fd_tower_join( void * shtower ) {
339 0 : fd_tower_t * tower = (fd_tower_t *)shtower;
340 :
341 0 : if( FD_UNLIKELY( !tower ) ) {
342 0 : FD_LOG_WARNING(( "NULL tower" ));
343 0 : return NULL;
344 0 : }
345 :
346 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)tower, fd_tower_align() ) ) ) {
347 0 : FD_LOG_WARNING(( "misaligned tower" ));
348 0 : return NULL;
349 0 : }
350 :
351 0 : tower->votes = fd_tower_vote_join( tower->votes );
352 0 : tower->blk_pool = blk_pool_join ( tower->blk_pool );
353 0 : tower->blk_map = blk_map_join ( tower->blk_map );
354 0 : tower->vtrs = fd_tower_vtr_join ( tower->vtrs );
355 0 : for( ulong i = 0; i < tower->vtr_max; i++ ) {
356 0 : tower->vtrs[i].votes = fd_tower_vote_join( tower->vtrs[i].votes );
357 0 : }
358 0 : tower->lck_pool = lockout_interval_pool_join( tower->lck_pool );
359 0 : tower->lck_map = lockout_interval_map_join ( tower->lck_map );
360 0 : tower->lck_slot_pool = lockout_slot_pool_join ( tower->lck_slot_pool );
361 0 : tower->lck_slot_map = lockout_slot_map_join ( tower->lck_slot_map );
362 0 : tower->lck_pubkey_pool = lockout_pubkey_pool_join ( tower->lck_pubkey_pool );
363 0 : tower->lck_pubkey_map = lockout_pubkey_map_join ( tower->lck_pubkey_map );
364 0 : tower->stk_vtr_map = fd_tower_stakes_vtr_map_join ( tower->stk_vtr_map );
365 0 : tower->stk_vtr_pool = fd_tower_stakes_vtr_pool_join( tower->stk_vtr_pool );
366 0 : tower->stk_slot_map = fd_tower_stakes_slot_join ( tower->stk_slot_map );
367 0 : tower->stk_used_acc = fd_used_acc_scratch_join ( tower->stk_used_acc );
368 :
369 0 : return tower;
370 0 : }
371 :
372 : void *
373 0 : fd_tower_leave( fd_tower_t const * tower ) {
374 :
375 0 : if( FD_UNLIKELY( !tower ) ) {
376 0 : FD_LOG_WARNING(( "NULL tower" ));
377 0 : return NULL;
378 0 : }
379 :
380 0 : return (void *)tower;
381 0 : }
382 :
383 : void *
384 0 : fd_tower_delete( void * shtower ) {
385 :
386 0 : if( FD_UNLIKELY( !shtower ) ) {
387 0 : FD_LOG_WARNING(( "NULL tower" ));
388 0 : return NULL;
389 0 : }
390 :
391 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shtower, fd_tower_align() ) ) ) {
392 0 : FD_LOG_WARNING(( "misaligned tower" ));
393 0 : return NULL;
394 0 : }
395 :
396 0 : return shtower;
397 0 : }
398 :
399 : /* expiration calculates the expiration slot of vote given a slot and
400 : confirmation count. */
401 :
402 : static inline ulong
403 0 : expiration_slot( fd_tower_vote_t const * vote ) {
404 0 : ulong lockout = 1UL << vote->conf;
405 0 : return vote->slot + lockout;
406 0 : }
407 :
408 : /* simulate_vote simulates voting for slot, popping all votes from the
409 : top that would be consecutively expired by voting for slot. */
410 :
411 : static ulong
412 : simulate_vote( fd_tower_vote_t const * votes,
413 0 : ulong slot ) {
414 0 : ulong cnt = fd_tower_vote_cnt( votes );
415 0 : while( cnt ) {
416 0 : fd_tower_vote_t const * top_vote = fd_tower_vote_peek_index_const( votes, cnt - 1 );
417 0 : if( FD_LIKELY( expiration_slot( top_vote ) >= slot ) ) break; /* expire only if consecutive */
418 0 : cnt--;
419 0 : }
420 0 : return cnt;
421 0 : }
422 :
423 : /* push_vote pushes a new vote for slot onto the tower. Pops and
424 : returns the new root (bottom of the tower) if it reaches max lockout
425 : as a result of the new vote. Otherwise, returns ULONG_MAX.
426 :
427 : Max lockout is equivalent to 1 << FD_TOWER_VOTE_MAX + 1 (which
428 : implies confirmation count is FD_TOWER_VOTE_MAX + 1). As a result,
429 : fd_tower_vote also maintains the invariant that the tower contains at
430 : most FD_TOWER_VOTE_MAX votes, because (in addition to vote expiry)
431 : there will always be a pop before reaching FD_TOWER_VOTE_MAX + 1. */
432 :
433 : static ulong
434 : push_vote( fd_tower_t * tower,
435 0 : ulong slot ) {
436 :
437 : /* Sanity check: slot should always be greater than previous vote slot in tower. */
438 :
439 0 : fd_tower_vote_t const * vote = fd_tower_vote_peek_tail_const( tower->votes );
440 0 : if( FD_UNLIKELY( vote && slot <= vote->slot ) ) FD_LOG_CRIT(( "[%s] slot %lu <= vote->slot %lu", __func__, slot, vote->slot ));
441 :
442 : /* Use simulate_vote to determine how many expired votes to pop. */
443 :
444 0 : ulong cnt = simulate_vote( tower->votes, slot );
445 :
446 : /* Pop everything that got expired. */
447 :
448 0 : while( FD_LIKELY( fd_tower_vote_cnt( tower->votes ) > cnt ) ) {
449 0 : fd_tower_vote_pop_tail( tower->votes );
450 0 : }
451 :
452 : /* If the tower is still full after expiring, then pop and return the
453 : bottom vote slot as the new root because this vote has incremented
454 : it to max lockout. Otherwise this is a no-op and there is no new
455 : root (ULONG_MAX). */
456 :
457 0 : ulong root = ULONG_MAX;
458 0 : if( FD_LIKELY( fd_tower_vote_full( tower->votes ) ) ) { /* optimize for full tower */
459 0 : root = fd_tower_vote_pop_head( tower->votes ).slot;
460 0 : }
461 :
462 : /* Increment confirmations (double lockouts) for consecutive
463 : confirmations in prior votes. */
464 :
465 0 : ulong prev_conf = 0;
466 0 : for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init_rev( tower->votes );
467 0 : !fd_tower_vote_iter_done_rev( tower->votes, iter );
468 0 : iter = fd_tower_vote_iter_prev ( tower->votes, iter ) ) {
469 0 : fd_tower_vote_t * vote = fd_tower_vote_iter_ele( tower->votes, iter );
470 0 : if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) break;
471 0 : vote->conf++;
472 0 : }
473 :
474 : /* Add the new vote to the tower. */
475 :
476 0 : fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot = slot, .conf = 1 } );
477 :
478 : /* Return the new root (FD_SLOT_NULL if there is none). */
479 :
480 0 : return root;
481 0 : }
482 :
483 : /* lockout_check checks if we are locked out from voting for slot.
484 : Returns 1 if we can vote for slot without violating lockout, 0
485 : otherwise.
486 :
487 : After voting for a slot n, we are locked out for 2^k slots, where k
488 : is the confirmation count of that vote. Once locked out, we cannot
489 : vote for a different fork until that previously-voted fork expires at
490 : slot n+2^k. This implies the earliest slot in which we can switch
491 : from the previously-voted fork is (n+2^k)+1. We use `ghost` to
492 : determine whether `slot` is on the same or different fork as previous
493 : vote slots.
494 :
495 : In the case of the tower, every vote has its own expiration slot
496 : depending on confirmations. The confirmation count is the max number
497 : of consecutive votes that have been pushed on top of the vote, and
498 : not necessarily its current height in the tower.
499 :
500 : For example, the following is a diagram of a tower pushing and
501 : popping with each vote:
502 :
503 :
504 : slot | confirmation count
505 : -----|-------------------
506 : 4 | 1 <- vote
507 : 3 | 2
508 : 2 | 3
509 : 1 | 4
510 :
511 :
512 : slot | confirmation count
513 : -----|-------------------
514 : 9 | 1 <- vote
515 : 2 | 3
516 : 1 | 4
517 :
518 :
519 : slot | confirmation count
520 : -----|-------------------
521 : 10 | 1 <- vote
522 : 9 | 2
523 : 2 | 3
524 : 1 | 4
525 :
526 :
527 : slot | confirmation count
528 : -----|-------------------
529 : 11 | 1 <- vote
530 : 10 | 2
531 : 9 | 3
532 : 2 | 4
533 : 1 | 5
534 :
535 :
536 : slot | confirmation count
537 : -----|-------------------
538 : 18 | 1 <- vote
539 : 2 | 4
540 : 1 | 5
541 :
542 :
543 : In the final tower, note the gap in confirmation counts between slot
544 : 18 and slot 2, even though slot 18 is directly above slot 2. */
545 :
546 : static int
547 : lockout_check( fd_tower_t * tower,
548 0 : ulong slot ) {
549 :
550 0 : if( FD_UNLIKELY( slot<tower->wait_to_vote_slot ) ) return 0;
551 :
552 : /* Mirrors Agave's Tower::is_recent(): reject slot if it is not strictly
553 : newer than our last vote (non-empty tower) or our root (empty tower,
554 : e.g. snapshot boot).
555 : https://github.com/anza-xyz/agave/blob/v4.0.0-alpha.0/core/src/consensus.rs#L825-L836 */
556 0 : if( FD_UNLIKELY( fd_tower_vote_empty( tower->votes ) ) )
557 0 : return tower->root==ULONG_MAX || slot>tower->root;
558 0 : if( FD_UNLIKELY( slot<=fd_tower_vote_peek_tail_const( tower->votes )->slot ) ) return 0;
559 :
560 : /* Simulate a vote to pop off all the votes that would be expired by
561 : voting for slot. Then check if the newly top-of-tower vote is on
562 : the same fork as slot (if so this implies we can vote for it). */
563 :
564 0 : ulong cnt = simulate_vote( tower->votes, slot ); /* pop off votes that would be expired */
565 0 : if( FD_UNLIKELY( !cnt ) ) return 1; /* tower is empty after popping expired votes */
566 :
567 0 : fd_tower_vote_t const * vote = fd_tower_vote_peek_index_const( tower->votes, cnt - 1 ); /* newly top-of-tower */
568 0 : int lockout = fd_tower_blocks_is_slot_descendant( tower, vote->slot, slot ); /* check if on same fork */
569 0 : return lockout;
570 0 : }
571 :
572 : /* switch_check checks if we can switch to the fork of `slot`. Returns
573 : 1 if we can switch, 0 otherwise. Assumes tower is non-empty.
574 :
575 : There are two forks of interest: our last vote fork ("vote fork") and
576 : the fork we want to switch to ("switch fork"). The switch fork is on
577 : the fork of `slot`.
578 :
579 : In order to switch, SWITCH_RATIO of stake must have voted for
580 : a slot that satisfies the following conditions: the
581 : GCA(slot, last_vote) is an ancestor of the switch_slot
582 :
583 : Recall from the lockout check a validator is locked out from voting
584 : for our last vote slot when their last vote slot is on a different
585 : fork, and that vote's expiration slot > our last vote slot.
586 :
587 : The following pseudocode describes the algorithm:
588 :
589 : ```
590 : for every fork f in the fork tree, take the most recently executed
591 : slot `s` (the leaf of the fork).
592 :
593 : Take the greatest common ancestor of the `s` and the our last vote
594 : slot. If the switch_slot is a descendant of this GCA, then votes for
595 : `s` can count towards the switch threshold.
596 :
597 : query banks(`s`) for vote accounts in `s`
598 : for all vote accounts v in `s`
599 : if v's locked out[1] from voting for our latest vote slot
600 : add v's stake to switch stake
601 :
602 : return switch stake >= total_stake * SWITCH_RATIO
603 : ```
604 :
605 : The switch check is used to safeguard optimistic confirmation.
606 : Specifically: optimistic confirmation pct + SWITCH_RATIO >= 1. */
607 :
608 : static int
609 : is_purged( fd_tower_t * tower,
610 0 : fd_ghost_blk_t * blk ) {
611 0 : fd_tower_blk_t * tower_blk = fd_tower_blocks_query( tower, blk->slot );
612 0 : return tower_blk->confirmed && memcmp( &tower_blk->confirmed_block_id, &blk->id, sizeof(fd_hash_t) );
613 0 : }
614 :
615 : static int
616 : switch_check( fd_tower_t * tower,
617 : fd_ghost_t * ghost,
618 : ulong total_stake,
619 0 : ulong switch_slot ) {
620 :
621 0 : lockout_interval_map_t * lck_map = tower->lck_map;
622 0 : lockout_interval_t * lck_pool = tower->lck_pool;
623 0 : lockout_slot_map_t const * lck_slot_map = tower->lck_slot_map;
624 0 : lockout_slot_t const * lck_slot_pool = tower->lck_slot_pool;
625 :
626 0 : ulong switch_stake = 0;
627 0 : ulong vote_slot = fd_tower_vote_peek_tail_const( tower->votes )->slot;
628 0 : ulong root_slot = tower->root;
629 :
630 0 : ulong null = fd_ghost_blk_idx_null( ghost );
631 0 : fd_ghost_blk_t * head = fd_ghost_blk_map_remove( ghost, fd_ghost_root( ghost ) );
632 0 : fd_ghost_blk_t * tail = head;
633 0 : head->next = null;
634 :
635 0 : while( FD_LIKELY( head ) ) {
636 0 : fd_ghost_blk_t * blk = head; /* guaranteed to not be purged */
637 :
638 : /* Because agave has particular behavior where if they replay a
639 : equivocating version of a slot and then the correct version, the
640 : original version and all of it's children get purged from all
641 : structures. None of the nodes on this subtree can be considered
642 : for the switch proof. Note that this means as we BFS, a node
643 : can be considered a "valid leaf" if either it has no children,
644 : or if all of it's children are purged/superseded slots. We
645 : detect this by comparing against tower_blocks confirmed. */
646 :
647 0 : int is_valid_leaf = 1;
648 0 : fd_ghost_blk_t * child = fd_ghost_blk_child( ghost, head );
649 0 : while( FD_LIKELY( child ) ) {
650 0 : if( FD_LIKELY( !is_purged( tower, child ) ) ) {
651 0 : fd_ghost_blk_map_remove( ghost, child );
652 0 : tail->next = fd_ghost_blk_idx( ghost, child );
653 0 : tail = child;
654 0 : tail->next = null;
655 0 : is_valid_leaf = 0;
656 0 : }
657 0 : child = fd_ghost_blk_sibling( ghost, child );
658 0 : }
659 :
660 0 : head = fd_ghost_blk_next( ghost, blk ); /* pop queue head */
661 0 : fd_ghost_blk_map_insert( ghost, blk ); /* re-insert into map */
662 :
663 0 : if( FD_UNLIKELY( !is_valid_leaf ) ) continue; /* not a real candidate */
664 :
665 0 : ulong candidate_slot = blk->slot;
666 0 : ulong lca = fd_tower_blocks_lowest_common_ancestor( tower, candidate_slot, vote_slot );
667 0 : if( FD_UNLIKELY( candidate_slot == vote_slot ) ) continue;
668 0 : if( FD_UNLIKELY( lca==ULONG_MAX ) ) continue; /* unlikely but this leaf is an already pruned minority fork */
669 :
670 0 : if( FD_UNLIKELY( fd_tower_blocks_is_slot_descendant( tower, lca, switch_slot ) ) ) {
671 :
672 : /* This candidate slot may be considered for the switch proof, if
673 : it passes the following conditions:
674 :
675 : https://github.com/anza-xyz/agave/blob/c7b97bc77addacf03b229c51b47c18650d909576/core/src/consensus.rs#L1117
676 :
677 : Now for this candidate slot, look at the lockouts that were
678 : created at the time that we processed the bank for this
679 : candidate slot. */
680 :
681 0 : uint candidate_slot_key = (uint)candidate_slot;
682 0 : lockout_slot_t const * lockout_slot = lockout_slot_map_ele_query_const( lck_slot_map, &candidate_slot_key, NULL, lck_slot_pool );
683 0 : if( FD_UNLIKELY( !lockout_slot ) ) continue;
684 0 : ulong slot_idx = lockout_slot_pool_idx( lck_slot_pool, lockout_slot );
685 :
686 0 : for( lockout_interval_t const * lockout = lockout_interval_pool_ele_const( lck_pool, lockout_slot->end_head );
687 0 : !!lockout;
688 0 : lockout = lockout_interval_pool_ele_const( lck_pool, lockout->end_next ) ) {
689 0 : uint interval_end = lockout->key.interval_end;
690 0 : lockout_interval_key_t key = lockout_interval_key( slot_idx, interval_end, 0U, 0UL );
691 :
692 : /* Intervals are keyed by the end of the interval. If the end of
693 : the interval is < the last vote slot, then these vote
694 : accounts with this particular lockout are NOT locked out from
695 : voting for the last vote slot, which means we can skip this
696 : set of intervals. */
697 :
698 0 : if( FD_LIKELY( interval_end < vote_slot ) ) continue;
699 :
700 : /* At this point we can actually query for the intervals by
701 : end interval to get the vote accounts. */
702 :
703 0 : for( lockout_interval_t const * interval = lockout_interval_map_ele_query_const( lck_map, &key, NULL, lck_pool );
704 0 : interval;
705 0 : interval = lockout_interval_map_ele_next_const( interval, NULL, lck_pool ) ) {
706 0 : fd_hash_t const * vote_acc = &lockout_pubkey_pool_ele_const( tower->lck_pubkey_pool, lockout_interval_key_pubkey_idx( &interval->key ) )->addr;
707 :
708 0 : uint interval_start = lockout_interval_start( interval );
709 0 : if( FD_UNLIKELY( !fd_tower_blocks_is_slot_descendant( tower, interval_start, vote_slot ) && interval_start > root_slot ) ) {
710 0 : fd_tower_stakes_vtr_xid_t key = { .addr = *vote_acc, .slot = switch_slot };
711 0 : fd_tower_stakes_vtr_t const * voter_stake = fd_tower_stakes_vtr_map_ele_query_const( tower->stk_vtr_map, &key, NULL, tower->stk_vtr_pool );
712 :
713 : /* Vote account could have been closed on the switch fork,
714 : and therefore not in the tower stakes map. In this case
715 : just count the vote stake as 0 and skip this voter.
716 : matches Agave. */
717 0 : if( FD_UNLIKELY( !voter_stake ) ) continue;
718 0 : ulong voter_idx = fd_tower_stakes_vtr_pool_idx( tower->stk_vtr_pool, voter_stake );
719 0 : if( FD_UNLIKELY( fd_used_acc_scratch_test( tower->stk_used_acc, voter_idx ) ) ) continue; /* exclude already counted voters */
720 0 : fd_used_acc_scratch_insert( tower->stk_used_acc, voter_idx );
721 0 : switch_stake += voter_stake->stake;
722 0 : if( FD_LIKELY( (double)switch_stake / (double)total_stake > SWITCH_RATIO ) ) {
723 0 : fd_used_acc_scratch_null( tower->stk_used_acc );
724 0 : FD_LOG_DEBUG(( "[%s] vote_slot: %lu. switch_slot: %lu. pct: %.0lf%%", __func__, vote_slot, switch_slot, (double)switch_stake / (double)total_stake * 100.0 ));
725 0 : while( FD_LIKELY( head ) ) { /* cleanup: re-insert remaining BFS queue into map */
726 0 : fd_ghost_blk_t * next = fd_ghost_blk_next( ghost, head );
727 0 : fd_ghost_blk_map_insert( ghost, head );
728 0 : head = next;
729 0 : }
730 0 : return 1;
731 0 : }
732 0 : }
733 0 : }
734 0 : }
735 0 : }
736 0 : }
737 0 : fd_used_acc_scratch_null( tower->stk_used_acc );
738 0 : FD_LOG_DEBUG(( "[%s] vote_slot: %lu. switch_slot: %lu. pct: %.0lf%%", __func__, vote_slot, switch_slot, (double)switch_stake / (double)total_stake * 100.0 ));
739 0 : return 0;
740 0 : }
741 :
742 : /* threshold_check checks if we pass the threshold required to vote for
743 : `slot`. Returns 1 if we pass the threshold check, 0 otherwise.
744 :
745 : The following pseudocode describes the algorithm:
746 :
747 : ```
748 : simulate that we have voted for `slot`
749 :
750 : for all vote accounts in the current epoch
751 :
752 : simulate that the vote account has voted for `slot`
753 :
754 : pop all votes expired by that simulated vote
755 :
756 : if the validator's latest tower vote after expiry >= our threshold
757 : slot ie. our vote from THRESHOLD_DEPTH back also after simulating,
758 : then add validator's stake to threshold_stake.
759 :
760 : return threshold_stake >= FD_TOWER_THRESHOLD_RATIO
761 : ```
762 :
763 : The threshold check simulates voting for the current slot to expire
764 : stale votes. This is to prevent validators that haven't voted in a
765 : long time from counting towards the threshold stake. */
766 :
767 : static int
768 : threshold_check( fd_tower_t const * tower,
769 : fd_tower_vtr_t const * accts,
770 : ulong total_stake,
771 0 : ulong slot ) {
772 :
773 : /* First, simulate a vote on our tower, popping off everything that
774 : would be expired by voting for slot. */
775 :
776 0 : ulong cnt = simulate_vote( tower->votes, slot );
777 :
778 : /* We can always vote if our tower is not at least THRESHOLD_DEPTH
779 : deep after simulating. */
780 :
781 0 : if( FD_UNLIKELY( cnt < THRESHOLD_DEPTH ) ) return 1;
782 :
783 : /* Get the vote slot from THRESHOLD_DEPTH back. Note THRESHOLD_DEPTH
784 : is the 8th index back _including_ the simulated vote at index 0. */
785 :
786 0 : ulong threshold_slot = fd_tower_vote_peek_index_const( tower->votes, cnt - THRESHOLD_DEPTH )->slot;
787 0 : ulong threshold_stake = 0;
788 0 : for( fd_tower_vtr_iter_t iter = fd_tower_vtr_iter_init( accts );
789 0 : !fd_tower_vtr_iter_done( accts, iter );
790 0 : iter = fd_tower_vtr_iter_next( accts, iter ) ) {
791 0 : fd_tower_vtr_t const * acct = fd_tower_vtr_iter_ele_const( accts, iter );
792 :
793 0 : ulong cnt = simulate_vote( acct->votes, slot ); /* expire votes */
794 0 : if( FD_UNLIKELY( !cnt ) ) continue; /* no votes left after expiry */
795 :
796 : /* Count their stake towards the threshold check if their prev vote
797 : slot >= our threshold slot.
798 :
799 : We know their prev vote slot is definitely on the same fork as
800 : our threshold slot, because these towers are sourced from vote
801 : _accounts_, not vote _transactions_ and the Vote Program
802 : validates that all slots in the vote account's tower exist on the
803 : current fork.
804 :
805 : Therefore, if their prev vote slot >= our threshold slot, we know
806 : that vote must be for the threshold slot itself or one of
807 : threshold slot's descendants. */
808 :
809 0 : ulong vote_slot = fd_tower_vote_peek_index_const( acct->votes, cnt - 1 )->slot;
810 0 : if( FD_LIKELY( vote_slot >= threshold_slot ) ) threshold_stake += acct->stake;
811 0 : }
812 :
813 0 : double threshold_pct = (double)threshold_stake / (double)total_stake;
814 0 : int threshold = threshold_pct > THRESHOLD_RATIO;
815 0 : if( FD_UNLIKELY( !threshold ) ) FD_LOG_DEBUG(( "[%s] vote_slot: %lu. threshold_slot: %lu. pct: %.0lf%%.", __func__, fd_tower_vote_peek_tail_const( tower->votes )->slot, threshold_slot, threshold_pct * 100.0 ));
816 0 : return threshold;
817 0 : }
818 :
819 : static int
820 : propagated_check( fd_tower_t * tower,
821 0 : ulong slot ) {
822 :
823 0 : fd_tower_blk_t * blk = fd_tower_blocks_query( tower, slot );
824 0 : FD_TEST( blk );
825 :
826 0 : if( FD_LIKELY( blk->leader ) ) return 1; /* can always vote for slot in which we're leader */
827 0 : if( FD_LIKELY( blk->prev_leader_slot==ULONG_MAX ) ) return 1; /* haven't been leader yet */
828 :
829 0 : fd_tower_blk_t * prev_leader_blk = fd_tower_blocks_query( tower, blk->prev_leader_slot );
830 0 : if( FD_LIKELY( !prev_leader_blk ) ) return 1; /* already pruned / rooted */
831 :
832 0 : return prev_leader_blk->propagated;
833 0 : }
834 :
835 : uchar
836 : fd_tower_vote_and_reset( fd_tower_t * tower,
837 : fd_ghost_t * ghost,
838 : fd_votes_t * votes FD_PARAM_UNUSED,
839 : ulong * reset_slot,
840 : fd_hash_t * reset_block_id,
841 : ulong * reset_bank_seq,
842 : ulong * vote_slot,
843 : fd_hash_t * vote_block_id,
844 : fd_hash_t * vote_bank_hash,
845 : ulong * root_slot,
846 0 : fd_hash_t * root_block_id ) {
847 :
848 0 : uchar flags = 0;
849 0 : fd_ghost_blk_t const * best_blk = fd_ghost_best( ghost, fd_ghost_root( ghost ) );
850 0 : fd_ghost_blk_t const * reset_blk = NULL;
851 0 : fd_ghost_blk_t const * vote_blk = NULL;
852 :
853 : /* Case 0: if we haven't voted yet then there are two subcases where
854 : we short-circuit. */
855 :
856 : /* Case 0a: on boot, tower->root is set to the snapshot slot before
857 : any votes are recorded. In this case, lockout_check returns 0 for
858 : slot <= root, preventing a vote on the snapshot slot itself. */
859 :
860 : /* TODO refactor: 0a is a tile-concern not logic-concern */
861 :
862 0 : if( FD_UNLIKELY( fd_tower_vote_empty( tower->votes ) && !lockout_check( tower, best_blk->slot ) ) ) {
863 0 : FD_BASE58_ENCODE_32_BYTES( best_blk->id.uc, best_blk_id );
864 0 : FD_LOG_DEBUG(( "[%s] case 0a: not recent (slot %lu <= root %lu). reset_blk: (%lu, %s). vote_blk: (NULL)", __func__, best_blk->slot, tower->root, best_blk->slot, best_blk_id ));
865 0 : *reset_slot = best_blk->slot;
866 0 : *reset_block_id = best_blk->id;
867 0 : *reset_bank_seq = best_blk->bank_seq;
868 0 : *vote_slot = ULONG_MAX;
869 0 : *vote_block_id = (fd_hash_t){0};
870 0 : *root_slot = ULONG_MAX;
871 0 : *root_block_id = (fd_hash_t){0};
872 0 : return flags;
873 0 : }
874 :
875 : /* Case 0b: if we haven't voted yet then we can always vote and reset
876 : to ghost_best. */
877 :
878 0 : if( FD_UNLIKELY( fd_tower_vote_empty( tower->votes ) ) ) {
879 0 : FD_BASE58_ENCODE_32_BYTES( best_blk->id.uc, best_blk_id );
880 0 : FD_LOG_DEBUG(( "[%s] case 0b: empty tower. reset_blk: (%lu, %s). vote_blk: (%lu, %s)", __func__, best_blk->slot, best_blk_id, best_blk->slot, best_blk_id ));
881 0 : fd_tower_blk_t * tower_blk = fd_tower_blocks_query( tower, best_blk->slot );
882 0 : tower_blk->voted = 1;
883 0 : tower_blk->voted_block_id = best_blk->id;
884 0 : *reset_slot = best_blk->slot;
885 0 : *reset_block_id = best_blk->id;
886 0 : *reset_bank_seq = best_blk->bank_seq;
887 0 : *vote_slot = best_blk->slot;
888 0 : *vote_block_id = best_blk->id;
889 0 : *vote_bank_hash = tower_blk->bank_hash;
890 0 : *root_slot = push_vote( tower, best_blk->slot );
891 0 : *root_block_id = ( fd_hash_t ){ 0 };
892 0 : return flags;
893 0 : }
894 :
895 0 : ulong prev_vote_slot = fd_tower_vote_peek_tail_const( tower->votes )->slot;
896 0 : fd_tower_blk_t * prev_vote_fork = fd_tower_blocks_query( tower, prev_vote_slot ); /* must exist */
897 :
898 0 : fd_hash_t * prev_vote_block_id = &prev_vote_fork->voted_block_id;
899 0 : fd_ghost_blk_t * prev_vote_blk = fd_ghost_query( ghost, prev_vote_block_id );
900 :
901 : /* Case 1: if any ancestor of our prev vote (including prev vote
902 : itself) is an unconfirmed duplicate, then our prev vote was on a
903 : duplicate fork.
904 :
905 : There are three subcases to check. */
906 :
907 0 : int invalid_ancestor = !!fd_ghost_invalid_ancestor( ghost, prev_vote_blk );
908 :
909 : /* Case 1a: ghost_best is an ancestor of prev vote. This means
910 : ghost_best is rolling back to an ancestor that precedes the
911 : duplicate ancestor on the same fork as our prev vote. In this
912 : case, we can't vote on our ancestor, but we do reset to that
913 : ancestor.
914 :
915 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1016-L1019 */
916 :
917 0 : int ancestor_rollback = prev_vote_blk != best_blk && !!fd_ghost_ancestor( ghost, prev_vote_blk, &best_blk->id );
918 :
919 : /* Case 1b: ghost_best is not an ancestor, but prev_vote is a
920 : duplicate and we've confirmed its duplicate sibling. In this
921 : case, we allow switching to ghost_best without a switch proof.
922 :
923 : Example: slot 5 is a duplicate. We first receive, replay and
924 : vote for block 5, so that is our prev vote. We later receive
925 : block 5' and observe that it is duplicate confirmed. ghost_best
926 : now returns block 5' and we both vote and reset to block 5'
927 : regardless of the switch check.
928 :
929 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1021-L1024 */
930 :
931 0 : int sibling_confirmed = prev_vote_fork->confirmed && 0!=memcmp( &prev_vote_fork->voted_block_id, &prev_vote_fork->confirmed_block_id, sizeof(fd_hash_t) );
932 :
933 0 : if( FD_UNLIKELY( invalid_ancestor && ancestor_rollback ) ) {
934 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_ANCESTOR_ROLLBACK );
935 0 : reset_blk = best_blk;
936 0 : FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id );
937 0 : FD_LOG_DEBUG(( "[%s] case 1a: ancestor rollback. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (NULL)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id ));
938 :
939 0 : } else if( FD_UNLIKELY( invalid_ancestor && sibling_confirmed ) ) {
940 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SIBLING_CONFIRMED );
941 0 : reset_blk = best_blk;
942 0 : vote_blk = best_blk;
943 0 : FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id );
944 0 : FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id );
945 0 : FD_LOG_DEBUG(( "[%s] case 1b: sibling confirmed. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (%lu, %s)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id, vote_blk->slot, vote_blk_id ));
946 0 : }
947 :
948 : /* Case 2: if our prev vote slot is an ancestor of the best slot, then
949 : they are on the same fork and we can both reset to it. We can also
950 : vote for it if we pass the can_vote checks.
951 :
952 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1057 */
953 :
954 0 : else if( FD_LIKELY( best_blk->slot == prev_vote_slot || fd_tower_blocks_is_slot_ancestor( tower, best_blk->slot, prev_vote_slot ) ) ) {
955 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SAME_FORK );
956 0 : reset_blk = best_blk;
957 0 : vote_blk = best_blk;
958 0 : FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id );
959 0 : FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id );
960 0 : FD_LOG_DEBUG(( "[%s] case 2: same fork. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (%lu, %s)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id, vote_blk->slot, vote_blk_id ));
961 0 : }
962 :
963 : /* Case 3: if our prev vote is not an ancestor of the best block, then
964 : it is on a different fork. If we pass the switch check, we can
965 : reset to it. If we additionally pass the lockout check, we can
966 : also vote for it.
967 :
968 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1208-L1215
969 :
970 : Note also Agave uses the best blk's total stake for checking the
971 : threshold.
972 :
973 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/fork_choice.rs#L443-L445 */
974 :
975 0 : else if( FD_LIKELY( switch_check( tower, ghost, best_blk->total_stake, best_blk->slot ) ) ) {
976 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_PASS );
977 0 : reset_blk = best_blk;
978 0 : vote_blk = best_blk;
979 0 : FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id );
980 0 : FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id );
981 0 : FD_LOG_DEBUG(( "[%s] case 3: switch pass. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (%lu, %s)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id, vote_blk->slot, vote_blk_id ));
982 0 : }
983 :
984 : /* Case 4: same as case 3 but we didn't pass the switch check. In
985 : this case we reset to either ghost_best or ghost_deepest beginning
986 : from our prev vote blk.
987 :
988 : We must reset to a block beginning from our prev vote fork to
989 : ensure votes get a chance to propagate. Because in order for votes
990 : to land, someone needs to build a block on that fork.
991 :
992 : We reset to ghost_best or ghost_deepest depending on whether our
993 : prev vote is valid. When it's invalid we use ghost_deepest instead
994 : of ghost_best, because ghost_best won't be able to return a valid
995 : block beginning from our prev_vote because by definition the entire
996 : subtree will be invalid.
997 :
998 : When our prev vote fork is not a duplicate, we want to propagate
999 : votes that might allow others to switch to our fork. In addition,
1000 : if our prev vote fork is a duplicate, we want to propagate votes
1001 : that might "duplicate confirm" that block (reach 52% of stake).
1002 :
1003 : See top-level documentation in fd_tower.h for more details on vote
1004 : propagation. */
1005 :
1006 0 : else {
1007 :
1008 : /* Case 4a: failed switch check and last vote slot has an invalid
1009 : ancestor.
1010 :
1011 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/heaviest_subtree_fork_choice.rs#L1187 */
1012 :
1013 0 : if( FD_UNLIKELY( invalid_ancestor ) ) {
1014 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_FAIL );
1015 0 : reset_blk = fd_ghost_deepest( ghost, prev_vote_blk );
1016 0 : FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id );
1017 0 : FD_LOG_DEBUG(( "[%s] case 4a: switch fail, invalid ancestor. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (NULL)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id ));
1018 0 : }
1019 :
1020 : /* Case 4b: failed switch check (no invalid ancestor).
1021 :
1022 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/fork_choice.rs#L200 */
1023 :
1024 0 : else {
1025 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_FAIL );
1026 0 : reset_blk = fd_ghost_best( ghost, prev_vote_blk );
1027 0 : FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id );
1028 0 : FD_LOG_DEBUG(( "[%s] case 4b: switch fail, no invalid ancestor. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (NULL)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id ));
1029 0 : }
1030 0 : }
1031 :
1032 : /* If there is a block to vote for, there are a few additional checks
1033 : to make sure we can actually vote for it.
1034 :
1035 : Specifically, we need to make sure we're not locked out, pass the
1036 : threshold check and that our previous leader block has propagated
1037 : (reached the prop threshold according to fd_votes).
1038 :
1039 : https://github.com/firedancer-io/agave/blob/master/core/src/consensus/fork_choice.rs#L382-L385
1040 :
1041 : Agave uses the total stake on the fork being threshold checked
1042 : (vote_blk) for determining whether it meets the stake threshold. */
1043 :
1044 0 : if( FD_LIKELY( vote_blk ) ) {
1045 0 : if ( FD_UNLIKELY( !lockout_check( tower, vote_blk->slot ) ) ) {
1046 0 : FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id );
1047 0 : FD_LOG_DEBUG(( "[%s] lockout check failed. prev_vote_slot: %lu. vote_blk: (%lu, %s)", __func__, prev_vote_slot, vote_blk->slot, vote_blk_id ));
1048 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_LOCKOUT_FAIL );
1049 0 : vote_blk = NULL;
1050 0 : }
1051 0 : else if( FD_UNLIKELY( !threshold_check( tower, tower->vtrs, vote_blk->total_stake, vote_blk->slot ) ) ) {
1052 0 : FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id );
1053 0 : FD_LOG_DEBUG(( "[%s] threshold check failed. prev_vote_slot: %lu. vote_blk: (%lu, %s)", __func__, prev_vote_slot, vote_blk->slot, vote_blk_id ));
1054 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_THRESHOLD_FAIL );
1055 0 : vote_blk = NULL;
1056 0 : }
1057 0 : else if( FD_UNLIKELY( !propagated_check( tower, vote_blk->slot ) ) ) {
1058 0 : FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id );
1059 0 : FD_LOG_DEBUG(( "[%s] propagated check failed. prev_vote_slot: %lu. vote_blk: (%lu, %s)", __func__, prev_vote_slot, vote_blk->slot, vote_blk_id ));
1060 0 : flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_PROPAGATED_FAIL );
1061 0 : vote_blk = NULL;
1062 0 : }
1063 0 : }
1064 :
1065 0 : FD_TEST( reset_blk ); /* always a reset_blk */
1066 0 : *reset_slot = reset_blk->slot;
1067 0 : *reset_block_id = reset_blk->id;
1068 0 : *reset_bank_seq = reset_blk->bank_seq;
1069 0 : *vote_slot = ULONG_MAX;
1070 0 : *vote_block_id = (fd_hash_t){0};
1071 0 : *vote_bank_hash = (fd_hash_t){0};
1072 0 : *root_slot = ULONG_MAX;
1073 0 : *root_block_id = (fd_hash_t){0};
1074 :
1075 : /* Finally, if our vote passed all the checks, we actually push the
1076 : vote onto the tower. */
1077 :
1078 0 : if( FD_LIKELY( vote_blk ) ) {
1079 0 : *vote_slot = vote_blk->slot;
1080 0 : *vote_block_id = vote_blk->id;
1081 0 : *root_slot = push_vote( tower, vote_blk->slot );
1082 :
1083 : /* Query our tower fork for this slot we're voting for. Note this
1084 : can never be NULL because we record tower forks as we replay, and
1085 : we should never be voting on something we haven't replayed. */
1086 :
1087 0 : fd_tower_blk_t * fork = fd_tower_blocks_query( tower, vote_blk->slot );
1088 0 : fork->voted = 1;
1089 0 : fork->voted_block_id = vote_blk->id;
1090 0 : *vote_bank_hash = fork->bank_hash;
1091 :
1092 : /* Query the root slot's block id from tower forks. This block id
1093 : may not necessarily be confirmed, because confirmation requires
1094 : votes on the block itself (vs. block and its descendants).
1095 :
1096 : So if we have a confirmed block id, we return that. Otherwise
1097 : we return our own vote block id for that slot, which we assume
1098 : is the cluster converged on by the time we're rooting it.
1099 :
1100 : The only way it is possible for us to root the wrong version of
1101 : a block (ie. not the one the cluster confirmed) is if there is
1102 : mass equivocation (>2/3 of threshold check stake has voted for
1103 : two versions of a block). This exceeds the equivocation safety
1104 : threshold and we would eventually detect this via a bank hash
1105 : mismatch and error out. */
1106 :
1107 0 : if( FD_LIKELY( *root_slot!=ULONG_MAX ) ) {
1108 0 : fd_tower_blk_t * root_fork = fd_tower_blocks_query( tower, *root_slot );
1109 0 : *root_block_id = *fd_ptr_if( root_fork->confirmed, &root_fork->confirmed_block_id, &root_fork->voted_block_id );
1110 0 : }
1111 0 : }
1112 :
1113 0 : FD_BASE58_ENCODE_32_BYTES( reset_block_id->uc, reset_block_id_b58 );
1114 0 : FD_BASE58_ENCODE_32_BYTES( vote_block_id->uc, vote_block_id_b58 );
1115 0 : FD_BASE58_ENCODE_32_BYTES( root_block_id->uc, root_block_id_b58 );
1116 0 : FD_LOG_DEBUG(( "[%s] flags: %d. reset_slot: %lu (%s). vote_slot: %lu (%s). root_slot: %lu (%s).", __func__, flags, *reset_slot, reset_block_id_b58, *vote_slot, vote_block_id_b58, *root_slot, root_block_id_b58 ));
1117 0 : return flags;
1118 0 : }
1119 :
1120 : /* fd_tower_reconcile reconciles our local tower with our on-chain tower
1121 : (stored inside our vote account). This function is important in two
1122 : contexts:
1123 :
1124 : ON BOOT
1125 :
1126 : When Firedancer boots up its local tower contains no votes, only a
1127 : root slot set to the snapshot slot. It needs to restore its "latest"
1128 : tower votes and root as of its previous run. This information is
1129 : stored on-chain itself, in a vote account, and Firedancer updates
1130 : vote account states during catchup by replaying blocks since the
1131 : snapshot. Firedancer reconciles its local tower with the on-chain
1132 : one every time it replays a block, and will by definition have its
1133 : "latest" tower once it has caught up.
1134 :
1135 : Note that it is possible Firedancer had voted for a minority fork in
1136 : the previous run. In this case, its true "latest" tower contains
1137 : votes for slots that were pruned by the time of this boot. In theory
1138 : TowerBFT stipulates that lockout can be up to 2^32 slots, but in
1139 : practice slots are pruned once they fall out of the slot hash history
1140 : limit, because they can no longer be canonically verified on-chain.
1141 : Therefore, Firedancer can safely ignore slots that are pruned and
1142 : restore its latest tower on the majority fork as of boot time.
1143 :
1144 : HIGH-AVAILABILITY SETUP
1145 :
1146 : A typical validator setup involves two nodes, a primary and a backup.
1147 : The primary is a valid fee payer, and the one landing votes recording
1148 : the latest state of its tower on-chain. The two nodes' towers will
1149 : usually be identical but occasionally diverge when one node votes
1150 : for slots that the other one doesn't. This usually happens when
1151 : there are multiple forks.
1152 :
1153 : This becomes a problem, because the primary's tower may contain votes
1154 : the backup doesn't have and/or vice versa. The primary's tower is
1155 : the canonical one, since it's the one recorded on-chain, so reconcile
1156 : is a no-op on the primary.
1157 :
1158 : On the backup, reconcile is more involved. Because what's on-chain
1159 : is the primary's tower, there may be slots the backup never actually
1160 : voted for. When the backup node reads back the on-chain tower, some
1161 : metadata, namely `voted` and `voted_block_id`, will be missing from
1162 : its fd_tower instance.
1163 :
1164 : fd_tower_reconcile assumes that if a tower has been recorded on-chain
1165 : then it is safe to assume the vote account registered with the
1166 : currently running Firedancer has in fact at some point voted for the
1167 : slots in that tower.
1168 :
1169 : In case the instance is the backup, it updates the local tower votes,
1170 : root, and metadata structures accordingly with this assumption namely
1171 : by inserting voted_block_id for votes that the backup didn't actually
1172 : vote for but can safely assume the primary did.
1173 :
1174 : This affects the Tower voting rules (see fd_tower_vote_and_reset) in
1175 : that the voted_block_id is used for certain vote and reset decisions.
1176 :
1177 : There are some corner cases to consider related to equivocation:
1178 :
1179 : 2
1180 : / \
1181 : 3 3' (confirmed)
1182 :
1183 : Assume 3 and 3' are alternate blocks for the same slot (3) and have
1184 : different block ids. 3' is the block that eventually gets confirmed.
1185 : Let's consider a scenario in which the primary votes for "3" and the
1186 : backup misses the vote for "3". fd_tower_reconcile needs to backfill
1187 : the voted_block_id for "3" on the backup. However, it's unclear
1188 : whether that vote is for 3 (unconfirmed) or 3' (confirmed), because
1189 : all the on-chain tower contains is the slot "3" (with no block_id).
1190 : How does the backup figure out the voted_block_id?
1191 :
1192 : It turns out it doesn't really matter either way, the backup can just
1193 : backfill with whichever block_id it happened to replay (we know the
1194 : backup has to have replayed either 3 or 3' in order to observe an
1195 : on-chain tower containing 3 in the first place):
1196 :
1197 : If the primary voted for 3 and the backup backfills with 3', we know
1198 : the primary will eventually switch to the DC block (3') via repair.
1199 : So backfilling with 3' is ok because the primary will converge to it.
1200 :
1201 : If the primary voted for 3' and the backup backfills with 3, then the
1202 : backup will similarly eventually switch to the DC block via repair.
1203 : Indeed, it will "freebie" switch in fd_tower_vote_and_reset ie. case
1204 : 1b: "sibling confirmed". Thus, the backup will converge to 3'. */
1205 :
1206 : void
1207 : fd_tower_reconcile( fd_tower_t * tower,
1208 : fd_tower_vote_t * onchain_votes,
1209 0 : ulong onchain_root ) {
1210 :
1211 0 : fd_tower_vote_t * local_votes = tower->votes;
1212 0 : ulong local_root = tower->root;
1213 :
1214 0 : ulong local_vote = fd_tower_vote_empty( local_votes ) ? ULONG_MAX : fd_tower_vote_peek_tail_const( local_votes )->slot;
1215 0 : ulong onchain_vote = fd_tower_vote_empty( onchain_votes ) ? ULONG_MAX : fd_tower_vote_peek_tail_const( onchain_votes )->slot;
1216 :
1217 : /* Cases:
1218 :
1219 : Agave checks Option<onchain_vote> <= Option<local_vote>. Breakdown of Ord<Option<Slot>>:
1220 :
1221 : None, None => True
1222 : None, Some => True
1223 : Some, None => False
1224 : Some, Some => onchain_vote <= local_vote */
1225 :
1226 0 : if( FD_LIKELY( onchain_vote==ULONG_MAX || /* None, None or None, Some */
1227 0 : ( local_vote !=ULONG_MAX && onchain_vote<=local_vote ) /* Some, Some */ ) ) return;
1228 :
1229 : /* On-chain tower is newer, so sync our local tower to the on-chain tower. */
1230 :
1231 0 : FD_LOG_NOTICE(( "[%s] overwriting local tower (last: %lu, root: %lu) with onchain tower (last: %lu, root: %lu)", __func__, local_vote, local_root, onchain_vote, onchain_root ));
1232 :
1233 0 : FD_TEST( local_root!=ULONG_MAX ); /* local root should always be set before fd_tower_reconcile */
1234 0 : if( FD_LIKELY( onchain_root==ULONG_MAX || local_root > onchain_root ) ) {
1235 :
1236 : /* Local root is larger than on-chain root. Overwrite on-chain root
1237 : with local root (this is just a copy, not writing to accdb). */
1238 :
1239 0 : FD_LOG_DEBUG(( "[%s] local_root %lu > onchain_root %lu", __func__, local_root, onchain_root ));
1240 0 : onchain_root = local_root;
1241 :
1242 : /* Drop on-chain votes <= local root. */
1243 :
1244 0 : while( FD_LIKELY( !fd_tower_vote_empty( onchain_votes ) ) ) {
1245 0 : fd_tower_vote_t const * vote = fd_tower_vote_peek_head_const( onchain_votes );
1246 0 : if( FD_LIKELY( vote->slot > local_root ) ) break;
1247 0 : FD_LOG_DEBUG(( "[%s] dropping on-chain vote for slot %lu since it's <= local root %lu", __func__, vote->slot, local_root ));
1248 0 : fd_tower_vote_pop_head( onchain_votes );
1249 0 : }
1250 :
1251 : /* TODO add sanity-check that onchain_root is an ancestor of the
1252 : first vote's ancestor at this point. */
1253 0 : }
1254 :
1255 0 : for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( tower->votes );
1256 0 : !fd_tower_vote_iter_done( tower->votes, iter );
1257 0 : iter = fd_tower_vote_iter_next( tower->votes, iter ) ) {
1258 0 : fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( tower->votes, iter );
1259 0 : fd_tower_blk_t * tower_blk = fd_tower_blocks_query( tower, vote->slot );
1260 0 : FD_TEST( tower_blk ); /* must exist if it's in our tower */
1261 0 : tower_blk->voted = 0;
1262 0 : }
1263 :
1264 : /* Overwrite the root. No-op if local_root > onchain_root. The slots
1265 : below it stay until the caller publishes the root to ghost. */
1266 :
1267 0 : tower->root = onchain_root;
1268 :
1269 : /* Clear out all local_votes. */
1270 :
1271 0 : fd_tower_vote_remove_all( tower->votes );
1272 :
1273 : /* Replace them with onchain_votes. */
1274 :
1275 0 : for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( onchain_votes );
1276 0 : !fd_tower_vote_iter_done( onchain_votes, iter );
1277 0 : iter = fd_tower_vote_iter_next( onchain_votes, iter ) ) {
1278 0 : fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( onchain_votes, iter );
1279 0 : fd_tower_vote_push_tail( tower->votes, *vote );
1280 :
1281 : /* Additionally, backfill voted_block_id for the slots we didn't
1282 : actually vote for. This is intentionally always using the latest
1283 : replayed_block_id if we overwrote it with a second replay. */
1284 :
1285 0 : fd_tower_blk_t * tower_blk = fd_tower_blocks_query( tower, vote->slot );
1286 0 : FD_TEST( tower_blk ); /* must exist because
1287 : 1. all on-chain votes > root slot
1288 : 2. all on-chain votes <= replay slot */
1289 0 : if( FD_UNLIKELY( !tower_blk->voted ) ) {
1290 0 : tower_blk->voted = 1;
1291 0 : tower_blk->voted_block_id = tower_blk->replayed_block_id;
1292 0 : }
1293 0 : }
1294 0 : }
1295 :
1296 : void
1297 : fd_tower_from_vote_acc( fd_tower_vote_t * votes,
1298 : ulong * root,
1299 : uchar const * data,
1300 0 : ulong data_sz ) {
1301 0 : fd_vote_acc_desc_t desc[1];
1302 0 : if( FD_UNLIKELY( !fd_vote_acc_desc( desc, data, data_sz ) ) ) {
1303 0 : *root = ULONG_MAX;
1304 0 : return;
1305 0 : }
1306 0 : *root = desc->root_slot;
1307 0 : for( ulong i=0UL; i<desc->vote_cnt; i++ ) {
1308 0 : fd_tower_vote_t vote = {0};
1309 0 : switch( desc->kind ) {
1310 0 : case FD_VOTE_ACC_V2: {
1311 0 : fd_vote_acc_vote_v2_t const * v = fd_vote_acc_desc_vote( desc, data, i );
1312 0 : vote.slot = v->slot;
1313 0 : vote.conf = v->conf;
1314 0 : break;
1315 0 : }
1316 0 : case FD_VOTE_ACC_V3:
1317 0 : case FD_VOTE_ACC_V4: {
1318 0 : fd_vote_acc_vote_t const * v = fd_vote_acc_desc_vote( desc, data, i );
1319 0 : vote.slot = v->slot;
1320 0 : vote.conf = v->conf;
1321 0 : break;
1322 0 : }
1323 0 : }
1324 0 : fd_tower_vote_push_tail( votes, vote );
1325 0 : }
1326 0 : }
1327 :
1328 : ulong
1329 : fd_tower_with_lat_from_vote_acc( fd_vote_acc_vote_t tower[ static FD_TOWER_VOTE_MAX ],
1330 : uchar const * data,
1331 0 : ulong data_sz ) {
1332 0 : fd_vote_acc_desc_t desc[1];
1333 0 : if( FD_UNLIKELY( !fd_vote_acc_desc( desc, data, data_sz ) ) ) return 0UL;
1334 0 : FD_DCHECK_CRIT( desc->vote_cnt <= FD_TOWER_VOTE_MAX, "invalid vote account" );
1335 0 : switch( desc->kind ) {
1336 0 : case FD_VOTE_ACC_V2: {
1337 0 : for( ulong i=0UL; i<desc->vote_cnt; i++ ) {
1338 0 : fd_vote_acc_vote_v2_t const * v = fd_vote_acc_desc_vote( desc, data, i );
1339 0 : tower[ i ] = (fd_vote_acc_vote_t){
1340 0 : .slot = v->slot,
1341 0 : .conf = v->conf,
1342 0 : .latency = UCHAR_MAX
1343 0 : };
1344 0 : }
1345 0 : break;
1346 0 : }
1347 0 : case FD_VOTE_ACC_V3:
1348 0 : case FD_VOTE_ACC_V4:
1349 0 : fd_memcpy( tower, fd_vote_acc_desc_vote( desc, data, 0UL ), desc->vote_cnt * sizeof(fd_vote_acc_vote_t) );
1350 0 : break;
1351 0 : }
1352 0 : return desc->vote_cnt;
1353 0 : }
1354 :
1355 : void
1356 : fd_tower_to_vote_txn( fd_tower_t const * tower,
1357 : fd_hash_t const * bank_hash,
1358 : fd_hash_t const * block_id,
1359 : fd_hash_t const * recent_blockhash,
1360 : fd_pubkey_t const * validator_identity,
1361 : fd_pubkey_t const * vote_authority,
1362 : fd_pubkey_t const * vote_acc,
1363 0 : fd_txn_p_t * vote_txn ) {
1364 :
1365 0 : FD_TEST( fd_tower_vote_cnt( tower->votes )<=FD_TOWER_VOTE_MAX );
1366 0 : fd_compact_tower_sync_serde_t tower_sync_serde = {
1367 0 : .root = fd_ulong_if( tower->root == ULONG_MAX, 0UL, tower->root ),
1368 0 : .lockouts_cnt = (ushort)fd_tower_vote_cnt( tower->votes ),
1369 : /* .lockouts populated below */
1370 0 : .hash = *bank_hash,
1371 0 : .timestamp_option = 1,
1372 0 : .timestamp = fd_log_wallclock() / (long)1e9, /* seconds */
1373 0 : .block_id = *block_id
1374 0 : };
1375 :
1376 0 : ulong i = 0UL;
1377 0 : ulong prev = tower_sync_serde.root;
1378 0 : for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( tower->votes );
1379 0 : !fd_tower_vote_iter_done( tower->votes, iter );
1380 0 : iter = fd_tower_vote_iter_next( tower->votes, iter ) ) {
1381 0 : fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( tower->votes, iter );
1382 0 : tower_sync_serde.lockouts[i].offset = vote->slot - prev;
1383 0 : tower_sync_serde.lockouts[i].confirmation_count = (uchar)vote->conf;
1384 0 : prev = vote->slot;
1385 0 : i++;
1386 0 : }
1387 :
1388 0 : uchar * txn_out = vote_txn->payload;
1389 0 : uchar * txn_meta_out = vote_txn->_;
1390 :
1391 0 : int same_addr = !memcmp( validator_identity, vote_authority, sizeof(fd_pubkey_t) );
1392 0 : if( FD_LIKELY( same_addr ) ) {
1393 :
1394 : /* 0: validator identity
1395 : 1: vote account address
1396 : 2: vote program */
1397 :
1398 0 : fd_txn_accounts_t votes;
1399 0 : votes.signature_cnt = 1;
1400 0 : votes.readonly_signed_cnt = 0;
1401 0 : votes.readonly_unsigned_cnt = 1;
1402 0 : votes.acct_cnt = 3;
1403 0 : votes.signers_w = validator_identity;
1404 0 : votes.signers_r = NULL;
1405 0 : votes.non_signers_w = vote_acc;
1406 0 : votes.non_signers_r = &fd_solana_vote_program_id;
1407 0 : FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, votes.signature_cnt, &votes, recent_blockhash->uc ) );
1408 :
1409 0 : } else {
1410 :
1411 : /* 0: validator identity
1412 : 1: vote authority
1413 : 2: vote account address
1414 : 3: vote program */
1415 :
1416 0 : fd_txn_accounts_t votes;
1417 0 : votes.signature_cnt = 2;
1418 0 : votes.readonly_signed_cnt = 1;
1419 0 : votes.readonly_unsigned_cnt = 1;
1420 0 : votes.acct_cnt = 4;
1421 0 : votes.signers_w = validator_identity;
1422 0 : votes.signers_r = vote_authority;
1423 0 : votes.non_signers_w = vote_acc;
1424 0 : votes.non_signers_r = &fd_solana_vote_program_id;
1425 0 : FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, votes.signature_cnt, &votes, recent_blockhash->uc ) );
1426 0 : }
1427 :
1428 : /* Add the vote instruction to the transaction. */
1429 :
1430 0 : uchar vote_ix_buf[FD_TXN_MTU];
1431 0 : ulong vote_ix_sz = 0;
1432 0 : FD_STORE( uint, vote_ix_buf, FD_VOTE_IX_KIND_TOWER_SYNC );
1433 0 : FD_TEST( 0==fd_compact_tower_sync_ser( &tower_sync_serde, vote_ix_buf + sizeof(uint), FD_TXN_MTU - sizeof(uint), &vote_ix_sz ) ); // cannot fail if fd_tower_vote_cnt( tower->votes ) <= FD_TOWER_VOTE_MAX
1434 0 : vote_ix_sz += sizeof(uint);
1435 0 : uchar program_id;
1436 0 : uchar ix_accs[2];
1437 0 : if( FD_LIKELY( same_addr ) ) {
1438 0 : ix_accs[0] = 1; /* vote account address */
1439 0 : ix_accs[1] = 0; /* vote authority */
1440 0 : program_id = 2; /* vote program */
1441 0 : } else {
1442 0 : ix_accs[0] = 2; /* vote account address */
1443 0 : ix_accs[1] = 1; /* vote authority */
1444 0 : program_id = 3; /* vote program */
1445 0 : }
1446 0 : vote_txn->payload_sz = (ushort)fd_txn_add_instr( txn_meta_out, txn_out, program_id, ix_accs, 2, vote_ix_buf, vote_ix_sz );
1447 0 : }
1448 :
1449 : int
1450 0 : fd_tower_verify( fd_tower_t const * tower ) {
1451 0 : if( FD_UNLIKELY( fd_tower_vote_cnt( tower->votes )>FD_TOWER_VOTE_MAX ) ) {
1452 0 : FD_LOG_WARNING(( "[%s] invariant violation: cnt %lu > FD_TOWER_VOTE_MAX %lu", __func__, fd_tower_vote_cnt( tower->votes ), (ulong)FD_TOWER_VOTE_MAX ));
1453 0 : return -1;
1454 0 : }
1455 :
1456 0 : fd_tower_vote_t const * prev = NULL;
1457 0 : for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( tower->votes );
1458 0 : !fd_tower_vote_iter_done( tower->votes, iter );
1459 0 : iter = fd_tower_vote_iter_next( tower->votes, iter ) ) {
1460 0 : fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( tower->votes, iter );
1461 0 : if( FD_UNLIKELY( prev && ( vote->slot <= prev->slot || vote->conf >= prev->conf ) ) ) {
1462 0 : FD_LOG_WARNING(( "[%s] invariant violation: vote (slot:%lu conf:%lu) prev (slot:%lu conf:%lu)", __func__, vote->slot, vote->conf, prev->slot, prev->conf ));
1463 0 : return -1;
1464 0 : }
1465 0 : prev = vote;
1466 0 : }
1467 0 : return 0;
1468 0 : }
1469 :
1470 : static void
1471 0 : to_cstr( fd_tower_t const * tower, char * s, ulong len ) {
1472 0 : ulong root = tower->root;
1473 0 : ulong off = 0;
1474 0 : int n;
1475 :
1476 0 : n = snprintf( s + off, len - off, "[Tower]\n\n" );
1477 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
1478 0 : off += (ulong)n;
1479 :
1480 0 : if( FD_UNLIKELY( fd_tower_vote_empty( tower->votes ) ) ) return;
1481 :
1482 0 : ulong max_slot = 0;
1483 :
1484 : /* Determine spacing. */
1485 :
1486 0 : for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init_rev( tower->votes );
1487 0 : !fd_tower_vote_iter_done_rev( tower->votes, iter );
1488 0 : iter = fd_tower_vote_iter_prev ( tower->votes, iter ) ) {
1489 0 : max_slot = fd_ulong_max( max_slot, fd_tower_vote_iter_ele_const( tower->votes, iter )->slot );
1490 0 : }
1491 :
1492 : /* Calculate the number of digits in the maximum slot value. */
1493 :
1494 :
1495 0 : int digit_cnt = (int)fd_ulong_base10_dig_cnt( max_slot );
1496 :
1497 : /* Print the column headers. */
1498 :
1499 0 : if( off < len ) {
1500 0 : n = snprintf( s + off, len - off, "slot%*s | %s\n", digit_cnt - (int)strlen("slot"), "", "confirmation count" );
1501 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
1502 0 : off += (ulong)n;
1503 0 : }
1504 :
1505 : /* Print the divider line. */
1506 :
1507 0 : for( int i = 0; i < digit_cnt && off < len; i++ ) {
1508 0 : s[off++] = '-';
1509 0 : }
1510 0 : if( off < len ) {
1511 0 : n = snprintf( s + off, len - off, " | " );
1512 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
1513 0 : off += (ulong)n;
1514 0 : }
1515 0 : for( ulong i = 0; i < strlen( "confirmation count" ) && off < len; i++ ) {
1516 0 : s[off++] = '-';
1517 0 : }
1518 0 : if( off < len ) {
1519 0 : s[off++] = '\n';
1520 0 : }
1521 :
1522 : /* Print each vote as a table. */
1523 :
1524 0 : for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init_rev( tower->votes );
1525 0 : !fd_tower_vote_iter_done_rev( tower->votes, iter );
1526 0 : iter = fd_tower_vote_iter_prev ( tower->votes, iter ) ) {
1527 0 : fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( tower->votes, iter );
1528 0 : if( off < len ) {
1529 0 : n = snprintf( s + off, len - off, "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf );
1530 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
1531 0 : off += (ulong)n;
1532 0 : }
1533 0 : }
1534 :
1535 0 : if( FD_UNLIKELY( root == ULONG_MAX ) ) {
1536 0 : if( off < len ) {
1537 0 : n = snprintf( s + off, len - off, "%*s | root\n", digit_cnt, "NULL" );
1538 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
1539 0 : off += (ulong)n;
1540 0 : }
1541 0 : } else {
1542 0 : if( off < len ) {
1543 0 : n = snprintf( s + off, len - off, "%*lu | root\n", digit_cnt, root );
1544 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
1545 0 : off += (ulong)n;
1546 0 : }
1547 0 : }
1548 :
1549 : /* Ensure null termination */
1550 0 : if( off < len ) {
1551 0 : s[off] = '\0';
1552 0 : } else {
1553 0 : s[len - 1] = '\0';
1554 0 : }
1555 0 : }
1556 :
1557 : char *
1558 : fd_tower_to_cstr( fd_tower_t const * tower,
1559 0 : char * cstr ) {
1560 0 : to_cstr( tower, cstr, FD_TOWER_CSTR_MIN );
1561 0 : return cstr;
1562 0 : }
1563 :
1564 : void
1565 : fd_tower_count_vote( fd_tower_t * tower,
1566 : fd_pubkey_t const * vote_acc,
1567 : ulong stake,
1568 : uchar const * data,
1569 0 : ulong data_sz ) {
1570 0 : fd_tower_vtr_t * vtr = fd_tower_vtr_push_tail_nocopy( tower->vtrs );
1571 0 : vtr->vote_acc = *vote_acc;
1572 0 : vtr->stake = stake;
1573 0 : fd_tower_vote_remove_all( vtr->votes );
1574 0 : fd_tower_from_vote_acc( vtr->votes, &vtr->root, data, data_sz );
1575 0 : }
1576 :
1577 : /* Block functions ********************************************************/
1578 :
1579 : static int
1580 : is_ancestor( fd_tower_t * tower,
1581 : ulong slot,
1582 0 : ulong ancestor_slot ) {
1583 0 : fd_tower_blk_t * anc = blk_map_ele_query( tower->blk_map, &slot, NULL, tower->blk_pool );
1584 0 : while( FD_LIKELY( anc ) ) {
1585 0 : if( FD_LIKELY( anc->parent_slot == ancestor_slot ) ) return 1;
1586 0 : anc = anc->parent_slot == ULONG_MAX ? NULL : blk_map_ele_query( tower->blk_map, &anc->parent_slot, NULL, tower->blk_pool );
1587 0 : }
1588 0 : return 0;
1589 0 : }
1590 :
1591 : int
1592 : fd_tower_blocks_is_slot_ancestor( fd_tower_t * tower,
1593 : ulong descendant_slot,
1594 0 : ulong ancestor_slot ) {
1595 0 : return is_ancestor( tower, descendant_slot, ancestor_slot );
1596 0 : }
1597 :
1598 : int
1599 : fd_tower_blocks_is_slot_descendant( fd_tower_t * tower,
1600 : ulong ancestor_slot,
1601 0 : ulong descendant_slot ) {
1602 0 : return is_ancestor( tower, descendant_slot, ancestor_slot );
1603 0 : }
1604 :
1605 : ulong
1606 : fd_tower_blocks_lowest_common_ancestor( fd_tower_t * tower,
1607 : ulong slot1,
1608 0 : ulong slot2 ) {
1609 :
1610 0 : fd_tower_blk_t * fork1 = blk_map_ele_query( tower->blk_map, &slot1, NULL, tower->blk_pool );
1611 0 : fd_tower_blk_t * fork2 = blk_map_ele_query( tower->blk_map, &slot2, NULL, tower->blk_pool );
1612 :
1613 0 : if( FD_UNLIKELY( !fork1 )) FD_LOG_CRIT(( "slot1 %lu not found", slot1 ));
1614 0 : if( FD_UNLIKELY( !fork2 )) FD_LOG_CRIT(( "slot2 %lu not found", slot2 ));
1615 :
1616 0 : while( FD_LIKELY( fork1 && fork2 ) ) {
1617 0 : if( FD_UNLIKELY( fork1->slot == fork2->slot ) ) return fork1->slot;
1618 0 : if( fork1->slot > fork2->slot ) fork1 = blk_map_ele_query( tower->blk_map, &fork1->parent_slot, NULL, tower->blk_pool );
1619 0 : else fork2 = blk_map_ele_query( tower->blk_map, &fork2->parent_slot, NULL, tower->blk_pool );
1620 0 : }
1621 :
1622 0 : return ULONG_MAX;
1623 0 : }
1624 :
1625 : fd_hash_t const *
1626 : fd_tower_blocks_canonical_block_id( fd_tower_t * tower,
1627 0 : ulong slot ) {
1628 0 : fd_tower_blk_t * blk = blk_map_ele_query( tower->blk_map, &slot, NULL, tower->blk_pool );
1629 0 : if( FD_UNLIKELY( !blk ) ) return NULL;
1630 0 : return fd_tower_blk_canonical_block_id( blk );
1631 0 : }
1632 :
1633 : fd_tower_blk_t *
1634 0 : fd_tower_blocks_query( fd_tower_t * tower, ulong slot ) {
1635 0 : return blk_map_ele_query( tower->blk_map, &slot, NULL, tower->blk_pool );
1636 0 : }
1637 :
1638 : fd_tower_blk_t *
1639 : fd_tower_blocks_insert( fd_tower_t * tower,
1640 : ulong slot,
1641 0 : ulong parent_slot ) {
1642 0 : FD_TEST( blk_pool_free( tower->blk_pool ) );
1643 0 : fd_tower_blk_t * blk = blk_pool_ele_acquire( tower->blk_pool );
1644 :
1645 0 : memset( blk, 0, sizeof(fd_tower_blk_t) );
1646 0 : blk->parent_slot = parent_slot;
1647 0 : blk->slot = slot;
1648 0 : blk->prev_leader_slot = ULONG_MAX;
1649 0 : blk_map_ele_insert( tower->blk_map, blk, tower->blk_pool );
1650 0 : return blk;
1651 0 : }
1652 :
1653 : void
1654 : fd_tower_blocks_remove( fd_tower_t * tower,
1655 0 : ulong slot ) {
1656 0 : fd_tower_blk_t * blk = blk_map_ele_query( tower->blk_map, &slot, NULL, tower->blk_pool );
1657 0 : if( FD_LIKELY( blk ) ) {
1658 0 : blk_map_ele_remove_fast( tower->blk_map, blk, tower->blk_pool );
1659 0 : blk_pool_ele_release( tower->blk_pool, blk );
1660 0 : }
1661 0 : }
1662 :
1663 : /* Lockos implementation */
1664 :
1665 : void
1666 : fd_tower_lockos_insert( fd_tower_t * tower,
1667 : ulong slot,
1668 : fd_hash_t const * addr,
1669 0 : fd_tower_vote_t * votes ) {
1670 :
1671 0 : lockout_interval_map_t * lck_map = tower->lck_map;
1672 0 : lockout_interval_t * lck_pool = tower->lck_pool;
1673 0 : lockout_slot_map_t * lck_slot_map = tower->lck_slot_map;
1674 0 : lockout_slot_t * lck_slot_pool = tower->lck_slot_pool;
1675 :
1676 0 : ulong vote_cnt = fd_tower_vote_cnt( votes );
1677 0 : uint pubkey_idx = UINT_MAX;
1678 0 : lockout_slot_t * lockout_slot = NULL;
1679 0 : if( FD_LIKELY( vote_cnt ) ) {
1680 0 : FD_CHECK_CRIT( vote_cnt<=UINT_MAX, "tower lockout vote count overflow" );
1681 :
1682 0 : uint slot_key = (uint)slot;
1683 0 : lockout_slot = lockout_slot_map_ele_query( lck_slot_map, &slot_key, NULL, lck_slot_pool );
1684 0 : if( FD_UNLIKELY( !lockout_slot ) ) {
1685 0 : FD_CHECK_CRIT( lockout_slot_pool_free( lck_slot_pool ), "no free entries in tower lockout slot pool" );
1686 0 : lockout_slot = lockout_slot_pool_ele_acquire( lck_slot_pool );
1687 0 : lockout_slot->slot = slot_key;
1688 0 : lockout_slot->end_head = (uint)lockout_interval_pool_idx_null( lck_pool );
1689 0 : FD_CHECK_CRIT( lockout_slot_map_ele_insert( lck_slot_map, lockout_slot, lck_slot_pool ), "unable to insert into tower lockout slot map" );
1690 0 : }
1691 :
1692 0 : lockout_pubkey_ref_t * ref = lockout_pubkey_map_ele_query( tower->lck_pubkey_map, addr, NULL, tower->lck_pubkey_pool );
1693 0 : if( FD_UNLIKELY( !ref ) ) {
1694 0 : FD_CHECK_CRIT( lockout_pubkey_pool_free( tower->lck_pubkey_pool ), "no free entries in tower lockout pubkey pool" );
1695 0 : ref = lockout_pubkey_pool_ele_acquire( tower->lck_pubkey_pool );
1696 0 : ref->addr = *addr;
1697 0 : ref->ref_cnt = 0U;
1698 0 : FD_CHECK_CRIT( lockout_pubkey_map_ele_insert( tower->lck_pubkey_map, ref, tower->lck_pubkey_pool ), "unable to insert into tower lockout pubkey map" );
1699 0 : }
1700 :
1701 0 : ref->ref_cnt += (uint)vote_cnt;
1702 0 : pubkey_idx = (uint)lockout_pubkey_pool_idx( tower->lck_pubkey_pool, ref );
1703 0 : FD_TEST( pubkey_idx<(1U<<LOCKOUT_INTERVAL_PUBKEY_IDX_BITS) );
1704 0 : }
1705 :
1706 0 : ulong slot_idx = vote_cnt ? lockout_slot_pool_idx( lck_slot_pool, lockout_slot ) : ULONG_MAX;
1707 0 : for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( votes );
1708 0 : !fd_tower_vote_iter_done( votes, iter );
1709 0 : iter = fd_tower_vote_iter_next( votes, iter ) ) {
1710 0 : fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( votes, iter );
1711 0 : FD_TEST( vote->conf<=31U );
1712 0 : uint interval_end = (uint)(vote->slot + (1UL << vote->conf));
1713 0 : lockout_interval_key_t key = lockout_interval_key( slot_idx, interval_end, (uint)vote->conf, pubkey_idx );
1714 :
1715 0 : int is_unique_end = !lockout_interval_map_ele_query( lck_map, &key, NULL, lck_pool );
1716 0 : FD_TEST( lockout_interval_pool_free( lck_pool ) );
1717 0 : lockout_interval_t * interval = lockout_interval_pool_ele_acquire( lck_pool );
1718 0 : interval->key = key;
1719 0 : interval->end_next = (uint)lockout_interval_pool_idx_null( lck_pool );
1720 0 : FD_TEST( lockout_interval_map_ele_insert( lck_map, interval, lck_pool ) );
1721 0 : if( is_unique_end ) {
1722 0 : interval->end_next = lockout_slot->end_head;
1723 0 : lockout_slot->end_head = (uint)lockout_interval_pool_idx( lck_pool, interval );
1724 0 : }
1725 0 : }
1726 0 : }
1727 :
1728 : void
1729 : fd_tower_lockos_remove( fd_tower_t * tower,
1730 0 : ulong slot ) {
1731 :
1732 0 : lockout_interval_map_t * lck_map = tower->lck_map;
1733 0 : lockout_interval_t * lck_pool = tower->lck_pool;
1734 0 : lockout_slot_map_t * lck_slot_map = tower->lck_slot_map;
1735 0 : lockout_slot_t * lck_slot_pool = tower->lck_slot_pool;
1736 :
1737 : /* First remove the lockout slot header from the fork slot map */
1738 0 : uint slot_key = (uint)slot;
1739 0 : lockout_slot_t * lockout_slot = lockout_slot_map_ele_remove( lck_slot_map, &slot_key, NULL, lck_slot_pool );
1740 0 : if( FD_UNLIKELY( !lockout_slot ) ) return;
1741 :
1742 : /* Iterate through unique end interval and for each of those, release
1743 : every interval sharing that same end slot. */
1744 0 : uint end_idx = lockout_slot->end_head;
1745 0 : ulong slot_idx = lockout_slot_pool_idx( lck_slot_pool, lockout_slot );
1746 0 : while( end_idx!=lockout_interval_pool_idx_null( lck_pool ) ) {
1747 0 : lockout_interval_t * unique_end = lockout_interval_pool_ele( lck_pool, end_idx );
1748 0 : uint next_end_idx = unique_end->end_next;
1749 0 : uint interval_end = unique_end->key.interval_end;
1750 :
1751 0 : lockout_interval_key_t key = lockout_interval_key( slot_idx, interval_end, 0U, 0UL );
1752 0 : for( lockout_interval_t * itrvl = lockout_interval_map_ele_remove( lck_map, &key, NULL, lck_pool );
1753 0 : !!itrvl;
1754 0 : itrvl = lockout_interval_map_ele_remove( lck_map, &key, NULL, lck_pool ) ) {
1755 0 : lockout_pubkey_ref_t * ref = lockout_pubkey_pool_ele( tower->lck_pubkey_pool, lockout_interval_key_pubkey_idx( &itrvl->key ) );
1756 0 : if( FD_LIKELY( !--ref->ref_cnt ) ) {
1757 0 : FD_CHECK_CRIT( lockout_pubkey_map_ele_remove( tower->lck_pubkey_map, &ref->addr, NULL, tower->lck_pubkey_pool ), "unable to remove tower lockout pubkey" );
1758 0 : lockout_pubkey_pool_ele_release( tower->lck_pubkey_pool, ref );
1759 0 : }
1760 0 : lockout_interval_pool_ele_release( lck_pool, itrvl );
1761 0 : }
1762 0 : end_idx = next_end_idx;
1763 0 : }
1764 0 : lockout_slot_pool_ele_release( lck_slot_pool, lockout_slot );
1765 0 : }
|