LCOV - code coverage report
Current view: top level - flamenco/rewards - fd_stake_rewards.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 152 182 83.5 %
Date: 2026-03-19 18:19:27 Functions: 18 18 100.0 %

          Line data    Source code
       1             : #include "fd_stake_rewards.h"
       2             : #include "fd_rewards_base.h"
       3             : #include "../../ballet/siphash13/fd_siphash13.h"
       4             : 
       5             : #define MAX_SUPPORTED_FORKS (128UL)
       6             : 
       7          12 : #define FD_STAKE_REWARDS_MAGIC (0xF17EDA2CE757A4E0) /* FIREDANCER STAKE V0 */
       8             : 
       9             : struct index_key {
      10             :   fd_pubkey_t pubkey;
      11             :   ulong       lamports;
      12             :   ulong       credits_observed;
      13             : };
      14             : typedef struct index_key index_key_t;
      15             : 
      16             : struct index_ele {
      17             :   union {
      18             :     struct {
      19             :       fd_pubkey_t pubkey;
      20             :       ulong       lamports;
      21             :       ulong       credits_observed;
      22             :     };
      23             :     index_key_t index_key;
      24             :   };
      25             :   uint next;
      26             : };
      27             : typedef struct index_ele index_ele_t;
      28             : 
      29             : #define MAP_NAME               index_map
      30             : #define MAP_KEY_T              index_key_t
      31             : #define MAP_ELE_T              index_ele_t
      32           4 : #define MAP_KEY                index_key
      33           0 : #define MAP_KEY_EQ(k0,k1)      (!memcmp( k0, k1, sizeof(index_key_t) ))
      34           8 : #define MAP_KEY_HASH(key,seed) (fd_hash( seed, key, sizeof(index_key_t) ))
      35           4 : #define MAP_NEXT               next
      36          28 : #define MAP_IDX_T              uint
      37             : #include "../../util/tmpl/fd_map_chain.c"
      38             : 
      39             : struct fork {
      40             :   int next;
      41             : };
      42             : typedef struct fork fork_t;
      43             : 
      44             : #define POOL_NAME  fork_pool
      45          24 : #define POOL_T     fork_t
      46          36 : #define POOL_NEXT  next
      47             : #define POOL_IDX_T int
      48             : #include "../../util/tmpl/fd_pool.c"
      49             : 
      50             : struct partition_ele  {
      51             :   uint index;
      52             :   uint next;
      53             : };
      54             : typedef struct partition_ele partition_ele_t;
      55             : 
      56             : struct fork_info {
      57             :   uint  ele_cnt;
      58             :   uint  partition_cnt;
      59             :   uint  partition_idxs_head[MAX_PARTITIONS_PER_EPOCH];
      60             :   uint  partition_idxs_tail[MAX_PARTITIONS_PER_EPOCH];
      61             :   ulong starting_block_height;
      62             :   ulong total_stake_rewards;
      63             : 
      64             : };
      65             : typedef struct fork_info fork_info_t;
      66             : 
      67             : struct fd_stake_rewards {
      68             :   ulong       magic;
      69             :   uint        total_ele_used;
      70             :   ulong       max_stake_accounts;
      71             :   fork_info_t fork_info[MAX_SUPPORTED_FORKS];
      72             :   ulong       fork_pool_offset;
      73             :   ulong       index_pool_offset;
      74             :   ulong       index_map_offset;
      75             :   ulong       partitions_offset;
      76             :   ulong       epoch;
      77             : 
      78             :   /* Temporary storage for the current stake reward being computed. */
      79             :   fd_hash_t parent_blockhash;
      80             :   uint      iter_curr_fork_idx;
      81             : };
      82             : typedef struct fd_stake_rewards fd_stake_rewards_t;
      83             : 
      84             : static inline fork_t *
      85           4 : get_fork_pool( fd_stake_rewards_t const * stake_rewards ) {
      86           4 :   return fd_type_pun( (uchar *)stake_rewards + stake_rewards->fork_pool_offset );
      87           4 : }
      88             : static inline index_ele_t *
      89           6 : get_index_pool( fd_stake_rewards_t const * stake_rewards ) {
      90           6 :   return fd_type_pun( (uchar *)stake_rewards + stake_rewards->index_pool_offset );
      91           6 : }
      92             : static inline index_map_t *
      93           8 : get_index_map( fd_stake_rewards_t const * stake_rewards ) {
      94           8 :   return fd_type_pun( (uchar *)stake_rewards + stake_rewards->index_map_offset );
      95           8 : }
      96             : 
      97             : static inline partition_ele_t *
      98             : get_partition_ele( fd_stake_rewards_t const * stake_rewards,
      99             :                    uchar                      fork_idx,
     100           8 :                    uint                       ele_cnt ) {
     101             : 
     102           8 :   return fd_type_pun( (uchar *)stake_rewards + stake_rewards->partitions_offset +
     103           8 :                       (fork_idx * stake_rewards->max_stake_accounts * sizeof(partition_ele_t)) +
     104           8 :                       (ele_cnt * sizeof(partition_ele_t)) );
     105           8 : }
     106             : 
     107             : ulong
     108         300 : fd_stake_rewards_align( void ) {
     109         300 :   return FD_STAKE_REWARDS_ALIGN;
     110         300 : }
     111             : 
     112             : ulong
     113             : fd_stake_rewards_footprint( ulong max_stake_accounts,
     114             :                             ulong expected_stake_accs,
     115          48 :                             ulong max_fork_width ) {
     116          48 :   ulong map_chain_cnt = index_map_chain_cnt_est( expected_stake_accs );
     117             : 
     118          48 :   ulong l = FD_LAYOUT_INIT;
     119          48 :   l  = FD_LAYOUT_APPEND( l, fd_stake_rewards_align(),  sizeof(fd_stake_rewards_t) );
     120          48 :   l =  FD_LAYOUT_APPEND( l, fork_pool_align(),         fork_pool_footprint( max_fork_width ) );
     121          48 :   l  = FD_LAYOUT_APPEND( l, alignof(index_ele_t),      sizeof(index_ele_t) * max_stake_accounts );
     122          48 :   l  = FD_LAYOUT_APPEND( l, index_map_align(),         index_map_footprint( map_chain_cnt ) );
     123          48 :   l  = FD_LAYOUT_APPEND( l, alignof(partition_ele_t),  max_fork_width * max_stake_accounts * sizeof(partition_ele_t) );
     124             : 
     125             :   /* we take advantage of the fact that the number of partitions * 8192
     126             :      is always == fd_ulong_align_up( max_stake_accounts, 8192UL ) */
     127             : 
     128          48 :   return FD_LAYOUT_FINI( l, fd_stake_rewards_align() );
     129          48 : }
     130             : 
     131             : void *
     132             : fd_stake_rewards_new( void * shmem,
     133             :                       ulong  max_stake_accounts,
     134             :                       ulong  expected_stake_accs,
     135             :                       ulong  max_fork_width,
     136          12 :                       ulong  seed ) {
     137          12 :   if( FD_UNLIKELY( !shmem ) ) {
     138           0 :     FD_LOG_WARNING(( "NULL shmem" ));
     139           0 :     return NULL;
     140           0 :   }
     141          12 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_stake_rewards_align() ) ) ) {
     142           0 :     FD_LOG_WARNING(( "misaligned shmem" ));
     143           0 :     return NULL;
     144           0 :   }
     145             : 
     146          12 :   ulong map_chain_cnt = index_map_chain_cnt_est( expected_stake_accs );
     147             : 
     148          12 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     149          12 :   fd_stake_rewards_t * stake_rewards  = FD_SCRATCH_ALLOC_APPEND( l, fd_stake_rewards_align(), sizeof(fd_stake_rewards_t) );
     150          12 :   void *               fork_pool_mem  = FD_SCRATCH_ALLOC_APPEND( l, fork_pool_align(),        fork_pool_footprint( max_fork_width ) );
     151          12 :   void *               index_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(index_ele_t),     sizeof(index_ele_t) * max_stake_accounts );
     152          12 :   void *               index_map_mem  = FD_SCRATCH_ALLOC_APPEND( l, index_map_align(),        index_map_footprint( map_chain_cnt ) );
     153          12 :   void *               partitions_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(partition_ele_t), max_fork_width * max_stake_accounts * sizeof(partition_ele_t) );
     154             : 
     155           0 :   fork_t * fork_pool = fork_pool_join( fork_pool_new( fork_pool_mem, max_fork_width ) );
     156          12 :   if( FD_UNLIKELY( !fork_pool ) ) {
     157           0 :     FD_LOG_WARNING(( "Failed to create fork pool" ));
     158           0 :     return NULL;
     159           0 :   }
     160          12 :   stake_rewards->fork_pool_offset = (ulong)fork_pool - (ulong)shmem;
     161             : 
     162          12 :   stake_rewards->index_pool_offset = (ulong)index_pool_mem - (ulong)shmem;
     163             : 
     164          12 :   index_map_t * index_map = index_map_join( index_map_new( index_map_mem, map_chain_cnt, seed ) );
     165          12 :   if( FD_UNLIKELY( !index_map ) ) {
     166           0 :     FD_LOG_WARNING(( "Failed to create index map" ));
     167           0 :     return NULL;
     168           0 :   }
     169          12 :   stake_rewards->index_map_offset   = (ulong)index_map - (ulong)shmem;
     170          12 :   stake_rewards->partitions_offset  = (ulong)partitions_mem - (ulong)shmem;
     171          12 :   stake_rewards->max_stake_accounts = max_stake_accounts;
     172          12 :   stake_rewards->epoch              = ULONG_MAX;
     173          12 :   stake_rewards->total_ele_used     = 0UL;
     174             : 
     175          12 :   FD_COMPILER_MFENCE();
     176          12 :   FD_VOLATILE( stake_rewards->magic ) = FD_STAKE_REWARDS_MAGIC;
     177          12 :   FD_COMPILER_MFENCE();
     178             : 
     179          12 :   return shmem;
     180          12 : }
     181             : 
     182             : fd_stake_rewards_t *
     183          12 : fd_stake_rewards_join( void * shmem ) {
     184          12 :   if( FD_UNLIKELY( !shmem ) ) {
     185           0 :     FD_LOG_WARNING(( "NULL shmem" ));
     186           0 :     return NULL;
     187           0 :   }
     188             : 
     189          12 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_stake_rewards_align() ) ) ) {
     190           0 :     FD_LOG_WARNING(( "misaligned shmem" ));
     191           0 :     return NULL;
     192           0 :   }
     193             : 
     194          12 :   fd_stake_rewards_t * stake_rewards = (fd_stake_rewards_t *)shmem;
     195          12 :   if( FD_UNLIKELY( stake_rewards->magic != FD_STAKE_REWARDS_MAGIC ) ) {
     196           0 :     FD_LOG_WARNING(( "Invalid stake rewards magic" ));
     197           0 :     return NULL;
     198           0 :   }
     199          12 :   return stake_rewards;
     200          12 : }
     201             : 
     202             : uchar
     203             : fd_stake_rewards_init( fd_stake_rewards_t * stake_rewards,
     204             :                        ulong                epoch,
     205             :                        fd_hash_t const *    parent_blockhash,
     206             :                        ulong                starting_block_height,
     207           4 :                        uint                 partitions_cnt ) {
     208           4 :   index_map_t * index_map = get_index_map( stake_rewards );
     209           4 :   fork_t *      fork_pool = get_fork_pool( stake_rewards );
     210             : 
     211             :   /* If this is the first reference to the stake rewards, we need to
     212             :      reset the backing map and pool all the forks will share. */
     213           4 :   if( FD_LIKELY( stake_rewards->epoch!=epoch ) ) {
     214           4 :     fork_pool_reset( fork_pool );
     215           4 :     index_map_reset( index_map );
     216           4 :     stake_rewards->epoch          = epoch;
     217           4 :     stake_rewards->total_ele_used = 0UL;
     218           4 :   }
     219             : 
     220           4 :   uchar fork_idx = (uchar)fork_pool_idx_acquire( fork_pool );
     221             : 
     222           4 :   stake_rewards->parent_blockhash = *parent_blockhash;
     223             : 
     224           4 :   stake_rewards->fork_info[fork_idx].partition_cnt         = partitions_cnt;
     225           4 :   stake_rewards->fork_info[fork_idx].starting_block_height = starting_block_height;
     226           4 :   stake_rewards->fork_info[fork_idx].ele_cnt               = 0UL;
     227           4 :   stake_rewards->fork_info[fork_idx].total_stake_rewards   = 0UL;
     228           4 :   memset( stake_rewards->fork_info[fork_idx].partition_idxs_head, 0xFF, sizeof(stake_rewards->fork_info[fork_idx].partition_idxs_head) );
     229           4 :   memset( stake_rewards->fork_info[fork_idx].partition_idxs_tail, 0xFF, sizeof(stake_rewards->fork_info[fork_idx].partition_idxs_tail) );
     230             : 
     231           4 :   return fork_idx;
     232           4 : }
     233             : 
     234             : void
     235             : fd_stake_rewards_insert( fd_stake_rewards_t * stake_rewards,
     236             :                          uchar                fork_idx,
     237             :                          fd_pubkey_t const *  pubkey,
     238             :                          ulong                lamports,
     239           4 :                          ulong                credits_observed ) {
     240           4 :   index_ele_t * index_ele = get_index_pool( stake_rewards );
     241           4 :   index_map_t * index_map = get_index_map( stake_rewards );
     242             : 
     243           4 :   index_key_t index_key = {
     244           4 :     .pubkey           = *pubkey,
     245           4 :     .lamports         = lamports,
     246           4 :     .credits_observed = credits_observed,
     247           4 :   };
     248             : 
     249           4 :   uint index = (uint)index_map_idx_query( index_map, &index_key, UINT_MAX, index_ele );
     250           4 :   if( FD_LIKELY( index==UINT_MAX ) ) {
     251           4 :     index = stake_rewards->total_ele_used;
     252           4 :     stake_rewards->total_ele_used++;
     253           4 :     if( FD_UNLIKELY( index>=stake_rewards->max_stake_accounts ) ) {
     254           0 :       FD_LOG_CRIT(( "invariant violation: index>=stake_rewards->max_stake_accounts" ));
     255           0 :     }
     256           4 :     index_ele_t * ele = (index_ele_t *)index_ele + index;
     257           4 :     ele->index_key = index_key;
     258           4 :     index_map_ele_insert( index_map, ele, index_ele );
     259           4 :   }
     260             : 
     261             :   /* We have an invariant that there can never be more than 8192 entries
     262             :      in a partition. */
     263           4 :   fd_siphash13_t   sip[1] = {0};
     264           4 :   fd_siphash13_t * hasher = fd_siphash13_init( sip, 0UL, 0UL );
     265           4 :   hasher = fd_siphash13_append( hasher, stake_rewards->parent_blockhash.hash, sizeof(fd_hash_t) );
     266           4 :   fd_siphash13_append( hasher, (uchar const *)pubkey->uc, sizeof(fd_pubkey_t) );
     267           4 :   ulong hash64 = fd_siphash13_fini( hasher );
     268             : 
     269           4 :   ulong partition_index = (ulong)((uint128)stake_rewards->fork_info[fork_idx].partition_cnt * (uint128) hash64 / ((uint128)ULONG_MAX + 1));
     270             : 
     271           4 :   uint curr_fork_len = stake_rewards->fork_info[fork_idx].ele_cnt;
     272           4 :   if( FD_UNLIKELY( curr_fork_len>=stake_rewards->max_stake_accounts ) ) {
     273           0 :     FD_LOG_CRIT(( "invariant violation: curr_fork_len>=stake_rewards->max_stake_accounts" ));
     274           0 :   }
     275             : 
     276           4 :   partition_ele_t * partition_ele = get_partition_ele( stake_rewards, fork_idx, curr_fork_len );
     277           4 :   partition_ele->index = index;
     278           4 :   partition_ele->next  = UINT_MAX;
     279             : 
     280           4 :   int is_first_ele = stake_rewards->fork_info[fork_idx].partition_idxs_head[partition_index] == UINT_MAX;
     281             : 
     282           4 :   if( FD_LIKELY( !is_first_ele ) ) {
     283           0 :     partition_ele_t * prev_partition_ele = get_partition_ele( stake_rewards, fork_idx, stake_rewards->fork_info[fork_idx].partition_idxs_tail[partition_index] );
     284           0 :     prev_partition_ele->next = curr_fork_len;
     285           0 :     stake_rewards->fork_info[fork_idx].partition_idxs_tail[partition_index] = curr_fork_len;
     286           4 :   } else {
     287           4 :     stake_rewards->fork_info[fork_idx].partition_idxs_head[partition_index] = curr_fork_len;
     288           4 :     stake_rewards->fork_info[fork_idx].partition_idxs_tail[partition_index] = curr_fork_len;
     289           4 :   }
     290             : 
     291           4 :   stake_rewards->fork_info[fork_idx].ele_cnt++;
     292           4 :   stake_rewards->fork_info[fork_idx].total_stake_rewards += lamports;
     293           4 : }
     294             : 
     295             : void
     296             : fd_stake_rewards_iter_init( fd_stake_rewards_t * stake_rewards,
     297             :                             uchar                fork_idx,
     298           2 :                             uint                 partition_idx ) {
     299           2 :   uint first_fork_idx = stake_rewards->fork_info[fork_idx].partition_idxs_head[partition_idx];
     300           2 :   stake_rewards->iter_curr_fork_idx = first_fork_idx;
     301           2 : }
     302             : 
     303             : void
     304             : fd_stake_rewards_iter_next( fd_stake_rewards_t * stake_rewards,
     305           2 :                             uchar                fork_idx ) {
     306           2 :   partition_ele_t * partition_ele = get_partition_ele( stake_rewards, fork_idx, stake_rewards->iter_curr_fork_idx );
     307           2 :   stake_rewards->iter_curr_fork_idx = partition_ele->next;
     308           2 : }
     309             : 
     310             : int
     311           4 : fd_stake_rewards_iter_done( fd_stake_rewards_t * stake_rewards ) {
     312           4 :   return stake_rewards->iter_curr_fork_idx == UINT_MAX;
     313           4 : }
     314             : 
     315             : void
     316             : fd_stake_rewards_iter_ele( fd_stake_rewards_t * stake_rewards,
     317             :                            uchar                fork_idx,
     318             :                            fd_pubkey_t *        pubkey_out,
     319             :                            ulong *              lamports_out,
     320           2 :                            ulong *              credits_observed_out ) {
     321           2 :   partition_ele_t * partition_ele = get_partition_ele( stake_rewards, fork_idx, stake_rewards->iter_curr_fork_idx );
     322             : 
     323           2 :   index_ele_t * index_ele = get_index_pool( stake_rewards ) + partition_ele->index;
     324           2 :   *pubkey_out = index_ele->index_key.pubkey;
     325           2 :   *lamports_out = index_ele->index_key.lamports;
     326           2 :   *credits_observed_out = index_ele->index_key.credits_observed;
     327           2 : }
     328             : 
     329             : ulong
     330             : fd_stake_rewards_total_rewards( fd_stake_rewards_t const * stake_rewards,
     331           2 :                                 uchar                      fork_idx ) {
     332           2 :   return stake_rewards->fork_info[fork_idx].total_stake_rewards;
     333           2 : }
     334             : 
     335             : uint
     336             : fd_stake_rewards_num_partitions( fd_stake_rewards_t const * stake_rewards,
     337          33 :                                  uchar                      fork_idx ) {
     338          33 :   return stake_rewards->fork_info[fork_idx].partition_cnt;
     339          33 : }
     340             : 
     341             : ulong
     342             : fd_stake_rewards_starting_block_height( fd_stake_rewards_t const * stake_rewards,
     343          31 :                                         uchar                      fork_idx ) {
     344          31 :   return stake_rewards->fork_info[fork_idx].starting_block_height;
     345          31 : }
     346             : 
     347             : ulong
     348             : fd_stake_rewards_exclusive_ending_block_height( fd_stake_rewards_t const * stake_rewards,
     349          31 :                                                 uchar                      fork_idx ) {
     350          31 :   return stake_rewards->fork_info[fork_idx].starting_block_height + stake_rewards->fork_info[fork_idx].partition_cnt;
     351          31 : }

Generated by: LCOV version 1.14