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

Generated by: LCOV version 1.14