LCOV - code coverage report
Current view: top level - choreo/votor - ag_votor.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 488 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 52 0.0 %

          Line data    Source code
       1             : #include "ag_votor.h"
       2             : 
       3             : struct vote_event {
       4             :   uchar     reason;
       5             :   ag_vote_t vote;
       6             : };
       7             : typedef struct vote_event vote_event_t;
       8             : 
       9             : #define QUEUE_NAME vote_events
      10           0 : #define QUEUE_T    vote_event_t
      11             : #include "../../util/tmpl/fd_queue_dynamic.c"
      12             : 
      13             : struct slot_state_ele {
      14             :   ulong slot;
      15             :   ulong next;
      16             : 
      17             :   int             voted;
      18             :   int             voted_notar;
      19             :   ag_block_hash_t voted_notar_hash;
      20             :   int             bad_window;
      21             :   int             block_notarized;
      22             :   ag_block_hash_t block_notarized_hash;
      23             :   int             pending_block;
      24             :   ag_block_info_t pending_block_info;
      25             :   int             retired;
      26             : 
      27             :   long timeout;
      28             : 
      29             :   struct { ulong prev; ulong next; } pending_dlist;
      30             :   struct { ulong prev; ulong next; } timeout_dlist;
      31             : };
      32             : typedef struct slot_state_ele slot_state_ele_t;
      33             : 
      34             : #define POOL_NAME slot_state_pool
      35           0 : #define POOL_T    slot_state_ele_t
      36             : #include "../../util/tmpl/fd_pool.c"
      37             : 
      38             : #define MAP_NAME               slot_state_map
      39             : #define MAP_ELE_T              slot_state_ele_t
      40           0 : #define MAP_KEY                slot
      41             : #define MAP_KEY_T              ulong
      42           0 : #define MAP_KEY_EQ(k0,k1)      ((*(k0))==(*(k1)))
      43           0 : #define MAP_KEY_HASH(key,seed) (fd_ulong_hash( (*(key)) ^ (seed) ))
      44           0 : #define MAP_NEXT               next
      45             : #include "../../util/tmpl/fd_map_chain.c"
      46             : 
      47             : #define DLIST_NAME  pending_dlist
      48             : #define DLIST_ELE_T slot_state_ele_t
      49           0 : #define DLIST_PREV  pending_dlist.prev
      50           0 : #define DLIST_NEXT  pending_dlist.next
      51             : #include "../../util/tmpl/fd_dlist.c"
      52             : 
      53             : #define DLIST_NAME  timeout_dlist
      54             : #define DLIST_ELE_T slot_state_ele_t
      55           0 : #define DLIST_PREV  timeout_dlist.prev
      56           0 : #define DLIST_NEXT  timeout_dlist.next
      57             : #include "../../util/tmpl/fd_dlist.c"
      58             : 
      59             : struct slot_states {
      60             :   slot_state_ele_t * pool;
      61             :   slot_state_map_t * map;
      62             : };
      63             : typedef struct slot_states slot_states_t;
      64             : 
      65             : #define SORT_NAME        slot_sort
      66           0 : #define SORT_KEY_T       ulong
      67           0 : #define SORT_BEFORE(a,b) ((a)<(b))
      68             : #include "../../util/tmpl/fd_sort.c"
      69             : 
      70             : struct epoch_bls_key {
      71             :   ulong start_slot;
      72             :   ulong rank;
      73             :   long  ns_per_slot;
      74             :   uchar bls_key[ FD_BLS_PUB_COMPRESSED_SZ ];
      75             :   int   has_bls_key;
      76             : };
      77             : typedef struct epoch_bls_key epoch_bls_key_t;
      78             : 
      79             : struct __attribute__((aligned(128UL))) ag_votor {
      80             :   ulong          slot_max;
      81             :   long           now;
      82             :   ulong          root;
      83             :   ushort         shred_version;
      84             :   long           ns_per_slot;
      85             : 
      86             :   slot_states_t *             slot_states;
      87             :   ag_parent_ready_tracker_t * parent_ready_tracker; /* Definition 15 over the certs received */
      88             :   ulong                       highest_final_cert_slot;
      89             :   ulong                       catchup_vote_slot; /* first slot worth a vote while catching up, 0 if none */
      90             : 
      91             :   fd_bls_sign_fn bls_sign_fn;
      92             :   void *         bls_sign_ctx;
      93             : 
      94             :   epoch_bls_key_t prev_epoch;
      95             :   epoch_bls_key_t curr_epoch;
      96             :   epoch_bls_key_t next_epoch;
      97             : 
      98             :   vote_event_t *    vote_events;
      99             :   pending_dlist_t * pending_dlist;
     100             :   timeout_dlist_t * timeout_dlist[ AG_SLOTS_PER_WINDOW ];
     101             : 
     102             :   struct {
     103             :     ulong *             slots;
     104             :     ag_parent_ready_t * parent_readys;
     105             :   } scratch;
     106             : };
     107             : 
     108             : static slot_state_ele_t *
     109             : state_mut( ag_votor_t * self,
     110           0 :            ulong        slot ) {
     111           0 :   slot_state_ele_t * ele = slot_state_map_ele_query( self->slot_states->map, &slot, NULL, self->slot_states->pool );
     112           0 :   if( FD_LIKELY( ele ) ) return ele;
     113             : 
     114           0 :   FD_TEST( slot_state_pool_free( self->slot_states->pool ) );
     115             : 
     116           0 :   ele          = slot_state_pool_ele_acquire( self->slot_states->pool );
     117           0 :   fd_memset( ele, 0, sizeof(slot_state_ele_t) );
     118           0 :   ele->slot    = slot;
     119           0 :   ele->timeout = LONG_MAX;
     120           0 :   slot_state_map_ele_insert( self->slot_states->map, ele, self->slot_states->pool );
     121           0 :   return ele;
     122           0 : }
     123             : 
     124             : static void
     125             : push_vote( ag_votor_t * self,
     126             :            ag_vote_t    vote,
     127           0 :            uchar        reason ) {
     128           0 :   FD_TEST( !vote_events_full( self->vote_events ) );
     129           0 :   vote_events_push( self->vote_events, (vote_event_t){ .reason = reason, .vote = vote } );
     130           0 : }
     131             : 
     132             : FD_FN_PURE static epoch_bls_key_t const *
     133             : own_epoch( ag_votor_t const * self,
     134           0 :            ulong              slot ) {
     135           0 :   if( FD_UNLIKELY( slot>=self->next_epoch.start_slot ) ) return &self->next_epoch;
     136           0 :   if( FD_LIKELY( slot>=self->curr_epoch.start_slot ) ) return &self->curr_epoch;
     137           0 :   return &self->prev_epoch;
     138           0 : }
     139             : 
     140             : /* No crashed-leader timeout.  Agave tracks one, but with
     141             :    delta_first_fec_set = delta_block its deadline lands on the first
     142             :    slot's timeout, so it never skips a window earlier. */
     143             : 
     144             : static void
     145             : set_timeouts( ag_votor_t * self,
     146           0 :               ulong        slot ) {
     147           0 :   FD_TEST( ag_is_start_of_window( slot ) );
     148             : 
     149           0 :   long ns_per_slot = own_epoch( self, slot )->ns_per_slot; /* the window's own epoch, not the latest one announced */
     150           0 :   if( FD_UNLIKELY( !ns_per_slot ) ) ns_per_slot = self->ns_per_slot;
     151           0 :   long deadline = self->now + AG_DELTA_TIMEOUT_NS + ns_per_slot;
     152             : 
     153           0 :   for( ulong s=slot; s<slot+AG_SLOTS_PER_WINDOW; s++ ) {
     154           0 :     deadline += fd_long_if( ag_is_start_of_window( s ), 0L, ns_per_slot );
     155             : 
     156             :     /* A slot's deadline is now plus an offset fixed by its position in the
     157             :        window, so each position's list fills in deadline order and its head
     158             :        is its earliest timer.  The walk back from the tail only runs if the
     159             :        slot length changed (block duration feature activation), or the clock
     160             :        stepped back. */
     161             : 
     162           0 :     slot_state_ele_t * ele  = state_mut( self, s );
     163           0 :     timeout_dlist_t *  list = self->timeout_dlist[ s%AG_SLOTS_PER_WINDOW ];
     164           0 :     slot_state_ele_t * pool = self->slot_states->pool;
     165           0 :     if( FD_LIKELY( ele->timeout!=LONG_MAX ) ) {
     166           0 :       if( FD_LIKELY( ele->timeout<=deadline ) ) continue;
     167           0 :       timeout_dlist_ele_remove( list, ele, pool );
     168           0 :     }
     169           0 :     ele->timeout = deadline;
     170             : 
     171           0 :     timeout_dlist_iter_t iter = timeout_dlist_iter_rev_init( list, pool );
     172           0 :     while( FD_UNLIKELY( !timeout_dlist_iter_done( iter, list, pool ) && timeout_dlist_iter_ele( iter, list, pool )->timeout>deadline ) ) {
     173           0 :       iter = timeout_dlist_iter_rev_next( iter, list, pool );
     174           0 :     }
     175           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 );
     176           0 :     else                                                            timeout_dlist_ele_push_head   ( list, ele, pool );
     177           0 :   }
     178           0 : }
     179             : 
     180             : static slot_state_ele_t *
     181           0 : next_timeout( ag_votor_t const * self ) {
     182           0 :   slot_state_ele_t * pool = self->slot_states->pool;
     183           0 :   slot_state_ele_t * head = NULL;
     184           0 :   for( ulong k=0UL; k<AG_SLOTS_PER_WINDOW; k++ ) {
     185           0 :     if( FD_LIKELY( timeout_dlist_is_empty( self->timeout_dlist[ k ], pool ) ) ) continue;
     186           0 :     slot_state_ele_t * ele = timeout_dlist_ele_peek_head( self->timeout_dlist[ k ], pool );
     187           0 :     if( !head || ele->timeout<head->timeout ) head = ele;
     188           0 :   }
     189           0 :   return head;
     190           0 : }
     191             : 
     192             : ulong
     193           0 : ag_votor_align( void ) {
     194           0 :   return alignof(ag_votor_t);
     195           0 : }
     196             : 
     197             : ulong
     198           0 : ag_votor_footprint( ulong slot_max ) {
     199           0 :   if( FD_UNLIKELY( slot_max<AG_SLOTS_PER_WINDOW ) ) return 0UL;
     200           0 :   ulong events_max = slot_max*( AG_NOTAR_FALLBACK_CERT_MAX + 1UL /* notar */ + 1UL /* skip */ );
     201           0 :   ulong slot_state_chain_cnt = slot_state_map_chain_cnt_est( slot_max );
     202           0 :   ulong ready_max            = slot_max + 2UL*AG_SLOTS_PER_WINDOW + 1UL; /* see ag_pool_footprint, plus the window below first_unpruned_slot */
     203           0 :   return FD_LAYOUT_FINI(
     204           0 :     FD_LAYOUT_APPEND(
     205           0 :     FD_LAYOUT_APPEND(
     206           0 :     FD_LAYOUT_APPEND(
     207           0 :     FD_LAYOUT_APPEND(
     208           0 :     FD_LAYOUT_APPEND(
     209           0 :     FD_LAYOUT_APPEND(
     210           0 :     FD_LAYOUT_APPEND(
     211           0 :     FD_LAYOUT_APPEND(
     212           0 :     FD_LAYOUT_APPEND(
     213           0 :     FD_LAYOUT_APPEND(
     214           0 :     FD_LAYOUT_INIT,
     215           0 :       alignof(ag_votor_t),      sizeof(ag_votor_t)                                ),
     216           0 :       alignof(slot_states_t),   sizeof(slot_states_t)                             ),
     217           0 :       slot_state_pool_align(),  slot_state_pool_footprint( slot_max )             ),
     218           0 :       slot_state_map_align(),   slot_state_map_footprint ( slot_state_chain_cnt ) ),
     219           0 :       pending_dlist_align(),    pending_dlist_footprint()                         ),
     220           0 :       timeout_dlist_align(),    timeout_dlist_footprint()*AG_SLOTS_PER_WINDOW     ),
     221           0 :       vote_events_align(),      vote_events_footprint( events_max )               ),
     222           0 :       alignof(ulong),           sizeof(ulong)*slot_max                            ),
     223           0 :       ag_parent_ready_tracker_align(), ag_parent_ready_tracker_footprint( ready_max ) ),
     224           0 :       alignof(ag_parent_ready_t), sizeof(ag_parent_ready_t)*slot_max              ),
     225           0 :     ag_votor_align() );
     226           0 : }
     227             : 
     228             : void *
     229             : ag_votor_new( void * mem,
     230             :               ulong  slot_max,
     231           0 :               ulong  seed ) {
     232           0 :   if( FD_UNLIKELY( !mem ) ) {
     233           0 :     FD_LOG_WARNING(( "NULL mem" ));
     234           0 :     return NULL;
     235           0 :   }
     236           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, ag_votor_align() ) ) ) {
     237           0 :     FD_LOG_WARNING(( "misaligned mem" ));
     238           0 :     return NULL;
     239           0 :   }
     240           0 :   ulong footprint = ag_votor_footprint( slot_max );
     241           0 :   if( FD_UNLIKELY( !footprint ) ) {
     242           0 :     FD_LOG_WARNING(( "bad slot_max (%lu)", slot_max ));
     243           0 :     return NULL;
     244           0 :   }
     245           0 :   fd_memset( mem, 0, footprint );
     246             : 
     247           0 :   ulong events_max           = slot_max*( AG_NOTAR_FALLBACK_CERT_MAX + 1UL /* notar */ + 1UL /* skip */ );
     248           0 :   ulong slot_state_chain_cnt = slot_state_map_chain_cnt_est( slot_max );
     249           0 :   ulong ready_max            = slot_max + 2UL*AG_SLOTS_PER_WINDOW + 1UL;
     250             : 
     251           0 :   FD_SCRATCH_ALLOC_INIT( l, mem );
     252           0 :   ag_votor_t * votor            = FD_SCRATCH_ALLOC_APPEND( l, alignof(ag_votor_t),      sizeof(ag_votor_t)                                );
     253           0 :   void *       slot_states      = FD_SCRATCH_ALLOC_APPEND( l, alignof(slot_states_t),   sizeof(slot_states_t)                             );
     254           0 :   void *       slot_state_pool  = FD_SCRATCH_ALLOC_APPEND( l, slot_state_pool_align(),  slot_state_pool_footprint( slot_max )             );
     255           0 :   void *       slot_state_map   = FD_SCRATCH_ALLOC_APPEND( l, slot_state_map_align(),   slot_state_map_footprint ( slot_state_chain_cnt ) );
     256           0 :   void *       pending_dlist    = FD_SCRATCH_ALLOC_APPEND( l, pending_dlist_align(),    pending_dlist_footprint()                         );
     257           0 :   uchar *      timeout_dlist    = FD_SCRATCH_ALLOC_APPEND( l, timeout_dlist_align(),    timeout_dlist_footprint()*AG_SLOTS_PER_WINDOW     );
     258           0 :   void *       vote_events      = FD_SCRATCH_ALLOC_APPEND( l, vote_events_align(),      vote_events_footprint( events_max )               );
     259           0 :   void *       slot_scratch     = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong),           sizeof(ulong)*slot_max                            );
     260           0 :   void *       ready_tracker    = FD_SCRATCH_ALLOC_APPEND( l, ag_parent_ready_tracker_align(), ag_parent_ready_tracker_footprint( ready_max ) );
     261           0 :   void *       ready_scratch    = FD_SCRATCH_ALLOC_APPEND( l, alignof(ag_parent_ready_t), sizeof(ag_parent_ready_t)*slot_max              );
     262           0 :   FD_TEST( FD_SCRATCH_ALLOC_FINI( l, ag_votor_align() ) == (ulong)mem + footprint );
     263             : 
     264           0 :   votor->slot_max                = slot_max;
     265           0 :   votor->now                     = 0L;
     266           0 :   votor->root                    = ULONG_MAX;
     267           0 :   votor->shred_version           = 0;
     268           0 :   votor->ns_per_slot             = 0L;
     269           0 :   votor->bls_sign_fn             = NULL;
     270           0 :   votor->bls_sign_ctx            = NULL;
     271           0 :   votor->slot_states             = (slot_states_t *)slot_states;
     272           0 :   votor->slot_states->pool       = slot_state_pool_join( slot_state_pool_new( slot_state_pool, slot_max                  ) );
     273           0 :   votor->slot_states->map        = slot_state_map_join ( slot_state_map_new ( slot_state_map,  slot_state_chain_cnt, seed ) );
     274           0 :   votor->highest_final_cert_slot = ULONG_MAX;
     275           0 :   votor->catchup_vote_slot       = 0UL;
     276           0 :   votor->prev_epoch              = (epoch_bls_key_t){ .start_slot = ULONG_MAX, .rank = USHORT_MAX };
     277           0 :   votor->curr_epoch              = (epoch_bls_key_t){ .start_slot = ULONG_MAX, .rank = USHORT_MAX };
     278           0 :   votor->next_epoch              = (epoch_bls_key_t){ .start_slot = ULONG_MAX, .rank = USHORT_MAX };
     279           0 :   votor->vote_events             = vote_events_join( vote_events_new( vote_events, events_max ) );
     280           0 :   votor->pending_dlist           = pending_dlist_join( pending_dlist_new( pending_dlist ) );
     281           0 :   for( ulong k=0UL; k<AG_SLOTS_PER_WINDOW; k++ ) votor->timeout_dlist[ k ] = timeout_dlist_join( timeout_dlist_new( timeout_dlist+k*timeout_dlist_footprint() ) );
     282           0 :   votor->scratch.slots           = (ulong *)slot_scratch;
     283           0 :   votor->parent_ready_tracker    = ag_parent_ready_tracker_join( ag_parent_ready_tracker_new( ready_tracker, ready_max, seed ) );
     284           0 :   votor->scratch.parent_readys   = (ag_parent_ready_t *)ready_scratch;
     285             : 
     286           0 :   return mem;
     287           0 : }
     288             : 
     289             : ag_votor_t *
     290           0 : ag_votor_join( void * mem ) {
     291           0 :   ag_votor_t * votor = (ag_votor_t *)mem;
     292           0 :   if( FD_UNLIKELY( !votor ) ) {
     293           0 :     FD_LOG_WARNING(( "NULL mem" ));
     294           0 :     return NULL;
     295           0 :   }
     296           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)votor, ag_votor_align() ) ) ) {
     297           0 :     FD_LOG_WARNING(( "misaligned mem" ));
     298           0 :     return NULL;
     299           0 :   }
     300           0 :   return votor;
     301           0 : }
     302             : 
     303             : void *
     304           0 : ag_votor_leave( ag_votor_t const * votor ) {
     305           0 :   if( FD_UNLIKELY( !votor ) ) {
     306           0 :     FD_LOG_WARNING(( "NULL votor" ));
     307           0 :     return NULL;
     308           0 :   }
     309           0 :   return (void *)votor;
     310           0 : }
     311             : 
     312             : void *
     313           0 : ag_votor_delete( void * mem ) {
     314           0 :   if( FD_UNLIKELY( !mem ) ) {
     315           0 :     FD_LOG_WARNING(( "NULL mem" ));
     316           0 :     return NULL;
     317           0 :   }
     318           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, ag_votor_align() ) ) ) {
     319           0 :     FD_LOG_WARNING(( "misaligned mem" ));
     320           0 :     return NULL;
     321           0 :   }
     322           0 :   return mem;
     323           0 : }
     324             : 
     325             : void
     326             : ag_votor_init( ag_votor_t *          self,
     327             :                ag_block_id_t const * root,
     328             :                long                  now,
     329             :                long                  ns_per_slot,
     330             :                ushort                shred_version,
     331             :                fd_bls_sign_fn        sign_fn,
     332           0 :                void *                sign_ctx ) {
     333           0 :   FD_TEST( sign_fn );
     334           0 :   self->now                     = now;
     335           0 :   self->root                    = root->slot;
     336           0 :   self->shred_version           = shred_version;
     337           0 :   self->ns_per_slot             = ns_per_slot;
     338           0 :   self->bls_sign_fn             = sign_fn;
     339           0 :   self->bls_sign_ctx            = sign_ctx;
     340             : 
     341           0 :   slot_state_ele_t * state       = state_mut( self, root->slot );
     342           0 :   state->voted                   = 1;
     343           0 :   state->voted_notar             = 1;
     344           0 :   state->block_notarized         = 1;
     345           0 :   state->retired                 = 1;
     346           0 :   memcpy( state->voted_notar_hash,     root->hash, sizeof(ag_block_hash_t) );
     347           0 :   memcpy( state->block_notarized_hash, root->hash, sizeof(ag_block_hash_t) );
     348             : 
     349           0 :   for( ulong s=ag_first_slot_in_window( root->slot ); s<root->slot; s++ ) {
     350           0 :     slot_state_ele_t * below = state_mut( self, s );
     351           0 :     below->voted               = 1;
     352           0 :     below->retired             = 1;
     353           0 :   }
     354             : 
     355           0 :   self->highest_final_cert_slot = root->slot;
     356             : 
     357           0 :   ulong ready_cnt;
     358           0 :   self->parent_ready_tracker->root = fd_ulong_sat_sub( ag_first_slot_in_window( fd_ulong_sat_sub( root->slot, AG_REWARD_SLOT_DELTA ) ), AG_SLOTS_PER_WINDOW );
     359           0 :   ready_cnt = ag_parent_ready_tracker_mark_notar_fallback( self->parent_ready_tracker, root, self->scratch.parent_readys );
     360           0 :   for( ulong i=0UL; i<ready_cnt; i++ ) ag_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;
     361           0 : }
     362             : 
     363             : void
     364             : ag_votor_arm_skip_timeouts( ag_votor_t * self,
     365           0 :                             long         now ) {
     366           0 :   FD_TEST( self->root!=ULONG_MAX );
     367           0 :   self->now = fd_long_max( self->now, now );
     368           0 :   set_timeouts( self, ag_first_slot_in_window( self->root ) );
     369           0 : }
     370             : 
     371             : void
     372           0 : ag_votor_fini( ag_votor_t * self ) {
     373           0 :   self->root                    = ULONG_MAX;
     374           0 :   self->highest_final_cert_slot = ULONG_MAX;
     375           0 : }
     376             : 
     377             : FD_FN_PURE ag_votor_metrics_t
     378           0 : ag_votor_metrics( ag_votor_t const * self ) {
     379           0 :   return (ag_votor_metrics_t){
     380           0 :     .slot_state_pool_used    = slot_state_pool_used( self->slot_states->pool ),
     381           0 :     .slot_state_pool_free    = slot_state_pool_free( self->slot_states->pool ),
     382           0 :     .highest_final_cert_slot = self->highest_final_cert_slot,
     383           0 :     .vote_events_cnt         = vote_events_cnt( self->vote_events )
     384           0 :   };
     385           0 : }
     386             : 
     387             : FD_FN_PURE static int
     388             : is_retired( ag_votor_t const * self,
     389           0 :             ulong              slot ) {
     390           0 :   slot_state_ele_t const * ele = slot_state_map_ele_query_const( self->slot_states->map, &slot, NULL, self->slot_states->pool );
     391           0 :   return ele && ele->retired;
     392           0 : }
     393             : 
     394             : FD_FN_PURE static int
     395             : has_voted( ag_votor_t const * self,
     396           0 :            ulong              slot ) {
     397           0 :   slot_state_ele_t const * ele = slot_state_map_ele_query_const( self->slot_states->map, &slot, NULL, self->slot_states->pool );
     398           0 :   return ele && ele->voted;
     399           0 : }
     400             : 
     401             : FD_FN_PURE static ulong
     402           0 : first_unpruned_slot( ag_votor_t const * self ) {
     403           0 :   return ag_first_slot_in_window( fd_ulong_sat_sub( self->highest_final_cert_slot, AG_REWARD_SLOT_DELTA ) );
     404           0 : }
     405             : 
     406             : static int
     407             : should_ignore_pool_event( ag_votor_t const *      self,
     408           0 :                           ag_pool_event_t const * event ) {
     409           0 :   ulong slot;
     410           0 :   switch( event->kind ) {
     411           0 :   case AG_POOL_EVENT_PARENT_READY:  slot = event->parent_ready.slot;             break;
     412           0 :   case AG_POOL_EVENT_SAFE_TO_NOTAR: slot = event->safe_to_notar.slot;            break;
     413           0 :   case AG_POOL_EVENT_SAFE_TO_SKIP:  slot = event->safe_to_skip;                  break;
     414           0 :   case AG_POOL_EVENT_CERT_CREATED:  slot = ag_cert_slot( &event->cert_created ); break;
     415           0 :   default:                          FD_LOG_CRIT(( "unreachable" ));
     416           0 :   }
     417           0 :   switch( event->kind ) {
     418           0 :   case AG_POOL_EVENT_CERT_CREATED:  return slot<first_unpruned_slot( self );
     419           0 :   case AG_POOL_EVENT_PARENT_READY:
     420           0 :   case AG_POOL_EVENT_SAFE_TO_NOTAR:
     421           0 :   case AG_POOL_EVENT_SAFE_TO_SKIP:  return slot<first_unpruned_slot( self ) || is_retired( self, slot );
     422           0 :   default:                          FD_LOG_CRIT(( "unreachable" ));
     423           0 :   }
     424           0 : }
     425             : 
     426             : static void
     427             : try_final( ag_votor_t *          self,
     428             :            ulong                 slot,
     429           0 :            ag_block_hash_t const hash ) {
     430           0 :   FD_TEST( slot>=first_unpruned_slot( self ) );
     431           0 :   epoch_bls_key_t const * epoch = own_epoch( self, slot );
     432             : 
     433           0 :   slot_state_ele_t const * state = slot_state_map_ele_query_const( self->slot_states->map, &slot, NULL, self->slot_states->pool );
     434           0 :   int notarized   = state && state->block_notarized && !memcmp( state->block_notarized_hash, hash, sizeof(ag_block_hash_t) );
     435           0 :   int voted_notar = state && state->voted_notar     && !memcmp( state->voted_notar_hash,     hash, sizeof(ag_block_hash_t) );
     436           0 :   int not_bad     = !( state && state->bad_window );
     437           0 :   if( FD_LIKELY( notarized && voted_notar && not_bad ) ) {
     438           0 :     state_mut( self, slot )->retired = 1;
     439           0 :     if( FD_UNLIKELY( !epoch->has_bls_key || slot<self->catchup_vote_slot ) ) return;
     440           0 :     ag_vote_t vote = ag_vote_construct_final( self->bls_sign_fn, self->bls_sign_ctx, epoch->bls_key, slot, (ushort)epoch->rank, self->shred_version );
     441           0 :     push_vote( self, vote, AG_VOTOR_REASON_BLOCK_NOTARIZED );
     442           0 :   }
     443           0 : }
     444             : 
     445             : static int
     446             : try_notar( ag_votor_t *            self,
     447             :            ulong                   slot,
     448             :            ag_block_info_t const * block_info,
     449           0 :            uchar                   reason ) {
     450           0 :   FD_TEST( slot>=first_unpruned_slot( self ) );
     451           0 :   epoch_bls_key_t const * epoch = own_epoch( self, slot );
     452           0 :   if( FD_UNLIKELY( has_voted( self, slot ) ) ) return 0;
     453             : 
     454           0 :   ag_block_hash_t hash;
     455           0 :   memcpy( hash, block_info->hash, sizeof(ag_block_hash_t) );
     456           0 :   ag_block_id_t parent = block_info->parent;
     457             : 
     458           0 :   if( FD_UNLIKELY( ag_is_start_of_window( slot ) ) ) {
     459           0 :     if( FD_UNLIKELY( !ag_parent_ready_tracker_is_parent_ready( self->parent_ready_tracker, slot, &parent ) ) ) return 0;
     460           0 :   } else {
     461           0 :     if( FD_UNLIKELY( parent.slot!=slot-1UL ) ) return 0;
     462           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 );
     463           0 :     if( FD_UNLIKELY( !parent_state || !parent_state->voted_notar                                      ) ) return 0;
     464           0 :     if( FD_UNLIKELY( memcmp( parent_state->voted_notar_hash, parent.hash, sizeof(ag_block_hash_t) )!=0 ) ) return 0;
     465           0 :   }
     466             : 
     467           0 :   if( FD_LIKELY( epoch->has_bls_key && slot>=self->catchup_vote_slot ) ) {
     468           0 :     ag_vote_t vote = ag_vote_construct_notar( self->bls_sign_fn, self->bls_sign_ctx, epoch->bls_key, slot, hash, (ushort)epoch->rank, self->shred_version );
     469           0 :     push_vote( self, vote, reason );
     470           0 :   }
     471             : 
     472           0 :   slot_state_ele_t * state = state_mut( self, slot );
     473           0 :   if( FD_UNLIKELY( state->pending_block ) ) pending_dlist_ele_remove( self->pending_dlist, state, self->slot_states->pool );
     474           0 :   state->voted         = 1;
     475           0 :   state->voted_notar   = 1;
     476           0 :   state->pending_block = 0;
     477           0 :   memcpy( state->voted_notar_hash, hash, sizeof(ag_block_hash_t) );
     478             : 
     479           0 :   try_final( self, slot, hash );
     480           0 :   return 1;
     481           0 : }
     482             : 
     483             : static void
     484             : try_skip_window( ag_votor_t * self,
     485             :                  ulong        slot,
     486           0 :                  uchar        reason ) {
     487           0 :   FD_TEST( slot>=first_unpruned_slot( self ) );
     488             : 
     489           0 :   ulong window_start = ag_first_slot_in_window( slot );
     490           0 :   for( ulong s=window_start; s<window_start+AG_SLOTS_PER_WINDOW; s++ ) {
     491           0 :     if( FD_UNLIKELY( has_voted( self, s ) ) ) continue;
     492             : 
     493           0 :     slot_state_ele_t * state = state_mut( self, s );
     494           0 :     state->voted             = 1;
     495           0 :     state->bad_window        = 1;
     496             : 
     497           0 :     epoch_bls_key_t const * epoch = own_epoch( self, s );
     498           0 :     if( FD_UNLIKELY( !epoch->has_bls_key || s<self->catchup_vote_slot ) ) continue;
     499             : 
     500           0 :     ag_vote_t vote = ag_vote_construct_skip( self->bls_sign_fn, self->bls_sign_ctx, epoch->bls_key, s, (ushort)epoch->rank, self->shred_version );
     501           0 :     push_vote( self, vote, reason );
     502           0 :   }
     503           0 : }
     504             : 
     505             : static void
     506             : check_pending_blocks( ag_votor_t * self,
     507           0 :                       uchar        reason ) {
     508           0 :   slot_state_map_t * map   = self->slot_states->map;
     509           0 :   slot_state_ele_t * pool  = self->slot_states->pool;
     510           0 :   ulong *            slots = self->scratch.slots;
     511           0 :   ulong              cnt   = 0UL;
     512             : 
     513           0 :   for( pending_dlist_iter_t iter = pending_dlist_iter_fwd_init( self->pending_dlist, pool );
     514           0 :                                   !pending_dlist_iter_done( iter, self->pending_dlist, pool );
     515           0 :                             iter = pending_dlist_iter_fwd_next( iter, self->pending_dlist, pool ) ) {
     516           0 :     slot_state_ele_t const * ele = pending_dlist_iter_ele_const( iter, self->pending_dlist, pool );
     517           0 :     if( FD_LIKELY( cnt<self->slot_max ) ) slots[ cnt++ ] = ele->slot;
     518           0 :   }
     519           0 :   slot_sort_inplace( slots, cnt );
     520             : 
     521           0 :   for( ulong i=0UL; i<cnt; i++ ) {
     522           0 :     slot_state_ele_t const * ele = slot_state_map_ele_query_const( map, &slots[i], NULL, pool );
     523           0 :     if( FD_LIKELY( ele && ele->pending_block ) ) try_notar( self, slots[i], &ele->pending_block_info, reason );
     524           0 :   }
     525           0 : }
     526             : 
     527             : static void
     528           0 : prune( ag_votor_t * self ) {
     529           0 :   ulong first_unpruned = first_unpruned_slot( self );
     530           0 :   for( ulong slot=self->root; slot<first_unpruned; slot++ ) {
     531           0 :     slot_state_ele_t * ele = slot_state_map_ele_remove( self->slot_states->map, &slot, NULL, self->slot_states->pool );
     532           0 :     if( FD_LIKELY( ele ) ) {
     533           0 :       if( FD_UNLIKELY( ele->pending_block     ) ) pending_dlist_ele_remove( self->pending_dlist, ele, self->slot_states->pool );
     534           0 :       if( FD_LIKELY  ( ele->timeout!=LONG_MAX ) ) timeout_dlist_ele_remove( self->timeout_dlist[ slot%AG_SLOTS_PER_WINDOW ], ele, self->slot_states->pool );
     535           0 :       slot_state_pool_ele_release( self->slot_states->pool, ele );
     536           0 :     }
     537           0 :   }
     538           0 :   self->root = first_unpruned;
     539           0 :   ag_parent_ready_tracker_prune( self->parent_ready_tracker, fd_ulong_sat_sub( first_unpruned, AG_SLOTS_PER_WINDOW ) ); /* parents of the window at first_unpruned */
     540           0 : }
     541             : 
     542             : static ag_cert_t const *
     543             : handle_cert_created( ag_votor_t *      self,
     544           0 :                      ag_cert_t const * cert ) {
     545           0 :   ulong slot = ag_cert_slot( cert );
     546             : 
     547           0 :   switch( cert->kind ) {
     548             : 
     549           0 :   case AG_CERT_KIND_FINAL:
     550           0 :   case AG_CERT_KIND_FAST_FINAL:
     551           0 :     set_timeouts( self, ag_first_slot_in_window( slot ) );
     552             : 
     553           0 :     self->highest_final_cert_slot = fd_ulong_max( self->highest_final_cert_slot, slot );
     554           0 :     prune( self );
     555           0 :     break;
     556             : 
     557           0 :   case AG_CERT_KIND_NOTAR: {
     558           0 :     uchar const * hash = ag_cert_block_hash( cert );
     559             : 
     560           0 :     slot_state_ele_t * state = state_mut( self, slot );
     561           0 :     state->block_notarized   = 1;
     562           0 :     memcpy( state->block_notarized_hash, hash, sizeof(ag_block_hash_t) );
     563             : 
     564           0 :     try_final( self, slot, hash );
     565           0 :     break;
     566           0 :   }
     567             : 
     568           0 :   case AG_CERT_KIND_NOTAR_FALLBACK:
     569           0 :   case AG_CERT_KIND_SKIP:
     570           0 :     break;
     571             : 
     572           0 :   default:
     573           0 :     FD_LOG_CRIT(( "unreachable" ));
     574           0 :   }
     575             : 
     576           0 :   return fd_ptr_if( slot>=self->catchup_vote_slot, cert, NULL );
     577           0 : }
     578             : 
     579             : void
     580             : ag_votor_advance_epoch( ag_votor_t *       self,
     581             :                         long               ns_per_slot,
     582             :                         ulong              epoch_rank,
     583             :                         ulong              epoch_slot,
     584           0 :                         ag_bls_key_t const bls_key ) {
     585           0 :   epoch_bls_key_t epoch = { .start_slot = epoch_slot, .rank = epoch_rank, .ns_per_slot = ns_per_slot, .has_bls_key = !!bls_key };
     586           0 :   if( FD_LIKELY( bls_key ) ) memcpy( epoch.bls_key, bls_key, FD_BLS_PUB_COMPRESSED_SZ );
     587             : 
     588           0 :   if( FD_UNLIKELY( self->curr_epoch.start_slot==ULONG_MAX ) ) {
     589           0 :     self->curr_epoch = epoch;
     590           0 :   } else if( FD_UNLIKELY( self->next_epoch.start_slot==ULONG_MAX ) ) {
     591           0 :     self->next_epoch = epoch;
     592           0 :   } else {
     593           0 :     self->prev_epoch = self->curr_epoch;
     594           0 :     self->curr_epoch = self->next_epoch;
     595           0 :     self->next_epoch = epoch;
     596           0 :   }
     597           0 : }
     598             : 
     599             : static epoch_bls_key_t *
     600             : epoch_starting_at( ag_votor_t * self,
     601           0 :                    ulong        epoch_slot ) {
     602           0 :   if( epoch_slot==self->prev_epoch.start_slot ) return &self->prev_epoch;
     603           0 :   if( epoch_slot==self->curr_epoch.start_slot ) return &self->curr_epoch;
     604           0 :   if( epoch_slot==self->next_epoch.start_slot ) return &self->next_epoch;
     605           0 :   FD_LOG_CRIT(( "no epoch starts at slot %lu", epoch_slot ));
     606           0 : }
     607             : 
     608             : void
     609             : ag_votor_set_bls_key( ag_votor_t *       self,
     610             :                       ulong              epoch_slot,
     611           0 :                       ag_bls_key_t const bls_key ) {
     612           0 :   epoch_bls_key_t * epoch = epoch_starting_at( self, epoch_slot );
     613           0 :   epoch->has_bls_key = !!bls_key;
     614           0 :   if( FD_LIKELY( bls_key ) ) memcpy( epoch->bls_key, bls_key, FD_BLS_PUB_COMPRESSED_SZ );
     615           0 : }
     616             : 
     617             : void
     618             : ag_votor_set_rank( ag_votor_t * self,
     619             :                    ulong        epoch_slot,
     620           0 :                    ulong        epoch_rank ) {
     621           0 :   epoch_starting_at( self, epoch_slot )->rank = epoch_rank;
     622           0 : }
     623             : 
     624             : ag_cert_t const *
     625             : ag_votor_handle_pool_event( ag_votor_t *            self,
     626             :                             ag_pool_event_t const * event,
     627           0 :                             long                    now ) {
     628           0 :   self->now = now;
     629             : 
     630             :   /* Definition 15 over the certs received, a fast-finalization cert is
     631             :      a notarization cert (Table 6).  Ahead of the ignore filter, a cert
     632             :      just below first_unpruned_slot can still ready its window start.  A
     633             :      window start that gains a ready parent may unblock a pending block. */
     634             : 
     635           0 :   if( FD_UNLIKELY( event->kind==AG_POOL_EVENT_CERT_CREATED ) ) {
     636           0 :     ag_cert_t const * cert      = &event->cert_created;
     637           0 :     ulong             ready_cnt = 0UL;
     638           0 :     switch( cert->kind ) {
     639           0 :     case AG_CERT_KIND_FINAL:          break;
     640           0 :     case AG_CERT_KIND_FAST_FINAL:
     641           0 :     case AG_CERT_KIND_NOTAR:
     642           0 :     case AG_CERT_KIND_NOTAR_FALLBACK: {
     643           0 :       ag_block_id_t block_id = ag_block_id( ag_cert_slot( cert ), ag_cert_block_hash( cert ) );
     644           0 :       ready_cnt = ag_parent_ready_tracker_mark_notar_fallback( self->parent_ready_tracker, &block_id, self->scratch.parent_readys );
     645           0 :       break;
     646           0 :     }
     647           0 :     case AG_CERT_KIND_SKIP:           ready_cnt = ag_parent_ready_tracker_mark_skipped( self->parent_ready_tracker, ag_cert_slot( cert ), self->scratch.parent_readys ); break;
     648           0 :     default:                          FD_LOG_CRIT(( "unreachable" ));
     649           0 :     }
     650           0 :     for( ulong i=0UL; i<ready_cnt; i++ ) ag_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;
     651           0 :     if( FD_UNLIKELY( ready_cnt ) ) check_pending_blocks( self, AG_VOTOR_REASON_PARENT_READY );
     652           0 :   }
     653             : 
     654           0 :   if( FD_UNLIKELY( should_ignore_pool_event( self, event ) ) ) return NULL;
     655             : 
     656           0 :   switch( event->kind ) {
     657             : 
     658           0 :   case AG_POOL_EVENT_PARENT_READY: {
     659           0 :     check_pending_blocks( self, AG_VOTOR_REASON_PARENT_READY );
     660           0 :     set_timeouts( self, event->parent_ready.slot );
     661           0 :     break;
     662           0 :   }
     663             : 
     664           0 :   case AG_POOL_EVENT_SAFE_TO_NOTAR: {
     665           0 :     ulong                   slot  = event->safe_to_notar.slot;
     666           0 :     uchar const *           hash  = event->safe_to_notar.hash;
     667           0 :     epoch_bls_key_t const * epoch = own_epoch( self, slot );
     668           0 :     if( FD_LIKELY( epoch->has_bls_key && slot>=self->catchup_vote_slot ) ) {
     669           0 :       ag_vote_t vote = ag_vote_construct_notar_fallback( self->bls_sign_fn, self->bls_sign_ctx, epoch->bls_key, slot, hash, (ushort)epoch->rank, self->shred_version );
     670           0 :       push_vote( self, vote, AG_VOTOR_REASON_SAFE_TO_NOTAR );
     671           0 :     }
     672           0 :     try_skip_window( self, slot, AG_VOTOR_REASON_SAFE_TO_NOTAR );
     673           0 :     state_mut( self, slot )->bad_window = 1;
     674           0 :     break;
     675           0 :   }
     676             : 
     677           0 :   case AG_POOL_EVENT_SAFE_TO_SKIP: {
     678           0 :     ulong                   slot  = event->safe_to_skip;
     679           0 :     epoch_bls_key_t const * epoch = own_epoch( self, slot );
     680           0 :     if( FD_LIKELY( epoch->has_bls_key && slot>=self->catchup_vote_slot ) ) {
     681           0 :       ag_vote_t vote = ag_vote_construct_skip_fallback( self->bls_sign_fn, self->bls_sign_ctx, epoch->bls_key, slot, (ushort)epoch->rank, self->shred_version );
     682           0 :       push_vote( self, vote, AG_VOTOR_REASON_SAFE_TO_SKIP );
     683           0 :     }
     684           0 :     try_skip_window( self, slot, AG_VOTOR_REASON_SAFE_TO_SKIP );
     685           0 :     state_mut( self, slot )->bad_window = 1;
     686           0 :     break;
     687           0 :   }
     688             : 
     689           0 :   case AG_POOL_EVENT_CERT_CREATED:
     690           0 :     return handle_cert_created( self, &event->cert_created );
     691             : 
     692           0 :   default:
     693           0 :     FD_LOG_ERR(( "invalid pool event kind %d", event->kind ));
     694           0 :   }
     695           0 :   return NULL;
     696           0 : }
     697             : 
     698             : void
     699             : ag_votor_process_replay( ag_votor_t *            self,
     700             :                          ulong                   slot,
     701           0 :                          ag_block_info_t const * block_info ) {
     702           0 :   if( FD_UNLIKELY( slot<first_unpruned_slot( self ) || is_retired( self, slot ) ) ) return;
     703             : 
     704           0 :   if( FD_UNLIKELY( has_voted( self, slot ) ) ) {
     705           0 :     FD_LOG_WARNING(( "not voting for block in slot %lu, already voted", slot ));
     706           0 :     return;
     707           0 :   }
     708           0 :   if( FD_LIKELY( try_notar( self, slot, block_info, AG_VOTOR_REASON_BLOCK_REPLAYED ) ) ) {
     709           0 :     check_pending_blocks( self, AG_VOTOR_REASON_BLOCK_REPLAYED );
     710           0 :   } else {
     711           0 :     slot_state_ele_t * state  = state_mut( self, slot );
     712           0 :     if( FD_LIKELY( !state->pending_block ) ) pending_dlist_ele_push_tail( self->pending_dlist, state, self->slot_states->pool );
     713           0 :     state->pending_block      = 1;
     714           0 :     state->pending_block_info = *block_info;
     715           0 :   }
     716           0 : }
     717             : 
     718             : void
     719             : ag_votor_set_catchup_vote_slot( ag_votor_t * self,
     720           0 :                                 ulong        slot ) {
     721           0 :   self->catchup_vote_slot = fd_ulong_max( self->catchup_vote_slot, slot );
     722           0 : }
     723             : 
     724             : void
     725             : ag_votor_handle_skip_timeout( ag_votor_t * self,
     726           0 :                               ulong        slot ) {
     727           0 :   if( FD_UNLIKELY( slot<first_unpruned_slot( self ) || is_retired( self, slot ) ) ) return;
     728             : 
     729           0 :   if( FD_UNLIKELY( !has_voted( self, slot ) ) ) try_skip_window( self, slot, AG_VOTOR_REASON_TIMEOUT );
     730           0 : }
     731             : 
     732             : int
     733             : ag_votor_poll_skip_timeout( ag_votor_t * self,
     734             :                             long         now,
     735           0 :                             ulong *      slot ) {
     736           0 :   self->now = now;
     737             : 
     738           0 :   slot_state_ele_t * ele = next_timeout( self );
     739           0 :   if( FD_LIKELY( !ele || ele->timeout>now ) ) return 0;
     740           0 :   timeout_dlist_ele_remove( self->timeout_dlist[ ele->slot%AG_SLOTS_PER_WINDOW ], ele, self->slot_states->pool );
     741           0 :   ele->timeout = LONG_MAX;
     742             : 
     743           0 :   *slot = ele->slot;
     744           0 :   return 1;
     745           0 : }
     746             : 
     747             : FD_FN_PURE long
     748           0 : ag_votor_next_skip_timeout( ag_votor_t const * self ) {
     749           0 :   slot_state_ele_t const * head = next_timeout( self );
     750           0 :   return head ? head->timeout : LONG_MAX;
     751           0 : }
     752             : 
     753             : int
     754             : ag_votor_poll_vote( ag_votor_t * self,
     755             :                     ag_vote_t *  vote,
     756           0 :                     uchar *      reason ) {
     757           0 :   if( FD_LIKELY( vote_events_empty( self->vote_events ) ) ) return 0;
     758           0 :   vote_event_t event = vote_events_pop( self->vote_events );
     759           0 :   *vote   = event.vote;
     760           0 :   *reason = event.reason;
     761           0 :   return 1;
     762           0 : }
     763             : 

Generated by: LCOV version 1.14