LCOV - code coverage report
Current view: top level - choreo/votor - fd_votor_loop.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 628 0.0 %
Date: 2026-11-11 12:40:43 Functions: 0 78 0.0 %

          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 : }

Generated by: LCOV version 1.14