LCOV - code coverage report
Current view: top level - choreo/hfork - fd_hfork.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 232 0.0 %
Date: 2026-03-19 18:19:27 Functions: 0 10 0.0 %

          Line data    Source code
       1             : #include "fd_hfork.h"
       2             : #include "fd_hfork_private.h"
       3             : 
       4             : static void
       5             : check( fd_hfork_t *  hfork,
       6             :        ulong         total_stake,
       7             :        candidate_t * candidate,
       8             :        int           dead,
       9           0 :        fd_hash_t *   our_bank_hash ) {
      10             : 
      11           0 :   if( FD_LIKELY( candidate->checked ) ) return; /* already checked this bank hash against our own */
      12           0 :   double pct = (double)candidate->stake * 100.0 / (double)total_stake;
      13           0 :   if( FD_LIKELY( pct < 52.0 ) ) return; /* not enough stake to compare */
      14             : 
      15           0 :   if( FD_UNLIKELY( dead ) ) {
      16           0 :     char msg[ 4096UL ];
      17           0 :     FD_BASE58_ENCODE_32_BYTES( candidate->key.block_id.uc, _block_id );
      18           0 :     FD_TEST( fd_cstr_printf_check( msg, sizeof( msg ), NULL,
      19           0 :                                   "HARD FORK DETECTED: our validator has marked slot %lu with block ID `%s` dead, but %lu validators with %.1f of stake have voted on it",
      20           0 :                                   candidate->slot,
      21           0 :                                   _block_id,
      22           0 :                                   candidate->cnt,
      23           0 :                                   pct ) );
      24             : 
      25           0 :     if( FD_UNLIKELY( hfork->fatal ) ) FD_LOG_ERR    (( "%s", msg ));
      26           0 :     else                              FD_LOG_WARNING(( "%s", msg ));
      27           0 :   } else if( FD_UNLIKELY( 0!=memcmp( our_bank_hash, &candidate->key.bank_hash, 32UL ) ) ) {
      28           0 :     char msg[ 4096UL ];
      29           0 :     FD_BASE58_ENCODE_32_BYTES( our_bank_hash->uc, _our_bank_hash );
      30           0 :     FD_BASE58_ENCODE_32_BYTES( candidate->key.block_id.uc, _block_id );
      31           0 :     FD_BASE58_ENCODE_32_BYTES( candidate->key.bank_hash.uc, _bank_hash );
      32           0 :     FD_TEST( fd_cstr_printf_check( msg, sizeof( msg ), NULL,
      33           0 :                                   "HARD FORK DETECTED: our validator has produced bank hash `%s` for slot %lu with block ID `%s`, but %lu validators with %.1f of stake have voted on a different bank hash `%s` for the same slot",
      34           0 :                                   _our_bank_hash,
      35           0 :                                   candidate->slot,
      36           0 :                                   _block_id,
      37           0 :                                   candidate->cnt,
      38           0 :                                   pct,
      39           0 :                                   _bank_hash ) );
      40             : 
      41           0 :     if( FD_UNLIKELY( hfork->fatal ) ) FD_LOG_ERR    (( "%s", msg ));
      42           0 :     else                              FD_LOG_WARNING(( "%s", msg ));
      43           0 :   }
      44           0 :   candidate->checked = 1;
      45           0 : }
      46             : 
      47             : ulong
      48           0 : fd_hfork_align( void ) {
      49           0 :   return 128UL;
      50           0 : }
      51             : 
      52             : ulong
      53             : fd_hfork_footprint( ulong max_live_slots,
      54           0 :                     ulong max_vote_accounts ) {
      55           0 :   ulong fork_max   = max_live_slots * max_vote_accounts;
      56           0 :   int   lg_blk_max = fd_ulong_find_msb( fd_ulong_pow2_up( fork_max ) ) + 1;
      57           0 :   int   lg_vtr_max = fd_ulong_find_msb( fd_ulong_pow2_up( max_vote_accounts ) ) + 1;
      58             : 
      59           0 :   ulong l = FD_LAYOUT_INIT;
      60           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_hfork_t),   sizeof(fd_hfork_t)                    );
      61           0 :   l = FD_LAYOUT_APPEND( l, blk_map_align(),       blk_map_footprint( lg_blk_max )       );
      62           0 :   l = FD_LAYOUT_APPEND( l, vtr_map_align(),       vtr_map_footprint( lg_vtr_max )       );
      63           0 :   l = FD_LAYOUT_APPEND( l, candidate_map_align(), candidate_map_footprint( lg_blk_max ) );
      64           0 :   l = FD_LAYOUT_APPEND( l, bank_hash_pool_align(), bank_hash_pool_footprint( fork_max ) );
      65           0 :   for( ulong i = 0UL; i < fd_ulong_pow2( lg_vtr_max ); i++ ) {
      66           0 :     l = FD_LAYOUT_APPEND( l, votes_align(), votes_footprint( max_live_slots ) );
      67           0 :   }
      68           0 :   return FD_LAYOUT_FINI( l, fd_hfork_align() );
      69           0 : }
      70             : 
      71             : void *
      72             : fd_hfork_new( void * shmem,
      73             :               ulong  max_live_slots,
      74             :               ulong  max_vote_accounts,
      75             :               ulong  seed,
      76           0 :               int    fatal ) {
      77             : 
      78           0 :   if( FD_UNLIKELY( !shmem ) ) {
      79           0 :     FD_LOG_WARNING(( "NULL mem" ));
      80           0 :     return NULL;
      81           0 :   }
      82             : 
      83           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_hfork_align() ) ) ) {
      84           0 :     FD_LOG_WARNING(( "misaligned mem" ));
      85           0 :     return NULL;
      86           0 :   }
      87             : 
      88           0 :   ulong footprint = fd_hfork_footprint( max_live_slots, max_vote_accounts );
      89           0 :   if( FD_UNLIKELY( !footprint ) ) {
      90           0 :     FD_LOG_WARNING(( "bad max_live_slots (%lu) or max_vote_accounts (%lu)", max_live_slots, max_vote_accounts ));
      91           0 :     return NULL;
      92           0 :   }
      93             : 
      94           0 :   fd_memset( shmem, 0, footprint );
      95             : 
      96           0 :   ulong fork_max   = max_live_slots * max_vote_accounts;
      97           0 :   int   lg_blk_max = fd_ulong_find_msb( fd_ulong_pow2_up( fork_max ) ) + 1;
      98           0 :   int   lg_vtr_max = fd_ulong_find_msb( fd_ulong_pow2_up( max_vote_accounts ) ) + 1;
      99             : 
     100           0 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     101           0 :   fd_hfork_t * hfork          = FD_SCRATCH_ALLOC_APPEND( l, fd_hfork_align(),       sizeof( fd_hfork_t )                  );
     102           0 :   void *       blk_map        = FD_SCRATCH_ALLOC_APPEND( l, blk_map_align(),        blk_map_footprint( lg_blk_max )       );
     103           0 :   void *       vtr_map        = FD_SCRATCH_ALLOC_APPEND( l, vtr_map_align(),        vtr_map_footprint( lg_vtr_max )       );
     104           0 :   void *       candidate_map  = FD_SCRATCH_ALLOC_APPEND( l, candidate_map_align(),  candidate_map_footprint( lg_blk_max ) );
     105           0 :   void *       bank_hash_pool = FD_SCRATCH_ALLOC_APPEND( l, bank_hash_pool_align(), bank_hash_pool_footprint( fork_max )  );
     106             : 
     107           0 :   hfork->blk_map        = blk_map_new( blk_map, lg_blk_max, seed );
     108           0 :   hfork->vtr_map        = vtr_map_new( vtr_map, lg_vtr_max, seed );
     109           0 :   hfork->candidate_map  = candidate_map_new( candidate_map, lg_blk_max, seed );
     110           0 :   hfork->bank_hash_pool = bank_hash_pool_new( bank_hash_pool, fork_max );
     111             : 
     112           0 :   vtr_t * vtr_map_ = vtr_map_join( hfork->vtr_map );
     113           0 :   for( ulong i = 0UL; i < fd_ulong_pow2( lg_vtr_max ); i++ ) {
     114           0 :     void * votes      = FD_SCRATCH_ALLOC_APPEND( l, votes_align(), votes_footprint( max_live_slots ) );
     115           0 :     vtr_map_[i].votes = votes_new( votes, max_live_slots );
     116           0 :   }
     117           0 :   FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_hfork_align() ) == (ulong)shmem + footprint );
     118           0 :   hfork->fatal = fatal;
     119           0 :   return shmem;
     120           0 : }
     121             : 
     122             : fd_hfork_t *
     123           0 : fd_hfork_join( void * shhfork ) {
     124           0 :   fd_hfork_t * hfork = (fd_hfork_t *)shhfork;
     125             : 
     126           0 :   if( FD_UNLIKELY( !hfork ) ) {
     127           0 :     FD_LOG_WARNING(( "NULL hfork" ));
     128           0 :     return NULL;
     129           0 :   }
     130             : 
     131           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)hfork, fd_hfork_align() ) ) ) {
     132           0 :     FD_LOG_WARNING(( "misaligned hfork" ));
     133           0 :     return NULL;
     134           0 :   }
     135             : 
     136           0 :   hfork->blk_map        = blk_map_join( hfork->blk_map );
     137           0 :   hfork->vtr_map        = vtr_map_join( hfork->vtr_map );
     138           0 :   hfork->candidate_map  = candidate_map_join( hfork->candidate_map );
     139           0 :   hfork->bank_hash_pool = bank_hash_pool_join( hfork->bank_hash_pool );
     140           0 :   for( ulong i = 0UL; i < vtr_map_slot_cnt( hfork->vtr_map ); i++ ) {
     141           0 :     hfork->vtr_map[i].votes = votes_join( hfork->vtr_map[i].votes );
     142           0 :   }
     143             : 
     144           0 :   return hfork;
     145           0 : }
     146             : 
     147             : void *
     148           0 : fd_hfork_leave( fd_hfork_t const * hfork ) {
     149             : 
     150           0 :   if( FD_UNLIKELY( !hfork ) ) {
     151           0 :     FD_LOG_WARNING(( "NULL hfork" ));
     152           0 :     return NULL;
     153           0 :   }
     154             : 
     155           0 :   return (void *)hfork;
     156           0 : }
     157             : 
     158             : void *
     159           0 : fd_hfork_delete( void * hfork ) {
     160             : 
     161           0 :   if( FD_UNLIKELY( !hfork ) ) {
     162           0 :     FD_LOG_WARNING(( "NULL hfork" ));
     163           0 :     return NULL;
     164           0 :   }
     165             : 
     166           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)hfork, fd_hfork_align() ) ) ) {
     167           0 :     FD_LOG_WARNING(( "misaligned hfork" ));
     168           0 :     return NULL;
     169           0 :   }
     170             : 
     171           0 :   return hfork;
     172           0 : }
     173             : 
     174             : void
     175           0 : remove( blk_t * blk, fd_hash_t * bank_hash, bank_hash_t * pool ) {
     176           0 :   bank_hash_t * prev = NULL;
     177           0 :   bank_hash_t * curr = blk->bank_hashes;
     178           0 :   while( FD_LIKELY( curr ) ) {
     179           0 :     if( FD_LIKELY( 0==memcmp( &curr->bank_hash, bank_hash, 32UL ) ) ) break;
     180           0 :     prev = curr;
     181           0 :     curr = bank_hash_pool_ele( pool, curr->next );
     182           0 :   }
     183           0 :   FD_TEST( curr ); /* assumes bank_hash in blk->bank_hashes */
     184             : 
     185             :   /* In most cases, there is only one bank_hash per blk, so it will be
     186             :      the first element in blk->bank_hashes and prev will be NULL. */
     187             : 
     188           0 :   if( FD_LIKELY( !prev ) ) blk->bank_hashes = bank_hash_pool_ele( pool, curr->next );
     189           0 :   else                     prev->next       = curr->next;
     190           0 :   bank_hash_pool_ele_release( pool, curr );
     191           0 : }
     192             : 
     193             : void
     194             : fd_hfork_count_vote( fd_hfork_t *         hfork,
     195             :                      fd_hfork_metrics_t * metrics,
     196             :                      fd_hash_t const *    vote_acc,
     197             :                      fd_hash_t const *    block_id,
     198             :                      fd_hash_t const *    bank_hash,
     199             :                      ulong                slot,
     200             :                      ulong                stake,
     201           0 :                      ulong                total_stake ) {
     202             : 
     203             :   /* Get the vtr. */
     204             : 
     205           0 :   vtr_t * vtr = vtr_map_query( hfork->vtr_map, *vote_acc, NULL );
     206           0 :   if( FD_UNLIKELY( !vtr ) ) {
     207           0 :     FD_TEST( vtr_map_key_cnt( hfork->vtr_map ) < vtr_map_key_max( hfork->vtr_map ) );
     208           0 :     vtr = vtr_map_insert( hfork->vtr_map, *vote_acc );
     209           0 :     votes_remove_all( vtr->votes );
     210           0 :   }
     211             : 
     212             :   /* Only process newer votes (by vote slot) from a given voter. */
     213             : 
     214           0 :   if( FD_UNLIKELY( !votes_empty( vtr->votes ) && votes_peek_tail_const( vtr->votes )->slot >= slot ) ) return;
     215             : 
     216             :   /* Evict the candidate's oldest vote (by vote slot). */
     217             : 
     218           0 :   if( FD_UNLIKELY( votes_full( vtr->votes ) ) ) {
     219           0 :     vote_t          vote      = votes_pop_head( vtr->votes );
     220           0 :     candidate_key_t key       = { .block_id = vote.block_id, .bank_hash = vote.bank_hash };
     221           0 :     candidate_t *   candidate = candidate_map_query( hfork->candidate_map, key, NULL );
     222           0 :     candidate->stake -= vote.stake;
     223           0 :     candidate->cnt--;
     224           0 :     if( FD_UNLIKELY( candidate->cnt==0 ) ) {
     225           0 :       candidate_map_remove( hfork->candidate_map, candidate );
     226           0 :       blk_t * blk = blk_map_query( hfork->blk_map, vote.block_id, NULL );
     227           0 :       FD_TEST( blk ); /* asumes if this is in candidate_map, it must also be in blk_map */
     228           0 :       remove( blk, &vote.bank_hash, hfork->bank_hash_pool );
     229           0 :       if( FD_UNLIKELY( !blk->bank_hashes ) ) {
     230           0 :         blk_map_remove( hfork->blk_map, blk );
     231           0 :         if( FD_UNLIKELY( blk->forked ) ) {
     232           0 :           metrics->active--;
     233           0 :           metrics->pruned++;
     234           0 :         }
     235           0 :       }
     236           0 :     }
     237           0 :   }
     238             : 
     239             :   /* Push the vote onto the vtr. */
     240             : 
     241           0 :   vote_t vote = { .block_id = *block_id, .bank_hash = *bank_hash, .slot = slot, .stake = stake };
     242           0 :   vtr->votes  = votes_push_tail( vtr->votes, vote );
     243             : 
     244             :   /* Update the hard fork candidate for this block id. */
     245             : 
     246           0 :   candidate_key_t key       = { .block_id = *block_id, .bank_hash = *bank_hash };
     247           0 :   candidate_t *   candidate = candidate_map_query( hfork->candidate_map, key, NULL );
     248           0 :   if( FD_UNLIKELY( !candidate ) ) {
     249           0 :     candidate          = candidate_map_insert( hfork->candidate_map, key );
     250           0 :     candidate->slot    = slot;
     251           0 :     candidate->stake   = 0UL;
     252           0 :     candidate->cnt     = 0UL;
     253           0 :     candidate->checked = 0;
     254           0 :   }
     255           0 :   candidate->cnt++;
     256           0 :   candidate->stake += stake;
     257             : 
     258             :   /* Update the list of bank hashes for this block_id. */
     259             : 
     260           0 :   blk_t * blk = blk_map_query( hfork->blk_map, *block_id, NULL );
     261           0 :   if( FD_UNLIKELY( !blk ) ) {
     262           0 :     FD_TEST( blk_map_key_cnt( hfork->blk_map ) < blk_map_key_max( hfork->blk_map ) ); /* invariant violation: blk_map full */
     263           0 :     blk              = blk_map_insert( hfork->blk_map, *block_id );
     264           0 :     blk->dead        = 0;
     265           0 :     blk->forked      = 0;
     266           0 :     blk->replayed    = 0;
     267           0 :     blk->bank_hashes = NULL;
     268           0 :   }
     269           0 :   int           found = 0;
     270           0 :   ulong         cnt   = 0;
     271           0 :   bank_hash_t * prev  = NULL;
     272           0 :   bank_hash_t * curr  = blk->bank_hashes;
     273           0 :   while( FD_LIKELY( curr ) ) {
     274           0 :     if( FD_LIKELY( 0==memcmp( curr, bank_hash, 32UL ) ) ) found = 1;
     275           0 :     prev = curr;
     276           0 :     curr = bank_hash_pool_ele( hfork->bank_hash_pool, curr->next );
     277           0 :     cnt++;
     278           0 :   }
     279           0 :   if( FD_UNLIKELY( !found ) ) {
     280           0 :     FD_TEST( bank_hash_pool_free( hfork->bank_hash_pool ) );
     281           0 :     bank_hash_t * ele = bank_hash_pool_ele_acquire( hfork->bank_hash_pool );
     282           0 :     ele->bank_hash    = *bank_hash;
     283           0 :     ele->next         = bank_hash_pool_idx_null( hfork->bank_hash_pool );
     284           0 :     if( FD_LIKELY( !prev ) ) blk->bank_hashes = ele;
     285           0 :     else {
     286           0 :       prev->next  = bank_hash_pool_idx( hfork->bank_hash_pool, ele );
     287           0 :       blk->forked = 1;
     288           0 :       metrics->seen++;
     289           0 :       metrics->active++;
     290           0 :     }
     291           0 :     cnt++;
     292           0 :   }
     293           0 :   metrics->max_width = fd_ulong_max( metrics->max_width, cnt );
     294             : 
     295             :   /* Check for hard forks. */
     296             : 
     297           0 :   if( FD_LIKELY( blk->replayed ) ) check( hfork, total_stake, candidate, blk->dead, &blk->our_bank_hash );
     298           0 : }
     299             : 
     300             : void
     301             : fd_hfork_record_our_bank_hash( fd_hfork_t * hfork,
     302             :                                fd_hash_t  * block_id,
     303             :                                fd_hash_t  * bank_hash,
     304           0 :                                ulong        total_stake ) {
     305           0 :   blk_t * blk = blk_map_query( hfork->blk_map, *block_id, NULL );
     306           0 :   if( FD_UNLIKELY( !blk ) ) {
     307           0 :     blk              = blk_map_insert( hfork->blk_map, *block_id );
     308             :     /* blk->dead set later */
     309           0 :     blk->forked      = 0;
     310             :     /* blk->replayed set later */
     311             :     /* blk->our_bank_hash set later */
     312           0 :     blk->bank_hashes = NULL;
     313           0 :   }
     314           0 :   if( FD_LIKELY( bank_hash ) ) { blk->dead = 0; blk->replayed = 1; blk->our_bank_hash = *bank_hash; }
     315           0 :   else                         { blk->dead = 1; blk->replayed = 0; /* our_bank_hash undefined */    }
     316             : 
     317           0 :   bank_hash_t * curr  = blk->bank_hashes;
     318           0 :   while( FD_LIKELY( curr ) ) {
     319           0 :     candidate_key_t key       = { .block_id = *block_id, .bank_hash = curr->bank_hash };
     320           0 :     candidate_t *   candidate = candidate_map_query( hfork->candidate_map, key, NULL );
     321           0 :     if( FD_LIKELY( candidate ) ) check( hfork, total_stake, candidate, blk->dead, &blk->our_bank_hash );
     322           0 :     curr = bank_hash_pool_ele( hfork->bank_hash_pool, curr->next );
     323           0 :   }
     324           0 : }

Generated by: LCOV version 1.14