LCOV - code coverage report
Current view: top level - flamenco/stakes - fd_top_votes.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 156 205 76.1 %
Date: 2026-05-15 07:18:56 Functions: 16 17 94.1 %

          Line data    Source code
       1             : #include "fd_top_votes.h"
       2             : #include "../accdb/fd_accdb_sync.h"
       3             : #include "../runtime/program/vote/fd_vote_state_versioned.h"
       4             : #include "../runtime/program/vote/fd_vote_codec.h"
       5             : 
       6        1185 : #define FD_TOP_VOTES_MAGIC (0xF17EDA2CE7401E70UL) /* FIREDANCER TOP VOTES V0 */
       7             : 
       8             : struct fd_top_votes {
       9             :   ulong magic;
      10             :   ulong pool_off;
      11             :   ulong heap_off;
      12             :   ulong map_off;
      13             : 
      14             :   ulong min_stake_wmark;
      15             : };
      16             : typedef struct fd_top_votes fd_top_votes_t;
      17             : 
      18             : struct vote_ele {
      19             :   fd_pubkey_t pubkey;
      20             :   fd_pubkey_t node_account;
      21             :   ulong       stake;
      22             :   ulong       last_vote_slot;
      23             :   long        last_vote_timestamp;
      24             :   uchar       commission;
      25             :   uchar       is_valid;
      26             : 
      27             :   ushort      left;
      28             :   ushort      right;
      29             :   ushort      next;
      30             : };
      31             : typedef struct vote_ele vote_ele_t;
      32             : 
      33             : #define HEAP_NAME       heap
      34       14013 : #define HEAP_IDX_T      ushort
      35             : #define HEAP_T          vote_ele_t
      36         117 : #define HEAP_LT(e0,e1) ( ((e0)->stake < (e1)->stake) | \
      37         117 :                          (((e0)->stake==(e1)->stake) & \
      38         117 :                           (memcmp( &(e0)->pubkey, &(e1)->pubkey, sizeof(fd_pubkey_t) )<0 ) ) )
      39             : #include "../../util/tmpl/fd_heap.c"
      40             : 
      41             : #define POOL_NAME  pool
      42        2370 : #define POOL_T     vote_ele_t
      43             : #define POOL_IDX_T ushort
      44             : #define POOL_LAZY  1
      45             : #include "../../util/tmpl/fd_pool.c"
      46             : 
      47             : #define MAP_NAME               map
      48             : #define MAP_KEY_T              fd_pubkey_t
      49             : #define MAP_ELE_T              vote_ele_t
      50        7035 : #define MAP_KEY                pubkey
      51        3837 : #define MAP_KEY_EQ(k0,k1)      (!memcmp( k0, k1, sizeof(fd_pubkey_t) ))
      52       10836 : #define MAP_KEY_HASH(key,seed) (fd_hash( seed, key, sizeof(fd_pubkey_t) ))
      53       19293 : #define MAP_IDX_T              ushort
      54             : #include "../../util/tmpl/fd_map_chain.c"
      55             : 
      56             : static inline vote_ele_t *
      57       18576 : get_pool( fd_top_votes_t const * top_votes ) {
      58       18576 :   return (vote_ele_t *)( (ulong)top_votes + top_votes->pool_off );
      59       18576 : }
      60             : 
      61             : static inline heap_t *
      62       14040 : get_heap( fd_top_votes_t const * top_votes ) {
      63       14040 :   return (heap_t *)( (ulong)top_votes + top_votes->heap_off );
      64       14040 : }
      65             : 
      66             : static inline map_t *
      67       18576 : get_map( fd_top_votes_t const * top_votes ) {
      68       18576 :   return (map_t *)( (ulong)top_votes + top_votes->map_off );
      69       18576 : }
      70             : 
      71             : ulong
      72       14247 : fd_top_votes_align( void ) {
      73       14247 :   return FD_TOP_VOTES_ALIGN;
      74       14247 : }
      75             : 
      76             : ulong
      77        2376 : fd_top_votes_footprint( ulong vote_accounts_max ) {
      78        2376 :   ulong map_chain_cnt = map_chain_cnt_est( vote_accounts_max );
      79             : 
      80        2376 :   ulong l = FD_LAYOUT_INIT;
      81        2376 :   l = FD_LAYOUT_APPEND( l, fd_top_votes_align(), sizeof(fd_top_votes_t) );
      82        2376 :   l = FD_LAYOUT_APPEND( l, pool_align(),         pool_footprint( vote_accounts_max ) );
      83        2376 :   l = FD_LAYOUT_APPEND( l, heap_align(),         heap_footprint( vote_accounts_max ) );
      84        2376 :   l = FD_LAYOUT_APPEND( l, map_align(),          map_footprint( map_chain_cnt ) );
      85        2376 :   return FD_LAYOUT_FINI( l, fd_top_votes_align() );
      86        2376 : }
      87             : 
      88             : void *
      89             : fd_top_votes_new( void * mem,
      90             :                   ushort vote_accounts_max,
      91        1188 :                   ulong  seed ) {
      92        1188 :   if( FD_UNLIKELY( !mem ) ) {
      93           3 :     FD_LOG_WARNING(( "NULL mem" ));
      94           3 :     return NULL;
      95           3 :   }
      96             : 
      97        1185 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_top_votes_align() ) ) ) {
      98           0 :     FD_LOG_WARNING(( "misaligned mem" ));
      99           0 :     return NULL;
     100           0 :   }
     101             : 
     102        1185 :   ulong map_chain_cnt = map_chain_cnt_est( vote_accounts_max );
     103             : 
     104        1185 :   FD_SCRATCH_ALLOC_INIT( l, mem );
     105        1185 :   fd_top_votes_t * top_votes = FD_SCRATCH_ALLOC_APPEND( l, fd_top_votes_align(), sizeof(fd_top_votes_t) );
     106        1185 :   void *           pool_mem  = FD_SCRATCH_ALLOC_APPEND( l, pool_align(),         pool_footprint( vote_accounts_max ) );
     107        1185 :   void *           heap_mem  = FD_SCRATCH_ALLOC_APPEND( l, heap_align(),         heap_footprint( vote_accounts_max ) );
     108        1185 :   void *           map_mem   = FD_SCRATCH_ALLOC_APPEND( l, map_align(),          map_footprint( map_chain_cnt ) );
     109             : 
     110        1185 :   if( FD_UNLIKELY( FD_SCRATCH_ALLOC_FINI( l, fd_top_votes_align() ) != (ulong)top_votes + fd_top_votes_footprint( vote_accounts_max ) ) ) {
     111           0 :     FD_LOG_WARNING(( "fd_banks_new: bad layout" ));
     112           0 :     return NULL;
     113           0 :   }
     114             : 
     115        1185 :   if( FD_UNLIKELY( fd_top_votes_footprint( vote_accounts_max )>FD_TOP_VOTES_MAX_FOOTPRINT ) ) {
     116           0 :     FD_LOG_WARNING(( "fd_top_votes_new: bad footprint" ));
     117           0 :     return NULL;
     118           0 :   }
     119             : 
     120        1185 :   vote_ele_t * pool = pool_join( pool_new( pool_mem, vote_accounts_max ) );
     121        1185 :   if( FD_UNLIKELY( !pool ) ) {
     122           0 :     FD_LOG_WARNING(( "Failed to create top votes pool" ));
     123           0 :     return NULL;
     124           0 :   }
     125        1185 :   top_votes->pool_off = (ulong)pool - (ulong)mem;
     126             : 
     127        1185 :   heap_t * heap = heap_join( heap_new( heap_mem, vote_accounts_max ) );
     128        1185 :   if( FD_UNLIKELY( !heap ) ) {
     129           0 :     FD_LOG_WARNING(( "Failed to create top votes heap" ));
     130           0 :     return NULL;
     131           0 :   }
     132        1185 :   top_votes->heap_off = (ulong)heap - (ulong)mem;
     133             : 
     134        1185 :   map_t * map = map_join( map_new( map_mem, map_chain_cnt, seed ) );
     135        1185 :   if( FD_UNLIKELY( !map ) ) {
     136           0 :     FD_LOG_WARNING(( "Failed to create top votes map" ));
     137           0 :     return NULL;
     138           0 :   }
     139        1185 :   top_votes->map_off = (ulong)map - (ulong)mem;
     140             : 
     141        1185 :   FD_COMPILER_MFENCE();
     142        1185 :   FD_VOLATILE( top_votes->magic ) = FD_TOP_VOTES_MAGIC;
     143        1185 :   FD_COMPILER_MFENCE();
     144             : 
     145        1185 :   return top_votes;
     146        1185 : }
     147             : 
     148             : fd_top_votes_t *
     149        1185 : fd_top_votes_join( void * mem ) {
     150        1185 :   fd_top_votes_t * top_votes = (fd_top_votes_t *)mem;
     151             : 
     152        1185 :   if( FD_UNLIKELY( !top_votes ) ) {
     153           0 :     FD_LOG_WARNING(( "NULL top votes" ));
     154           0 :     return NULL;
     155           0 :   }
     156             : 
     157        1185 :   if( FD_UNLIKELY( top_votes->magic!=FD_TOP_VOTES_MAGIC ) ) {
     158           0 :     FD_LOG_WARNING(( "Invalid top votes magic" ));
     159           0 :     return NULL;
     160           0 :   }
     161             : 
     162        1185 :   return top_votes;
     163        1185 : }
     164             : 
     165             : void
     166        6981 : fd_top_votes_init( fd_top_votes_t * top_votes ) {
     167        6981 :   vote_ele_t * pool = get_pool( top_votes );
     168        6981 :   heap_t *     heap = get_heap( top_votes );
     169        6981 :   map_t *      map  = get_map( top_votes );
     170             : 
     171        6981 :   top_votes->min_stake_wmark = 0UL;
     172             : 
     173             :   /* TODO: A smarter reset can probably be done here. */
     174       13941 :   while( heap_ele_cnt( heap ) ) heap_ele_remove_min( heap, pool );
     175             : 
     176        6981 :   map_reset( map );
     177        6981 :   pool_reset( pool );
     178        6981 : }
     179             : 
     180             : void
     181             : fd_top_votes_insert( fd_top_votes_t *    top_votes,
     182             :                      fd_pubkey_t const * pubkey,
     183             :                      fd_pubkey_t const * node_account,
     184             :                      ulong               stake,
     185        7059 :                      uchar               commission ) {
     186             : /* If the heap is full, treat the current minimum stake as the cutoff
     187             :    stake.  This matches Agave's retain(stake > floor_stake) behavior:
     188             :    1. Reject candidates below the cutoff.
     189             :    2. Evict all entries at the cutoff stake and watermark that stake so
     190             :       later inserts at or below the cutoff stay excluded.
     191             :    3. Insert the candidate only if it is strictly above the cutoff. */
     192             : 
     193        7059 :   vote_ele_t * pool = get_pool( top_votes );
     194        7059 :   heap_t *     heap = get_heap( top_votes );
     195        7059 :   map_t *      map  = get_map( top_votes );
     196             : 
     197        7059 :   if( FD_UNLIKELY( stake==0UL || stake<=top_votes->min_stake_wmark ) ) return;
     198             : 
     199        7044 :   if( FD_UNLIKELY( heap_ele_cnt( heap )==heap_ele_max( heap ) ) ) {
     200          15 :     vote_ele_t * ele       = heap_ele_peek_min( heap, pool );
     201          15 :     ulong        min_stake = ele->stake;
     202          15 :     if( stake<min_stake ) return;
     203             : 
     204          12 :     top_votes->min_stake_wmark = min_stake;
     205          30 :     while( (ele=heap_ele_peek_min( heap, pool )) && ele && min_stake==ele->stake ) {
     206          18 :       heap_ele_remove_min( heap, pool );
     207          18 :       map_ele_remove( map, &ele->pubkey, NULL, pool );
     208          18 :       pool_ele_release( pool, ele );
     209          18 :     }
     210          12 :     if( FD_UNLIKELY( stake==min_stake ) ) return;
     211          12 :   }
     212             : 
     213        7035 :   vote_ele_t * ele         = pool_ele_acquire( pool );
     214        7035 :   ele->pubkey              = *pubkey;
     215        7035 :   ele->node_account        = *node_account;
     216        7035 :   ele->stake               = stake;
     217        7035 :   ele->commission          = commission;
     218        7035 :   ele->last_vote_slot      = 0UL;
     219        7035 :   ele->last_vote_timestamp = 0L;
     220        7035 :   ele->is_valid            = 1;
     221        7035 :   heap_ele_insert( heap, ele, pool );
     222        7035 :   map_ele_insert( map, ele, pool );
     223        7035 : }
     224             : 
     225             : void
     226             : fd_top_votes_update( fd_top_votes_t *    top_votes,
     227             :                      fd_pubkey_t const * pubkey,
     228             :                      ulong               last_vote_slot,
     229         132 :                      long                last_vote_timestamp ) {
     230         132 :   vote_ele_t * pool = get_pool( top_votes );
     231         132 :   map_t *      map  = get_map( top_votes );
     232             : 
     233         132 :   ushort idx = (ushort)map_idx_query_const( map, pubkey, USHORT_MAX, pool );
     234         132 :   if( FD_UNLIKELY( idx==USHORT_MAX ) ) return;
     235         132 :   vote_ele_t * ele = pool_ele( pool, idx );
     236         132 :   ele->last_vote_slot      = last_vote_slot;
     237         132 :   ele->last_vote_timestamp = last_vote_timestamp;
     238         132 :   ele->is_valid            = 1;
     239         132 : }
     240             : 
     241             : void
     242             : fd_top_votes_invalidate( fd_top_votes_t *    top_votes,
     243           3 :                          fd_pubkey_t const * pubkey ) {
     244           3 :   vote_ele_t * pool = get_pool( top_votes );
     245           3 :   map_t *      map  = get_map( top_votes );
     246             : 
     247           3 :   ushort idx = (ushort)map_idx_query_const( map, pubkey, USHORT_MAX, pool );
     248           3 :   if( FD_UNLIKELY( idx==USHORT_MAX ) ) return;
     249           3 :   pool_ele( pool, idx )->is_valid = 0;
     250           3 : }
     251             : 
     252             : int
     253             : fd_top_votes_query( fd_top_votes_t const * top_votes,
     254             :                     fd_pubkey_t const *    pubkey,
     255             :                     fd_pubkey_t *          node_account_out_opt,
     256             :                     ulong *                stake_out_opt,
     257             :                     ulong *                last_vote_slot_out_opt,
     258             :                     long *                 last_vote_timestamp_out_opt,
     259        3648 :                     uchar *                commission_out_opt ) {
     260        3648 :   vote_ele_t * pool = get_pool( top_votes );
     261        3648 :   map_t *      map  = get_map( top_votes );
     262             : 
     263        3648 :   vote_ele_t const * ele = map_ele_query_const( map, pubkey, NULL, pool );
     264        3648 :   if( FD_UNLIKELY( !ele ) ) return 0;
     265        3591 :   if( FD_UNLIKELY( !ele->is_valid ) ) return 0;
     266             : 
     267        3588 :   if( node_account_out_opt )        *node_account_out_opt        = ele->node_account;
     268        3588 :   if( stake_out_opt )               *stake_out_opt               = ele->stake;
     269        3588 :   if( last_vote_slot_out_opt )      *last_vote_slot_out_opt      = ele->last_vote_slot;
     270        3588 :   if( last_vote_timestamp_out_opt ) *last_vote_timestamp_out_opt = ele->last_vote_timestamp;
     271        3588 :   if( commission_out_opt )          *commission_out_opt          = ele->commission;
     272        3588 :   return 1;
     273        3591 : }
     274             : 
     275             : void
     276             : fd_top_votes_refresh( fd_top_votes_t *          top_votes,
     277             :                       fd_accdb_user_t *         accdb,
     278           0 :                       fd_funk_txn_xid_t const * xid ) {
     279           0 :   uchar __attribute__((aligned(FD_TOP_VOTES_ITER_ALIGN))) top_votes_iter_mem[ FD_TOP_VOTES_ITER_FOOTPRINT ];
     280           0 :   for( fd_top_votes_iter_t * iter = fd_top_votes_iter_init( top_votes, top_votes_iter_mem );
     281           0 :         !fd_top_votes_iter_done( top_votes, iter );
     282           0 :         fd_top_votes_iter_next( top_votes, iter ) ) {
     283           0 :     fd_pubkey_t pubkey;
     284           0 :     fd_top_votes_iter_ele( top_votes, iter, &pubkey, NULL, NULL, NULL, NULL, NULL );
     285             : 
     286           0 :     int is_valid = 1;
     287           0 :     fd_accdb_ro_t acc[1];
     288           0 :     if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, acc, xid, &pubkey ) ) ) {
     289           0 :       is_valid = 0;
     290           0 :     } else if( FD_UNLIKELY( !fd_vsv_is_correct_size_owner_and_init( acc->meta ) ) ) {
     291           0 :       fd_accdb_close_ro( accdb, acc );
     292           0 :       is_valid = 0;
     293           0 :     }
     294             : 
     295           0 :     if( FD_LIKELY( is_valid ) ) {
     296           0 :       fd_vote_block_timestamp_t last_vote;
     297           0 :       FD_TEST( !fd_vote_account_last_timestamp( fd_account_data( acc->meta ), acc->meta->dlen, &last_vote ) );
     298           0 :       fd_top_votes_update( top_votes, &pubkey, last_vote.slot, last_vote.timestamp );
     299           0 :       fd_accdb_close_ro( accdb, acc );
     300           0 :     } else {
     301           0 :       fd_top_votes_invalidate( top_votes, &pubkey );
     302           0 :     }
     303           0 :   }
     304           0 : }
     305             : 
     306             : FD_STATIC_ASSERT( FD_TOP_VOTES_ITER_FOOTPRINT == sizeof(map_iter_t), top_votes_iter );
     307             : FD_STATIC_ASSERT( FD_TOP_VOTES_ITER_ALIGN == alignof(map_iter_t), top_votes_iter );
     308             : 
     309             : fd_top_votes_iter_t *
     310             : fd_top_votes_iter_init( fd_top_votes_t const * top_votes,
     311         147 :                         uchar                  iter_mem[ static FD_TOP_VOTES_ITER_FOOTPRINT ] ) {
     312         147 :   map_iter_t iter = map_iter_init( get_map( top_votes ), get_pool( top_votes ) );
     313         147 :   memcpy( iter_mem, &iter, sizeof(map_iter_t) );
     314         147 :   return (fd_top_votes_iter_t *)iter_mem;
     315         147 : }
     316             : 
     317             : int
     318             : fd_top_votes_iter_done( fd_top_votes_t const * top_votes,
     319         300 :                         fd_top_votes_iter_t *  iter ) {
     320         300 :   map_iter_t * map_iter = (map_iter_t *)iter;
     321         300 :   return map_iter_done( *map_iter, get_map( top_votes ), get_pool( top_votes ) );
     322         300 : }
     323             : 
     324             : void
     325             : fd_top_votes_iter_next( fd_top_votes_t const * top_votes,
     326         153 :                         fd_top_votes_iter_t *  iter ) {
     327         153 :   map_iter_t * map_iter = (map_iter_t *)iter;
     328         153 :   *map_iter = map_iter_next( *map_iter, get_map( top_votes ), get_pool( top_votes ) );
     329         153 : }
     330             : 
     331             : int
     332             : fd_top_votes_iter_ele( fd_top_votes_t const * top_votes,
     333             :                        fd_top_votes_iter_t *  iter,
     334             :                        fd_pubkey_t *          pubkey_out,
     335             :                        fd_pubkey_t *          node_account_out_opt,
     336             :                        ulong *                stake_out_opt,
     337             :                        uchar *                commission_out_opt,
     338             :                        ulong *                last_vote_slot_out_opt,
     339         153 :                        long *                 last_vote_timestamp_out_opt ) {
     340         153 :   map_iter_t * map_iter = (map_iter_t *)iter;
     341         153 :   vote_ele_t * ele      = map_iter_ele( *map_iter, get_map( top_votes ), get_pool( top_votes ) );
     342         153 :   *pubkey_out = ele->pubkey;
     343             : 
     344         153 :   if( node_account_out_opt )        *node_account_out_opt        = ele->node_account;
     345         153 :   if( stake_out_opt )               *stake_out_opt               = ele->stake;
     346         153 :   if( last_vote_slot_out_opt )      *last_vote_slot_out_opt      = ele->last_vote_slot;
     347         153 :   if( last_vote_timestamp_out_opt ) *last_vote_timestamp_out_opt = ele->last_vote_timestamp;
     348         153 :   if( commission_out_opt )          *commission_out_opt          = ele->commission;
     349             : 
     350         153 :   return ele->is_valid;
     351         153 : }

Generated by: LCOV version 1.14