Line data Source code
1 : #include "fd_votor_loop.h"
2 :
3 0 : #define BLOCK_REPLAYED (0)
4 0 : #define PARENT_READY (1)
5 0 : #define BLOCK_NOTARIZED (2)
6 0 : #define TIMEOUT (3)
7 0 : #define SAFE_TO_NOTAR (4)
8 0 : #define SAFE_TO_SKIP (5)
9 :
10 : struct vote_event {
11 : uchar reason;
12 : fd_vote_t vote;
13 : };
14 : typedef struct vote_event vote_event_t;
15 :
16 : #define QUEUE_NAME vote_events
17 0 : #define QUEUE_T vote_event_t
18 : #include "../../util/tmpl/fd_queue_dynamic.c"
19 :
20 : struct slot_state_ele {
21 : ulong slot;
22 : ulong next;
23 :
24 : int voted;
25 : int voted_notar;
26 : fd_block_hash_t voted_notar_hash;
27 : int bad_window;
28 : int block_notarized;
29 : fd_block_hash_t block_notarized_hash;
30 : int pending_block;
31 : fd_block_info_t pending_block_info;
32 : int retired;
33 :
34 : long timeout;
35 :
36 : struct { ulong prev; ulong next; } pending_dlist;
37 : struct { ulong prev; ulong next; } timeout_dlist;
38 : };
39 : typedef struct slot_state_ele slot_state_ele_t;
40 :
41 : #define POOL_NAME slot_state_pool
42 0 : #define POOL_T slot_state_ele_t
43 : #include "../../util/tmpl/fd_pool.c"
44 :
45 : #define MAP_NAME slot_state_map
46 : #define MAP_ELE_T slot_state_ele_t
47 0 : #define MAP_KEY slot
48 : #define MAP_KEY_T ulong
49 0 : #define MAP_KEY_EQ(k0,k1) ((*(k0))==(*(k1)))
50 0 : #define MAP_KEY_HASH(key,seed) (fd_ulong_hash((*(key))^(seed)))
51 0 : #define MAP_NEXT next
52 : #include "../../util/tmpl/fd_map_chain.c"
53 :
54 : #define DLIST_NAME pending_dlist
55 : #define DLIST_ELE_T slot_state_ele_t
56 0 : #define DLIST_PREV pending_dlist.prev
57 0 : #define DLIST_NEXT pending_dlist.next
58 : #include "../../util/tmpl/fd_dlist.c"
59 :
60 : #define DLIST_NAME timeout_dlist
61 : #define DLIST_ELE_T slot_state_ele_t
62 0 : #define DLIST_PREV timeout_dlist.prev
63 0 : #define DLIST_NEXT timeout_dlist.next
64 : #include "../../util/tmpl/fd_dlist.c"
65 :
66 : struct slot_states {
67 : slot_state_ele_t * pool;
68 : slot_state_map_t * map;
69 : };
70 : typedef struct slot_states slot_states_t;
71 :
72 : #define SORT_NAME slot_sort
73 0 : #define SORT_KEY_T ulong
74 0 : #define SORT_BEFORE(a,b) ((a)<(b))
75 : #include "../../util/tmpl/fd_sort.c"
76 :
77 : struct epoch_bls_key {
78 : ulong start_slot;
79 : ulong rank;
80 : long ns_per_slot;
81 : uchar bls_key[ FD_BLS_PUB_COMPRESSED_SZ ];
82 : int has_bls_key;
83 : };
84 : typedef struct epoch_bls_key epoch_bls_key_t;
85 :
86 : struct __attribute__((aligned(128UL))) fd_votor_loop {
87 : ulong slot_max;
88 : long now;
89 : ulong root;
90 : ushort shred_version;
91 : long ns_per_slot;
92 :
93 : slot_states_t * slot_states;
94 : fd_parent_ready_tracker_t * parent_ready_tracker; /* Definition 15 over the certs received */
95 : ulong highest_final_slot;
96 : long highest_final_ts; /* when highest_final_slot last rose */
97 : ulong catchup_vote_slot; /* first slot worth a vote while catching up, 0 if none */
98 :
99 : fd_bls_sign_fn bls_sign_fn;
100 : void * bls_sign_ctx;
101 : ulong failover_vote_bound; /* we cast no vote at or below this slot, ULONG_MAX when none */
102 :
103 : epoch_bls_key_t prev_epoch;
104 : epoch_bls_key_t curr_epoch;
105 : epoch_bls_key_t next_epoch;
106 :
107 : vote_event_t * vote_events;
108 : pending_dlist_t * pending_dlist;
109 : timeout_dlist_t * timeout_dlist[ FD_VOTOR_SLOTS_PER_WINDOW ];
110 :
111 : struct {
112 : ulong * slots;
113 : fd_parent_ready_t * parent_readys;
114 : } scratch;
115 : };
116 :
117 : static slot_state_ele_t *
118 : state_mut( fd_votor_loop_t * self,
119 0 : ulong slot ) {
120 0 : slot_state_ele_t * ele = slot_state_map_ele_query( self->slot_states->map, &slot, NULL, self->slot_states->pool );
121 0 : if( FD_LIKELY( ele ) ) return ele;
122 :
123 0 : FD_TEST( slot_state_pool_free( self->slot_states->pool ) );
124 :
125 0 : ele = slot_state_pool_ele_acquire( self->slot_states->pool );
126 0 : fd_memset( ele, 0, sizeof(slot_state_ele_t) );
127 0 : ele->slot = slot;
128 0 : ele->timeout = LONG_MAX;
129 0 : slot_state_map_ele_insert( self->slot_states->map, ele, self->slot_states->pool );
130 0 : return ele;
131 0 : }
132 :
133 : static void
134 : push_vote( fd_votor_loop_t * self,
135 : fd_vote_t vote,
136 0 : uchar reason ) {
137 0 : FD_TEST( !vote_events_full( self->vote_events ) );
138 0 : vote_events_push( self->vote_events, (vote_event_t){ .reason = reason, .vote = vote } );
139 0 : }
140 :
141 : FD_FN_PURE static epoch_bls_key_t const *
142 : own_epoch( fd_votor_loop_t const * self,
143 0 : ulong slot ) {
144 0 : if( FD_UNLIKELY( slot>=self->next_epoch.start_slot ) ) return &self->next_epoch;
145 0 : if( FD_LIKELY( slot>=self->curr_epoch.start_slot ) ) return &self->curr_epoch;
146 0 : return &self->prev_epoch;
147 0 : }
148 :
149 : /* No crashed-leader timeout. Agave tracks one, but with
150 : delta_first_fec_set = delta_block its deadline lands on the first
151 : slot's timeout, so it never skips a window earlier. */
152 :
153 : static void
154 : set_timeouts( fd_votor_loop_t * self,
155 0 : ulong slot ) {
156 0 : FD_TEST( fd_is_start_of_window( slot ) );
157 :
158 0 : long ns_per_slot = own_epoch( self, slot )->ns_per_slot; /* the window's own epoch, not the latest one announced */
159 0 : if( FD_UNLIKELY( !ns_per_slot ) ) ns_per_slot = self->ns_per_slot;
160 :
161 : /* Section 4.1: after DELTA_STANDSTILL without a new finalization, the
162 : skip timeout grows 5% per window past the finalized slot. */
163 :
164 0 : long delta_timeout = FD_VOTOR_DELTA_TIMEOUT_NS;
165 0 : if( FD_UNLIKELY( self->now-self->highest_final_ts>=FD_VOTOR_DELTA_STANDSTILL_NS ) ) {
166 0 : for( ulong w=fd_ulong_sat_sub( slot, self->highest_final_slot )/FD_VOTOR_SLOTS_PER_WINDOW; w && delta_timeout<FD_VOTOR_DELTA_TIMEOUT_MAX_NS; w-- ) delta_timeout += delta_timeout/20L;
167 0 : delta_timeout = fd_long_min( delta_timeout, FD_VOTOR_DELTA_TIMEOUT_MAX_NS );
168 0 : }
169 0 : long deadline = self->now + delta_timeout + ns_per_slot;
170 :
171 0 : for( ulong s=slot; s<slot+FD_VOTOR_SLOTS_PER_WINDOW; s++ ) {
172 0 : deadline += fd_long_if( fd_is_start_of_window( s ), 0L, ns_per_slot );
173 :
174 : /* A slot's deadline is now plus an offset fixed by its position in the
175 : window, so each position's list fills in deadline order and its head
176 : is its earliest timer. The walk back from the tail only runs if the
177 : slot length changed (block duration feature activation), or the clock
178 : stepped back. */
179 :
180 0 : slot_state_ele_t * ele = state_mut( self, s );
181 0 : timeout_dlist_t * list = self->timeout_dlist[ s%FD_VOTOR_SLOTS_PER_WINDOW ];
182 0 : slot_state_ele_t * pool = self->slot_states->pool;
183 0 : if( FD_LIKELY( ele->timeout!=LONG_MAX ) ) {
184 0 : if( FD_LIKELY( ele->timeout<=deadline ) ) continue;
185 0 : timeout_dlist_ele_remove( list, ele, pool );
186 0 : }
187 0 : ele->timeout = deadline;
188 :
189 0 : timeout_dlist_iter_t iter = timeout_dlist_iter_rev_init( list, pool );
190 0 : while( FD_UNLIKELY( !timeout_dlist_iter_done( iter, list, pool ) && timeout_dlist_iter_ele( iter, list, pool )->timeout>deadline ) ) {
191 0 : iter = timeout_dlist_iter_rev_next( iter, list, pool );
192 0 : }
193 0 : if( FD_LIKELY( !timeout_dlist_iter_done( iter, list, pool ) ) ) timeout_dlist_ele_insert_after( list, ele, timeout_dlist_iter_ele( iter, list, pool ), pool );
194 0 : else timeout_dlist_ele_push_head ( list, ele, pool );
195 0 : }
196 0 : }
197 :
198 : static slot_state_ele_t *
199 0 : next_timeout( fd_votor_loop_t const * self ) {
200 0 : slot_state_ele_t * pool = self->slot_states->pool;
201 0 : slot_state_ele_t * head = NULL;
202 0 : for( ulong k=0UL; k<FD_VOTOR_SLOTS_PER_WINDOW; k++ ) {
203 0 : if( FD_LIKELY( timeout_dlist_is_empty( self->timeout_dlist[ k ], pool ) ) ) continue;
204 0 : slot_state_ele_t * ele = timeout_dlist_ele_peek_head( self->timeout_dlist[ k ], pool );
205 0 : if( !head || ele->timeout<head->timeout ) head = ele;
206 0 : }
207 0 : return head;
208 0 : }
209 :
210 : ulong
211 0 : fd_votor_loop_align( void ) {
212 0 : return alignof(fd_votor_loop_t);
213 0 : }
214 :
215 : ulong
216 0 : fd_votor_loop_footprint( ulong slot_max ) {
217 0 : if( FD_UNLIKELY( slot_max<FD_VOTOR_SLOTS_PER_WINDOW ) ) return 0UL;
218 0 : ulong events_max = slot_max*( FD_VOTOR_NOTAR_FALLBACK_CERT_MAX + 1UL /* notar */ + 1UL /* skip */ );
219 0 : ulong slot_state_chain_cnt = slot_state_map_chain_cnt_est( slot_max );
220 0 : ulong ready_max = slot_max + 2UL*FD_VOTOR_SLOTS_PER_WINDOW + 1UL; /* see fd_votor_pool_footprint, plus the window below first_unpruned_slot */
221 0 : return FD_LAYOUT_FINI(
222 0 : FD_LAYOUT_APPEND(
223 0 : FD_LAYOUT_APPEND(
224 0 : FD_LAYOUT_APPEND(
225 0 : FD_LAYOUT_APPEND(
226 0 : FD_LAYOUT_APPEND(
227 0 : FD_LAYOUT_APPEND(
228 0 : FD_LAYOUT_APPEND(
229 0 : FD_LAYOUT_APPEND(
230 0 : FD_LAYOUT_APPEND(
231 0 : FD_LAYOUT_APPEND(
232 0 : FD_LAYOUT_INIT,
233 0 : alignof(fd_votor_loop_t), sizeof(fd_votor_loop_t) ),
234 0 : alignof(slot_states_t), sizeof(slot_states_t) ),
235 0 : slot_state_pool_align(), slot_state_pool_footprint( slot_max ) ),
236 0 : slot_state_map_align(), slot_state_map_footprint ( slot_state_chain_cnt ) ),
237 0 : pending_dlist_align(), pending_dlist_footprint() ),
238 0 : timeout_dlist_align(), timeout_dlist_footprint()*FD_VOTOR_SLOTS_PER_WINDOW ),
239 0 : vote_events_align(), vote_events_footprint( events_max ) ),
240 0 : alignof(ulong), sizeof(ulong)*slot_max ),
241 0 : fd_parent_ready_tracker_align(), fd_parent_ready_tracker_footprint( ready_max ) ),
242 0 : alignof(fd_parent_ready_t), sizeof(fd_parent_ready_t)*slot_max ),
243 0 : fd_votor_loop_align() );
244 0 : }
245 :
246 : void *
247 : fd_votor_loop_new( void * mem,
248 : ulong slot_max,
249 0 : ulong seed ) {
250 0 : if( FD_UNLIKELY( !mem ) ) {
251 0 : FD_LOG_WARNING(( "NULL mem" ));
252 0 : return NULL;
253 0 : }
254 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_votor_loop_align() ) ) ) {
255 0 : FD_LOG_WARNING(( "misaligned mem" ));
256 0 : return NULL;
257 0 : }
258 0 : ulong footprint = fd_votor_loop_footprint( slot_max );
259 0 : if( FD_UNLIKELY( !footprint ) ) {
260 0 : FD_LOG_WARNING(( "bad slot_max (%lu)", slot_max ));
261 0 : return NULL;
262 0 : }
263 0 : fd_memset( mem, 0, footprint );
264 :
265 0 : ulong events_max = slot_max*( FD_VOTOR_NOTAR_FALLBACK_CERT_MAX + 1UL /* notar */ + 1UL /* skip */ );
266 0 : ulong slot_state_chain_cnt = slot_state_map_chain_cnt_est( slot_max );
267 0 : ulong ready_max = slot_max + 2UL*FD_VOTOR_SLOTS_PER_WINDOW + 1UL;
268 :
269 0 : FD_SCRATCH_ALLOC_INIT( l, mem );
270 0 : fd_votor_loop_t * votor = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_votor_loop_t), sizeof(fd_votor_loop_t) );
271 0 : void * slot_states = FD_SCRATCH_ALLOC_APPEND( l, alignof(slot_states_t), sizeof(slot_states_t) );
272 0 : void * slot_state_pool = FD_SCRATCH_ALLOC_APPEND( l, slot_state_pool_align(), slot_state_pool_footprint( slot_max ) );
273 0 : void * slot_state_map = FD_SCRATCH_ALLOC_APPEND( l, slot_state_map_align(), slot_state_map_footprint ( slot_state_chain_cnt ) );
274 0 : void * pending_dlist = FD_SCRATCH_ALLOC_APPEND( l, pending_dlist_align(), pending_dlist_footprint() );
275 0 : uchar * timeout_dlist = FD_SCRATCH_ALLOC_APPEND( l, timeout_dlist_align(), timeout_dlist_footprint()*FD_VOTOR_SLOTS_PER_WINDOW );
276 0 : void * vote_events = FD_SCRATCH_ALLOC_APPEND( l, vote_events_align(), vote_events_footprint( events_max ) );
277 0 : void * slot_scratch = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), sizeof(ulong)*slot_max );
278 0 : void * ready_tracker = FD_SCRATCH_ALLOC_APPEND( l, fd_parent_ready_tracker_align(), fd_parent_ready_tracker_footprint( ready_max ) );
279 0 : void * ready_scratch = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_parent_ready_t), sizeof(fd_parent_ready_t)*slot_max );
280 0 : FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_votor_loop_align() ) == (ulong)mem + footprint );
281 :
282 0 : votor->slot_max = slot_max;
283 0 : votor->now = 0L;
284 0 : votor->root = ULONG_MAX;
285 0 : votor->shred_version = 0;
286 0 : votor->ns_per_slot = 0L;
287 0 : votor->bls_sign_fn = NULL;
288 0 : votor->bls_sign_ctx = NULL;
289 0 : votor->slot_states = (slot_states_t *)slot_states;
290 0 : votor->slot_states->pool = slot_state_pool_join( slot_state_pool_new( slot_state_pool, slot_max ) );
291 0 : votor->slot_states->map = slot_state_map_join ( slot_state_map_new ( slot_state_map, slot_state_chain_cnt, seed ) );
292 0 : votor->highest_final_slot = ULONG_MAX;
293 0 : votor->highest_final_ts = 0L;
294 0 : votor->catchup_vote_slot = 0UL;
295 0 : votor->prev_epoch = (epoch_bls_key_t){ .start_slot = ULONG_MAX, .rank = USHORT_MAX };
296 0 : votor->curr_epoch = (epoch_bls_key_t){ .start_slot = ULONG_MAX, .rank = USHORT_MAX };
297 0 : votor->next_epoch = (epoch_bls_key_t){ .start_slot = ULONG_MAX, .rank = USHORT_MAX };
298 0 : votor->failover_vote_bound = ULONG_MAX;
299 0 : votor->vote_events = vote_events_join( vote_events_new( vote_events, events_max ) );
300 0 : votor->pending_dlist = pending_dlist_join( pending_dlist_new( pending_dlist ) );
301 0 : for( ulong k=0UL; k<FD_VOTOR_SLOTS_PER_WINDOW; k++ ) votor->timeout_dlist[ k ] = timeout_dlist_join( timeout_dlist_new( timeout_dlist+k*timeout_dlist_footprint() ) );
302 0 : votor->scratch.slots = (ulong *)slot_scratch;
303 0 : votor->parent_ready_tracker = fd_parent_ready_tracker_join( fd_parent_ready_tracker_new( ready_tracker, ready_max, seed ) );
304 0 : votor->scratch.parent_readys = (fd_parent_ready_t *)ready_scratch;
305 :
306 0 : return mem;
307 0 : }
308 :
309 : fd_votor_loop_t *
310 0 : fd_votor_loop_join( void * mem ) {
311 0 : fd_votor_loop_t * votor = (fd_votor_loop_t *)mem;
312 0 : if( FD_UNLIKELY( !votor ) ) {
313 0 : FD_LOG_WARNING(( "NULL mem" ));
314 0 : return NULL;
315 0 : }
316 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)votor, fd_votor_loop_align() ) ) ) {
317 0 : FD_LOG_WARNING(( "misaligned mem" ));
318 0 : return NULL;
319 0 : }
320 0 : return votor;
321 0 : }
322 :
323 : void *
324 0 : fd_votor_loop_leave( fd_votor_loop_t const * self ) {
325 0 : if( FD_UNLIKELY( !self ) ) {
326 0 : FD_LOG_WARNING(( "NULL votor" ));
327 0 : return NULL;
328 0 : }
329 0 : return (void *)self;
330 0 : }
331 :
332 : void *
333 0 : fd_votor_loop_delete( void * mem ) {
334 0 : if( FD_UNLIKELY( !mem ) ) {
335 0 : FD_LOG_WARNING(( "NULL mem" ));
336 0 : return NULL;
337 0 : }
338 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_votor_loop_align() ) ) ) {
339 0 : FD_LOG_WARNING(( "misaligned mem" ));
340 0 : return NULL;
341 0 : }
342 0 : return mem;
343 0 : }
344 :
345 : void
346 : fd_votor_loop_init( fd_votor_loop_t * self,
347 : fd_block_id_t const * root,
348 : long now,
349 : long ns_per_slot,
350 : ushort shred_version,
351 : fd_bls_sign_fn sign_fn,
352 0 : void * sign_ctx ) {
353 0 : FD_TEST( sign_fn );
354 0 : self->now = now;
355 0 : self->root = root->slot;
356 0 : self->shred_version = shred_version;
357 0 : self->ns_per_slot = ns_per_slot;
358 0 : self->bls_sign_fn = sign_fn;
359 0 : self->bls_sign_ctx = sign_ctx;
360 :
361 0 : slot_state_ele_t * state = state_mut( self, root->slot );
362 0 : state->voted = 1;
363 0 : state->voted_notar = 1;
364 0 : state->block_notarized = 1;
365 0 : state->retired = 1;
366 0 : memcpy( &state->voted_notar_hash, &root->hash, sizeof(fd_block_hash_t) );
367 0 : memcpy( &state->block_notarized_hash, &root->hash, sizeof(fd_block_hash_t) );
368 :
369 0 : for( ulong s=fd_first_slot_in_window( root->slot ); s<root->slot; s++ ) {
370 0 : slot_state_ele_t * below = state_mut( self, s );
371 0 : below->voted = 1;
372 0 : below->retired = 1;
373 0 : }
374 :
375 0 : self->highest_final_slot = root->slot;
376 0 : self->highest_final_ts = now;
377 :
378 0 : ulong ready_cnt;
379 0 : self->parent_ready_tracker->root = fd_ulong_sat_sub( fd_first_slot_in_window( fd_ulong_sat_sub( root->slot, FD_VOTOR_REWARD_SLOT_DELTA ) ), FD_VOTOR_SLOTS_PER_WINDOW );
380 0 : ready_cnt = fd_parent_ready_tracker_mark_notar_fallback( self->parent_ready_tracker, root, self->scratch.parent_readys );
381 0 : for( ulong i=0UL; i<ready_cnt; i++ ) fd_parent_ready_state_map_ele_query( self->parent_ready_tracker->states.map, &self->scratch.parent_readys[i].slot, NULL, self->parent_ready_tracker->states.pool )->ready = 0;
382 0 : }
383 :
384 : void
385 0 : fd_votor_loop_fini( fd_votor_loop_t * self ) {
386 0 : self->root = ULONG_MAX;
387 0 : self->highest_final_slot = ULONG_MAX;
388 0 : }
389 :
390 : FD_FN_PURE fd_votor_loop_metrics_t
391 0 : fd_votor_loop_metrics( fd_votor_loop_t const * self ) {
392 0 : return (fd_votor_loop_metrics_t){
393 0 : .slot_state_pool_used = slot_state_pool_used( self->slot_states->pool ),
394 0 : .slot_state_pool_free = slot_state_pool_free( self->slot_states->pool ),
395 0 : .highest_final_slot = self->highest_final_slot,
396 0 : .vote_events_cnt = vote_events_cnt( self->vote_events )
397 0 : };
398 0 : }
399 :
400 : FD_FN_PURE static int
401 : is_retired( fd_votor_loop_t const * self,
402 0 : ulong slot ) {
403 0 : slot_state_ele_t const * ele = slot_state_map_ele_query_const( self->slot_states->map, &slot, NULL, self->slot_states->pool );
404 0 : return ele && ele->retired;
405 0 : }
406 :
407 : FD_FN_PURE static int
408 : has_voted( fd_votor_loop_t const * self,
409 0 : ulong slot ) {
410 0 : slot_state_ele_t const * ele = slot_state_map_ele_query_const( self->slot_states->map, &slot, NULL, self->slot_states->pool );
411 0 : return ele && ele->voted;
412 0 : }
413 :
414 : FD_FN_PURE static ulong
415 0 : first_unpruned_slot( fd_votor_loop_t const * self ) {
416 0 : return fd_first_slot_in_window( fd_ulong_sat_sub( self->highest_final_slot, FD_VOTOR_REWARD_SLOT_DELTA ) );
417 0 : }
418 :
419 : /* Returns whether slot is at or below our vote bound. */
420 :
421 : FD_FN_PURE static int
422 : below_failover_vote_bound( fd_votor_loop_t const * self,
423 0 : ulong slot ) {
424 0 : return self->failover_vote_bound!=ULONG_MAX && slot<=self->failover_vote_bound;
425 0 : }
426 :
427 : static int
428 : should_ignore_pool_event( fd_votor_loop_t const * self,
429 0 : fd_votor_pool_event_t const * event ) {
430 0 : ulong slot;
431 0 : switch( event->kind ) {
432 0 : case FD_VOTOR_POOL_EVENT_PARENT_READY: slot = event->parent_ready.slot; break;
433 0 : case FD_VOTOR_POOL_EVENT_SAFE_TO_NOTAR: slot = event->safe_to_notar.slot; break;
434 0 : case FD_VOTOR_POOL_EVENT_SAFE_TO_SKIP: slot = event->safe_to_skip; break;
435 0 : case FD_VOTOR_POOL_EVENT_CERT_CREATED: slot = fd_cert_slot( &event->cert_created ); break;
436 0 : default: FD_LOG_CRIT(( "unreachable" ));
437 0 : }
438 0 : switch( event->kind ) {
439 0 : case FD_VOTOR_POOL_EVENT_CERT_CREATED: return slot<first_unpruned_slot( self );
440 0 : case FD_VOTOR_POOL_EVENT_PARENT_READY:
441 0 : case FD_VOTOR_POOL_EVENT_SAFE_TO_NOTAR:
442 0 : case FD_VOTOR_POOL_EVENT_SAFE_TO_SKIP: return slot<first_unpruned_slot( self ) || is_retired( self, slot );
443 0 : default: FD_LOG_CRIT(( "unreachable" ));
444 0 : }
445 0 : }
446 :
447 : static void
448 : try_final( fd_votor_loop_t * self,
449 : ulong slot,
450 0 : fd_block_hash_t const * hash ) {
451 0 : FD_TEST( slot>=first_unpruned_slot( self ) );
452 0 : if( FD_UNLIKELY( below_failover_vote_bound( self, slot ) ) ) return;
453 0 : epoch_bls_key_t const * epoch = own_epoch( self, slot );
454 :
455 0 : slot_state_ele_t const * state = slot_state_map_ele_query_const( self->slot_states->map, &slot, NULL, self->slot_states->pool );
456 0 : int notarized = state && state->block_notarized && !memcmp( &state->block_notarized_hash, hash, sizeof(fd_block_hash_t) );
457 0 : int voted_notar = state && state->voted_notar && !memcmp( &state->voted_notar_hash, hash, sizeof(fd_block_hash_t) );
458 0 : int not_bad = !( state && state->bad_window );
459 0 : if( FD_LIKELY( notarized && voted_notar && not_bad ) ) {
460 0 : state_mut( self, slot )->retired = 1;
461 0 : if( FD_UNLIKELY( !epoch->has_bls_key || slot<self->catchup_vote_slot ) ) return;
462 0 : fd_vote_t vote = fd_vote_construct_final( self->bls_sign_fn, self->bls_sign_ctx, epoch->bls_key, slot, (ushort)epoch->rank, self->shred_version );
463 0 : push_vote( self, vote, BLOCK_NOTARIZED );
464 0 : }
465 0 : }
466 :
467 : static int
468 : try_notar( fd_votor_loop_t * self,
469 : ulong slot,
470 : fd_block_info_t const * block_info,
471 0 : uchar reason ) {
472 0 : FD_TEST( slot>=first_unpruned_slot( self ) );
473 0 : epoch_bls_key_t const * epoch = own_epoch( self, slot );
474 0 : if( FD_UNLIKELY( !epoch->has_bls_key ) ) return 0;
475 0 : if( FD_UNLIKELY( has_voted( self, slot ) ) ) return 0;
476 0 : if( FD_UNLIKELY( below_failover_vote_bound( self, slot ) ) ) return 0;
477 :
478 0 : fd_block_hash_t hash;
479 0 : memcpy( &hash, &block_info->hash, sizeof(fd_block_hash_t) );
480 0 : fd_block_id_t parent = block_info->parent;
481 :
482 0 : if( FD_UNLIKELY( fd_is_start_of_window( slot ) ) ) {
483 0 : if( FD_UNLIKELY( !fd_parent_ready_tracker_is_parent_ready( self->parent_ready_tracker, slot, &parent ) ) ) return 0;
484 0 : } else {
485 0 : if( FD_UNLIKELY( parent.slot!=slot-1UL ) ) return 0;
486 0 : slot_state_ele_t const * parent_state = slot_state_map_ele_query_const( self->slot_states->map, &parent.slot, NULL, self->slot_states->pool );
487 0 : if( FD_UNLIKELY( !parent_state || !parent_state->voted_notar ) ) return 0;
488 0 : if( FD_UNLIKELY( memcmp( &parent_state->voted_notar_hash, &parent.hash, sizeof(fd_block_hash_t) )!=0 ) ) return 0;
489 0 : }
490 :
491 0 : if( FD_LIKELY( slot>=self->catchup_vote_slot ) ) {
492 0 : fd_vote_t vote = fd_vote_construct_notar( self->bls_sign_fn, self->bls_sign_ctx, epoch->bls_key, slot, &hash, (ushort)epoch->rank, self->shred_version );
493 0 : push_vote( self, vote, reason );
494 0 : }
495 :
496 0 : slot_state_ele_t * state = state_mut( self, slot );
497 0 : if( FD_UNLIKELY( state->pending_block ) ) pending_dlist_ele_remove( self->pending_dlist, state, self->slot_states->pool );
498 0 : state->voted = 1;
499 0 : state->voted_notar = 1;
500 0 : state->pending_block = 0;
501 0 : memcpy( &state->voted_notar_hash, &hash, sizeof(fd_block_hash_t) );
502 :
503 0 : try_final( self, slot, &hash );
504 0 : return 1;
505 0 : }
506 :
507 : static void
508 : try_skip_window( fd_votor_loop_t * self,
509 : ulong slot,
510 0 : uchar reason ) {
511 0 : FD_TEST( slot>=first_unpruned_slot( self ) );
512 :
513 0 : ulong window_start = fd_first_slot_in_window( slot );
514 0 : for( ulong s=window_start; s<window_start+FD_VOTOR_SLOTS_PER_WINDOW; s++ ) {
515 0 : epoch_bls_key_t const * epoch = own_epoch( self, s );
516 0 : if( FD_UNLIKELY( !epoch->has_bls_key ) ) continue;
517 0 : if( FD_UNLIKELY( has_voted( self, s ) ) ) continue;
518 0 : if( FD_UNLIKELY( below_failover_vote_bound( self, s ) ) ) continue;
519 :
520 0 : slot_state_ele_t * state = state_mut( self, s );
521 0 : state->voted = 1;
522 0 : state->bad_window = 1;
523 :
524 0 : if( FD_UNLIKELY( s<self->catchup_vote_slot ) ) continue;
525 :
526 0 : fd_vote_t vote = fd_vote_construct_skip( self->bls_sign_fn, self->bls_sign_ctx, epoch->bls_key, s, (ushort)epoch->rank, self->shred_version );
527 0 : push_vote( self, vote, reason );
528 0 : }
529 0 : }
530 :
531 : static void
532 : check_pending_blocks( fd_votor_loop_t * self,
533 0 : uchar reason ) {
534 0 : slot_state_map_t * map = self->slot_states->map;
535 0 : slot_state_ele_t * pool = self->slot_states->pool;
536 0 : ulong * slots = self->scratch.slots;
537 0 : ulong cnt = 0UL;
538 :
539 0 : for( pending_dlist_iter_t iter = pending_dlist_iter_fwd_init( self->pending_dlist, pool );
540 0 : !pending_dlist_iter_done( iter, self->pending_dlist, pool );
541 0 : iter = pending_dlist_iter_fwd_next( iter, self->pending_dlist, pool ) ) {
542 0 : slot_state_ele_t const * ele = pending_dlist_iter_ele_const( iter, self->pending_dlist, pool );
543 0 : if( FD_LIKELY( cnt<self->slot_max ) ) slots[ cnt++ ] = ele->slot;
544 0 : }
545 0 : slot_sort_inplace( slots, cnt );
546 :
547 0 : for( ulong i=0UL; i<cnt; i++ ) {
548 0 : slot_state_ele_t const * ele = slot_state_map_ele_query_const( map, &slots[i], NULL, pool );
549 0 : if( FD_LIKELY( ele && ele->pending_block ) ) try_notar( self, slots[i], &ele->pending_block_info, reason );
550 0 : }
551 0 : }
552 :
553 : static void
554 0 : prune( fd_votor_loop_t * self ) {
555 0 : ulong first_unpruned = first_unpruned_slot( self );
556 0 : for( ulong slot=self->root; slot<first_unpruned; slot++ ) {
557 0 : slot_state_ele_t * ele = slot_state_map_ele_remove( self->slot_states->map, &slot, NULL, self->slot_states->pool );
558 0 : if( FD_LIKELY( ele ) ) {
559 0 : if( FD_UNLIKELY( ele->pending_block ) ) pending_dlist_ele_remove( self->pending_dlist, ele, self->slot_states->pool );
560 0 : if( FD_LIKELY ( ele->timeout!=LONG_MAX ) ) timeout_dlist_ele_remove( self->timeout_dlist[ slot%FD_VOTOR_SLOTS_PER_WINDOW ], ele, self->slot_states->pool );
561 0 : slot_state_pool_ele_release( self->slot_states->pool, ele );
562 0 : }
563 0 : }
564 0 : self->root = first_unpruned;
565 0 : fd_parent_ready_tracker_prune( self->parent_ready_tracker, fd_ulong_sat_sub( first_unpruned, FD_VOTOR_SLOTS_PER_WINDOW ) ); /* parents of the window at first_unpruned */
566 0 : }
567 :
568 : static fd_cert_t const *
569 : handle_cert_created( fd_votor_loop_t * self,
570 0 : fd_cert_t const * cert ) {
571 0 : ulong slot = fd_cert_slot( cert );
572 :
573 0 : switch( cert->kind ) {
574 :
575 0 : case FD_CERT_KIND_FINAL:
576 0 : case FD_CERT_KIND_FAST_FINAL:
577 0 : if( FD_LIKELY( slot>self->highest_final_slot ) ) {
578 0 : self->highest_final_slot = slot;
579 0 : self->highest_final_ts = self->now;
580 0 : }
581 0 : set_timeouts( self, fd_first_slot_in_window( slot ) );
582 0 : prune( self );
583 0 : break;
584 :
585 0 : case FD_CERT_KIND_NOTAR: {
586 0 : fd_block_hash_t const * hash = fd_cert_block_hash( cert );
587 :
588 0 : slot_state_ele_t * state = state_mut( self, slot );
589 0 : state->block_notarized = 1;
590 0 : memcpy( &state->block_notarized_hash, hash, sizeof(fd_block_hash_t) );
591 :
592 0 : try_final( self, slot, hash );
593 0 : break;
594 0 : }
595 :
596 0 : case FD_CERT_KIND_NOTAR_FALLBACK:
597 0 : case FD_CERT_KIND_SKIP:
598 0 : break;
599 :
600 0 : default:
601 0 : FD_LOG_CRIT(( "unreachable" ));
602 0 : }
603 :
604 0 : return fd_ptr_if( slot>=self->catchup_vote_slot, cert, NULL );
605 0 : }
606 :
607 : void
608 : fd_votor_loop_advance_epoch( fd_votor_loop_t * self,
609 : long ns_per_slot,
610 : ulong epoch_rank,
611 : ulong epoch_slot,
612 0 : uchar const bls_key[ FD_BLS_PUB_COMPRESSED_SZ ] ) {
613 0 : epoch_bls_key_t epoch = { .start_slot = epoch_slot, .rank = epoch_rank, .ns_per_slot = ns_per_slot, .has_bls_key = !!bls_key };
614 0 : if( FD_LIKELY( bls_key ) ) memcpy( epoch.bls_key, bls_key, FD_BLS_PUB_COMPRESSED_SZ );
615 :
616 0 : if( FD_UNLIKELY( self->curr_epoch.start_slot==ULONG_MAX ) ) {
617 0 : self->curr_epoch = epoch;
618 0 : } else if( FD_UNLIKELY( self->next_epoch.start_slot==ULONG_MAX ) ) {
619 0 : self->next_epoch = epoch;
620 0 : } else {
621 0 : self->prev_epoch = self->curr_epoch;
622 0 : self->curr_epoch = self->next_epoch;
623 0 : self->next_epoch = epoch;
624 0 : }
625 0 : }
626 :
627 : void
628 : fd_votor_loop_arm_skip_timeouts( fd_votor_loop_t * self,
629 0 : long now ) {
630 0 : FD_TEST( self->root!=ULONG_MAX );
631 0 : self->now = fd_long_max( self->now, now );
632 0 : self->highest_final_ts = self->now;
633 0 : set_timeouts( self, fd_first_slot_in_window( self->root ) );
634 0 : }
635 :
636 : static epoch_bls_key_t *
637 : epoch_starting_at( fd_votor_loop_t * self,
638 0 : ulong epoch_slot ) {
639 0 : if( epoch_slot==self->prev_epoch.start_slot ) return &self->prev_epoch;
640 0 : if( epoch_slot==self->curr_epoch.start_slot ) return &self->curr_epoch;
641 0 : if( epoch_slot==self->next_epoch.start_slot ) return &self->next_epoch;
642 0 : FD_LOG_CRIT(( "no epoch starts at slot %lu", epoch_slot ));
643 0 : }
644 :
645 : void
646 : fd_votor_loop_set_bls_key( fd_votor_loop_t * self,
647 : ulong epoch_slot,
648 0 : uchar const bls_key[ FD_BLS_PUB_COMPRESSED_SZ ] ) {
649 0 : epoch_bls_key_t * epoch = epoch_starting_at( self, epoch_slot );
650 0 : epoch->has_bls_key = !!bls_key;
651 0 : if( FD_LIKELY( bls_key ) ) memcpy( epoch->bls_key, bls_key, FD_BLS_PUB_COMPRESSED_SZ );
652 0 : if( FD_LIKELY( bls_key ) ) check_pending_blocks( self, BLOCK_REPLAYED );
653 0 : }
654 :
655 : void
656 : fd_votor_loop_update_own_rank( fd_votor_loop_t * self,
657 : ulong epoch_slot,
658 0 : ulong epoch_rank ) {
659 0 : epoch_starting_at( self, epoch_slot )->rank = epoch_rank;
660 0 : }
661 :
662 : fd_cert_t const *
663 : fd_votor_loop_handle_pool_event( fd_votor_loop_t * self,
664 : fd_votor_pool_event_t const * event,
665 0 : long now ) {
666 0 : self->now = now;
667 :
668 : /* Definition 15 over the certs received, a fast-finalization cert is
669 : a notarization cert (Table 6). Ahead of the ignore filter, a cert
670 : just below first_unpruned_slot can still ready its window start. A
671 : window start that gains a ready parent may unblock a pending block. */
672 :
673 0 : if( FD_UNLIKELY( event->kind==FD_VOTOR_POOL_EVENT_CERT_CREATED ) ) {
674 0 : fd_cert_t const * cert = &event->cert_created;
675 0 : ulong ready_cnt = 0UL;
676 0 : switch( cert->kind ) {
677 0 : case FD_CERT_KIND_FINAL: break;
678 0 : case FD_CERT_KIND_FAST_FINAL:
679 0 : case FD_CERT_KIND_NOTAR:
680 0 : case FD_CERT_KIND_NOTAR_FALLBACK: {
681 0 : fd_block_id_t block_id = fd_block_id( fd_cert_slot( cert ), fd_cert_block_hash( cert )->uc );
682 0 : ready_cnt = fd_parent_ready_tracker_mark_notar_fallback( self->parent_ready_tracker, &block_id, self->scratch.parent_readys );
683 0 : break;
684 0 : }
685 0 : case FD_CERT_KIND_SKIP: ready_cnt = fd_parent_ready_tracker_mark_skipped( self->parent_ready_tracker, fd_cert_slot( cert ), self->scratch.parent_readys ); break;
686 0 : default: FD_LOG_CRIT(( "unreachable" ));
687 0 : }
688 0 : for( ulong i=0UL; i<ready_cnt; i++ ) fd_parent_ready_state_map_ele_query( self->parent_ready_tracker->states.map, &self->scratch.parent_readys[i].slot, NULL, self->parent_ready_tracker->states.pool )->ready = 0;
689 0 : if( FD_UNLIKELY( ready_cnt ) ) check_pending_blocks( self, PARENT_READY );
690 0 : }
691 :
692 0 : if( FD_UNLIKELY( should_ignore_pool_event( self, event ) ) ) return NULL;
693 :
694 0 : switch( event->kind ) {
695 :
696 0 : case FD_VOTOR_POOL_EVENT_PARENT_READY: {
697 0 : check_pending_blocks( self, PARENT_READY );
698 0 : set_timeouts( self, event->parent_ready.slot );
699 0 : break;
700 0 : }
701 :
702 0 : case FD_VOTOR_POOL_EVENT_SAFE_TO_NOTAR: {
703 0 : ulong slot = event->safe_to_notar.slot;
704 0 : fd_block_hash_t const * hash = &event->safe_to_notar.hash;
705 : /* No fallback vote at or below the vote bound. */
706 0 : if( FD_UNLIKELY( below_failover_vote_bound( self, slot ) ) ) { try_skip_window( self, slot, SAFE_TO_NOTAR ); break; }
707 0 : epoch_bls_key_t const * epoch = own_epoch( self, slot );
708 0 : if( FD_UNLIKELY( !epoch->has_bls_key ) ) break;
709 0 : if( FD_LIKELY( slot>=self->catchup_vote_slot ) ) {
710 0 : fd_vote_t vote = fd_vote_construct_notar_fallback( self->bls_sign_fn, self->bls_sign_ctx, epoch->bls_key, slot, hash, (ushort)epoch->rank, self->shred_version );
711 0 : push_vote( self, vote, SAFE_TO_NOTAR );
712 0 : }
713 0 : try_skip_window( self, slot, SAFE_TO_NOTAR );
714 0 : state_mut( self, slot )->bad_window = 1;
715 0 : break;
716 0 : }
717 :
718 0 : case FD_VOTOR_POOL_EVENT_SAFE_TO_SKIP: {
719 0 : ulong slot = event->safe_to_skip;
720 : /* No fallback vote at or below the vote bound. */
721 0 : if( FD_UNLIKELY( below_failover_vote_bound( self, slot ) ) ) { try_skip_window( self, slot, SAFE_TO_SKIP ); break; }
722 0 : epoch_bls_key_t const * epoch = own_epoch( self, slot );
723 0 : if( FD_UNLIKELY( !epoch->has_bls_key ) ) break;
724 0 : if( FD_LIKELY( slot>=self->catchup_vote_slot ) ) {
725 0 : fd_vote_t vote = fd_vote_construct_skip_fallback( self->bls_sign_fn, self->bls_sign_ctx, epoch->bls_key, slot, (ushort)epoch->rank, self->shred_version );
726 0 : push_vote( self, vote, SAFE_TO_SKIP );
727 0 : }
728 0 : try_skip_window( self, slot, SAFE_TO_SKIP );
729 0 : state_mut( self, slot )->bad_window = 1;
730 0 : break;
731 0 : }
732 :
733 0 : case FD_VOTOR_POOL_EVENT_CERT_CREATED:
734 0 : return handle_cert_created( self, &event->cert_created );
735 :
736 0 : default:
737 0 : FD_LOG_ERR(( "invalid pool event kind %d", event->kind ));
738 0 : }
739 0 : return NULL;
740 0 : }
741 :
742 : void
743 : fd_votor_loop_process_replay( fd_votor_loop_t * self,
744 : ulong slot,
745 0 : fd_block_info_t const * block_info ) {
746 0 : if( FD_UNLIKELY( slot<first_unpruned_slot( self ) || is_retired( self, slot ) ) ) return;
747 :
748 0 : if( FD_UNLIKELY( has_voted( self, slot ) ) ) {
749 0 : FD_LOG_WARNING(( "not voting for block in slot %lu, already voted", slot ));
750 0 : return;
751 0 : }
752 0 : if( FD_LIKELY( try_notar( self, slot, block_info, BLOCK_REPLAYED ) ) ) {
753 0 : check_pending_blocks( self, BLOCK_REPLAYED );
754 0 : } else {
755 0 : slot_state_ele_t * state = state_mut( self, slot );
756 0 : if( FD_LIKELY( !state->pending_block ) ) pending_dlist_ele_push_tail( self->pending_dlist, state, self->slot_states->pool );
757 0 : state->pending_block = 1;
758 0 : state->pending_block_info = *block_info;
759 0 : }
760 0 : }
761 :
762 : ulong
763 : fd_votor_loop_advance_cert_wmark( fd_votor_loop_t * self,
764 : ulong cert_slot,
765 0 : int final ) {
766 0 : ulong slot = fd_ulong_max( fd_ulong_sat_sub( cert_slot+1UL, self->slot_max ), fd_ulong_if( final, fd_ulong_sat_sub( cert_slot+1UL, FD_VOTOR_REWARD_SLOT_DELTA ), 0UL ) );
767 0 : self->catchup_vote_slot = fd_ulong_max( self->catchup_vote_slot, slot );
768 0 : return self->catchup_vote_slot;
769 0 : }
770 :
771 : void
772 : fd_votor_loop_handle_skip_timeout( fd_votor_loop_t * self,
773 0 : ulong slot ) {
774 0 : if( FD_UNLIKELY( slot<first_unpruned_slot( self ) || is_retired( self, slot ) ) ) return;
775 :
776 0 : if( FD_UNLIKELY( !has_voted( self, slot ) ) ) try_skip_window( self, slot, TIMEOUT );
777 0 : }
778 :
779 : int
780 : fd_votor_loop_poll_skip_timeout( fd_votor_loop_t * self,
781 : long now,
782 0 : ulong * slot ) {
783 0 : self->now = now;
784 :
785 0 : slot_state_ele_t * ele = next_timeout( self );
786 0 : if( FD_LIKELY( !ele || ele->timeout>now ) ) return 0;
787 0 : timeout_dlist_ele_remove( self->timeout_dlist[ ele->slot%FD_VOTOR_SLOTS_PER_WINDOW ], ele, self->slot_states->pool );
788 0 : ele->timeout = LONG_MAX;
789 :
790 0 : *slot = ele->slot;
791 0 : return 1;
792 0 : }
793 :
794 : FD_FN_PURE long
795 0 : fd_votor_loop_next_skip_timeout( fd_votor_loop_t const * self ) {
796 0 : slot_state_ele_t const * head = next_timeout( self );
797 0 : return head ? head->timeout : LONG_MAX;
798 0 : }
799 :
800 : int
801 : fd_votor_loop_poll_vote( fd_votor_loop_t * self,
802 : fd_vote_t * vote,
803 0 : uchar * reason ) {
804 0 : if( FD_LIKELY( vote_events_empty( self->vote_events ) ) ) return 0;
805 0 : vote_event_t event = vote_events_pop( self->vote_events );
806 0 : *vote = event.vote;
807 0 : *reason = event.reason;
808 0 : return 1;
809 0 : }
810 :
811 : /* advance_root raises the finality anchor to slot and prunes below it, if
812 : slot is past the anchor. */
813 :
814 : static void
815 : advance_root( fd_votor_loop_t * self,
816 0 : ulong slot ) {
817 0 : if( FD_UNLIKELY( self->highest_final_slot==ULONG_MAX || slot<=self->highest_final_slot ) ) return;
818 0 : self->highest_final_slot = slot;
819 0 : self->highest_final_ts = self->now;
820 0 : prune( self );
821 0 : }
822 :
823 : void
824 : fd_votor_loop_failover_set_vote_bound( fd_votor_loop_t * self,
825 0 : ulong bound ) {
826 0 : self->failover_vote_bound = self->failover_vote_bound==ULONG_MAX ? bound : fd_ulong_max( self->failover_vote_bound, bound );
827 0 : }
828 :
829 : ulong
830 0 : fd_votor_loop_failover_get_vote_bound( fd_votor_loop_t const * self ) {
831 0 : return self->failover_vote_bound;
832 0 : }
833 :
834 : int
835 : fd_votor_loop_failover_hist_export( fd_votor_loop_t * self,
836 : ulong last_leader_slot,
837 0 : fd_votor_hist_t * out ) {
838 0 : out->last_leader_slot = last_leader_slot;
839 0 : out->vote_bound = self->failover_vote_bound;
840 0 : out->rec_cnt = 0UL;
841 0 : if( FD_UNLIKELY( self->highest_final_slot==ULONG_MAX ) ) {
842 0 : out->anchor = 0UL;
843 0 : return 0;
844 0 : }
845 :
846 0 : slot_state_map_t * map = self->slot_states->map;
847 0 : slot_state_ele_t * pool = self->slot_states->pool;
848 0 : ulong * slots = self->scratch.slots;
849 0 : ulong first = first_unpruned_slot( self );
850 0 : ulong cnt = 0UL;
851 0 : for( slot_state_map_iter_t iter = slot_state_map_iter_init( map, pool );
852 0 : !slot_state_map_iter_done( iter, map, pool );
853 0 : iter = slot_state_map_iter_next( iter, map, pool ) ) {
854 0 : slot_state_ele_t const * ele = slot_state_map_iter_ele_const( iter, map, pool );
855 0 : if( FD_LIKELY( ele->voted && ele->slot>=first && cnt<self->slot_max ) ) slots[ cnt++ ] = ele->slot;
856 0 : }
857 0 : slot_sort_inplace( slots, cnt );
858 :
859 : /* If there are too many voted slots, we drop whole windows from the
860 : bottom and raise the anchor past them. */
861 0 : ulong lo = 0UL;
862 0 : while( cnt-lo>FD_VOTOR_HIST_MAX ) {
863 0 : ulong next_window = fd_first_slot_in_window( slots[ lo ] )+FD_VOTOR_SLOTS_PER_WINDOW;
864 0 : while( lo<cnt && slots[ lo ]<next_window ) lo++;
865 0 : }
866 0 : int truncated = lo>0UL;
867 0 : out->anchor = self->highest_final_slot;
868 0 : if( FD_UNLIKELY( truncated ) ) out->anchor = fd_ulong_max( out->anchor, fd_first_slot_in_window( slots[ lo ] )+FD_VOTOR_REWARD_SLOT_DELTA );
869 :
870 0 : for( ulong i=lo; i<cnt; i++ ) {
871 0 : slot_state_ele_t const * ele = slot_state_map_ele_query_const( map, &slots[ i ], NULL, pool );
872 0 : fd_votor_hist_rec_t * rec = &out->rec[ out->rec_cnt++ ];
873 0 : rec->slot = ele->slot;
874 0 : rec->flags = (uchar)( FD_VOTOR_HIST_FLAG_VOTED |
875 0 : fd_uint_if( ele->voted_notar, FD_VOTOR_HIST_FLAG_VOTED_NOTAR, 0U ) |
876 0 : fd_uint_if( ele->bad_window, FD_VOTOR_HIST_FLAG_BAD_WINDOW, 0U ) |
877 0 : fd_uint_if( ele->retired, FD_VOTOR_HIST_FLAG_RETIRED, 0U ) );
878 0 : if( ele->voted_notar ) fd_memcpy( &rec->notar_hash, &ele->voted_notar_hash, sizeof(fd_block_hash_t) );
879 0 : else fd_memset( &rec->notar_hash, 0, sizeof(fd_block_hash_t) );
880 0 : }
881 0 : return truncated;
882 0 : }
883 :
884 : ulong
885 : fd_votor_loop_failover_hist_adopt( fd_votor_loop_t * self,
886 0 : fd_votor_hist_t const * hist ) {
887 :
888 : /* Dry-run: */
889 :
890 0 : slot_state_map_t * map = self->slot_states->map;
891 0 : slot_state_ele_t * pool = self->slot_states->pool;
892 0 : ulong first = fd_votor_hist_first_slot( fd_ulong_max( self->highest_final_slot, hist->anchor ) );
893 0 : ulong free = slot_state_pool_free( pool );
894 0 : if( hist->anchor>self->highest_final_slot ) {
895 0 : for( slot_state_map_iter_t iter = slot_state_map_iter_init( map, pool );
896 0 : !slot_state_map_iter_done( iter, map, pool );
897 0 : iter = slot_state_map_iter_next( iter, map, pool ) ) {
898 0 : ulong slot = slot_state_map_iter_ele_const( iter, map, pool )->slot;
899 0 : free += slot>=self->root && slot<first;
900 0 : }
901 0 : }
902 : /* Account for exactly what advance_root prunes before changing state. */
903 0 : for( ulong i=0UL; i<hist->rec_cnt; i++ ) {
904 0 : ulong slot = hist->rec[ i ].slot;
905 0 : if( slot<first || slot_state_map_ele_query_const( map, &slot, NULL, pool ) ) continue;
906 0 : if( FD_UNLIKELY( !free ) ) return ULONG_MAX;
907 0 : free--;
908 0 : }
909 :
910 : /* A newer anchor prunes the skip timeouts of the windows below it. */
911 0 : int set_anchor_timeouts = hist->anchor>self->highest_final_slot && next_timeout( self );
912 0 : if( set_anchor_timeouts ) {
913 0 : for( ulong s=fd_first_slot_in_window( hist->anchor ); s<fd_first_slot_in_window( hist->anchor )+FD_VOTOR_SLOTS_PER_WINDOW; s++ ) {
914 0 : if( slot_state_map_ele_query_const( map, &s, NULL, pool ) ) continue;
915 0 : int imported = 0;
916 0 : for( ulong i=0UL; i<hist->rec_cnt; i++ ) imported |= hist->rec[ i ].slot==s;
917 0 : if( imported ) continue;
918 0 : if( FD_UNLIKELY( !free ) ) return ULONG_MAX;
919 0 : free--;
920 0 : }
921 0 : }
922 :
923 : /* Mutation: */
924 :
925 0 : advance_root( self, hist->anchor );
926 0 : if( set_anchor_timeouts ) set_timeouts( self, fd_first_slot_in_window( hist->anchor ) );
927 : /* Later pruning must visit records imported below the startup cursor. */
928 0 : self->root = fd_ulong_min( self->root, first );
929 : /* Take the exporter's vote bound as well. */
930 0 : if( FD_UNLIKELY( hist->vote_bound!=ULONG_MAX ) ) fd_votor_loop_failover_set_vote_bound( self, hist->vote_bound );
931 0 : ulong conflicts = 0UL;
932 0 : for( ulong i=0UL; i<hist->rec_cnt; i++ ) {
933 0 : fd_votor_hist_rec_t const * rec = &hist->rec[ i ];
934 0 : if( FD_UNLIKELY( rec->slot<first ) ) continue;
935 0 : slot_state_ele_t * state = state_mut( self, rec->slot );
936 0 : if( rec->flags & FD_VOTOR_HIST_FLAG_VOTED_NOTAR ) {
937 0 : if( FD_UNLIKELY( state->voted_notar && !fd_memeq( &state->voted_notar_hash, &rec->notar_hash, sizeof(fd_block_hash_t) ) ) ) conflicts++;
938 0 : state->voted_notar = 1;
939 0 : fd_memcpy( &state->voted_notar_hash, &rec->notar_hash, sizeof(fd_block_hash_t) );
940 0 : }
941 0 : if( !state->voted ) {
942 0 : if( FD_UNLIKELY( state->pending_block ) ) pending_dlist_ele_remove( self->pending_dlist, state, self->slot_states->pool );
943 0 : state->pending_block = 0;
944 0 : state->voted = 1;
945 0 : }
946 0 : state->bad_window |= !!( rec->flags & FD_VOTOR_HIST_FLAG_BAD_WINDOW );
947 0 : state->retired |= !!( rec->flags & FD_VOTOR_HIST_FLAG_RETIRED );
948 0 : }
949 0 : return conflicts;
950 0 : }
|