LCOV - code coverage report
Current view: top level - flamenco/stakes - fd_vote_states.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 1 11 9.1 %
Date: 2025-11-29 04:46:19 Functions: 0 0 -

          Line data    Source code
       1             : #ifndef HEADER_fd_src_flamenco_stakes_fd_vote_states_h
       2             : #define HEADER_fd_src_flamenco_stakes_fd_vote_states_h
       3             : 
       4             : #include "../../util/fd_util_base.h"
       5             : #include "../../util/tmpl/fd_map.h"
       6             : #include "../types/fd_types_custom.h"
       7             : 
       8           3 : #define FD_VOTE_STATES_MAGIC (0xF17EDA2CE7601E70UL) /* FIREDANCER VOTER V0 */
       9             : 
      10             : /* fd_vote_states_t is a cache of vote accounts mapping the pubkey of
      11             :    a vote account to various information about the vote account
      12             :    including, stake, last vote slot/timestamp, and commission for the
      13             :    vote account.  The vote states are safe to be used across multiple
      14             :    threads but concurrent reads/writes must be synchronized by the
      15             :    caller.
      16             : 
      17             :    In the runtime, there are 3 instances of fd_vote_states_t that are
      18             :    maintained and used at different points, notably around epoch reward
      19             :    and leader schedule calculations. The 3 instances are:
      20             :    1. vote_states: This is the vote states for the current epoch. This
      21             :       is updated through the course of an epoch as vote accounts are
      22             :       updated.
      23             :    2. vote_states_prev: This is the vote states as of the end of
      24             :       previous epoch E-1 if we are currently executing epoch E.
      25             :       This gets updated at the end of an epoch when vote_states are
      26             :       copied into vote_states_prev.
      27             :    3. vote_states_prev_prev: This is the vote states as of the end of
      28             :       epoch E-2 if we are currently executing epoch E. This only gets
      29             :       updated at the end of an epoch when vote_states_prev is copied
      30             :       into vote_states_prev_prev.
      31             : 
      32             :    The implementation of fd_vote_states_t is a hash map which is backed
      33             :    by a memory pool. Callers are allowed to insert, replace, and remove
      34             :    entries from the map.
      35             : 
      36             :    In practice, fd_vote_states_t are updated in 3 cases:
      37             :    1. They are initially populated from the versioned vote account
      38             :       stake accounts in the snapshot manifest. These are populated from
      39             :       the raw vote account data. This is done in a single pass over the
      40             :       vote account data.
      41             :    2. The vote states for the current epoch can be updated after
      42             :       transaction execution. This is done for vote accounts that are
      43             :       referenced during a transaction.
      44             :    3. Vote states are updated at the epoch boundary. The stake
      45             :       information for the vote states is refreshed at the boundary.
      46             : 
      47             :    The vote states in reality manage a few different sets of
      48             :    information about the vote account:
      49             :    - vote account state: state from the vote account data including
      50             :      the last vote slot/timestamp, commission, and node pubkey.  This is
      51             :      used for clock sysvar and rewards calculations.
      52             :    - stake: stake as of the end of the previous epoch.  This is used
      53             :      eventually for leader schedule calculations.  The stake from epoch
      54             :      T-2 (stake_t_2) is used for the stake in clock calculations and Tower's threshold switch checks.
      55             :      T-1 (stake_t_1)
      56             :    - rewards: this information is only used at the epoch boundary.
      57             : 
      58             :    NOTE: The leader schedule epoch is almost always equal to the current
      59             :    epoch + 1.
      60             :    TODO: highlight the cases where this is not possible.
      61             : 
      62             :    In the agave client, each bank holds its own epoch stakes (agave
      63             :    equivalent of vote_states).  These are keyed by leader schedule epoch
      64             :    (which is almost always equal to epoch + 1).  Old entries are removed
      65             :    and new entries are inserted at the epoch boundary.  When we use the
      66             :    epoch stakes for consensus and for clock timestamp calculations, we
      67             :    query the epoch stakes by the current epoch we are executing in.
      68             : 
      69             :    When we cross from Epoch E-1 to E, we compute our stakes assuming
      70             :    we are in E.  At this point, because we are in epoch E, this means
      71             :    that the leader schedule epoch is E+1 so an epoch_stakes entry for
      72             :    epoch E+1 is inserted into epoch_stakes.
      73             : 
      74             :    After we cross the boundary, in epoch E, we use the epoch stakes as
      75             :    keyed by epoch E for clock/consensus/leader schedule calculations.
      76             :    The stakes keyed by epoch E are actually the ones calculated at the
      77             :    boundary between epoch E-2 and E-1 since that's when the leader
      78             :    schedule epoch is E.  Similiarly, when we are in epoch E+1 we use the
      79             :    stakes caclulated between epoch E-1 and E since that's when the leader
      80             :    schedule epoch is E+1.
      81             : 
      82             :    The firedancer naming of vote states is instead done by reference to
      83             :    the epoch boundary that the stakes were calculated in.  If we are
      84             :    currently in epoch E, we use the vote states calculated at the end of
      85             :    two epochs ago (E-2 -> E-1), hence vote_states_prev_prev.  We store
      86             :    the vote states calculated at the end of the previous epoch
      87             :    (E-1 -> E) as vote_states_prev.  This is also how we cache the stake
      88             :    values in the current vote states which are continually updated. */
      89             : 
      90           0 : #define FD_VOTE_STATES_ALIGN (128UL)
      91             : 
      92             : /* Agave defines the max number of epoch credits to store to be 64.
      93             :    https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v2.2.6/vote-interface/src/state/mod.rs#L37 */
      94             : #define EPOCH_CREDITS_MAX (64UL)
      95             : 
      96             : struct fd_vote_state_credits {
      97             :   ulong       credits_cnt;
      98             :   ushort      epoch       [ EPOCH_CREDITS_MAX ];
      99             :   ulong       credits     [ EPOCH_CREDITS_MAX ];
     100             :   ulong       prev_credits[ EPOCH_CREDITS_MAX ];
     101             : };
     102             : typedef struct fd_vote_state_credits fd_vote_state_credits_t;
     103             : 
     104             : struct fd_vote_state_ele {
     105             :   /* Internal pool/map use */
     106             :   ulong       idx;
     107             :   ulong       next_;
     108             : 
     109             :   /* Vote account stake information which is derived from the stake
     110             :      delegations.  This information is used for leader schedule
     111             :      calculation and clock stake-weighted median calculations.
     112             : 
     113             :      stake_t_2 is used in Tower, for it's threshold switch checks. */
     114             :   ulong       stake;
     115             :   ulong       stake_t_1;
     116             :   ulong       stake_t_2;
     117             : 
     118             :   /* Vote account information which is derived from the vote account
     119             :      data and is used for clock timestamp calculations. */
     120             :   fd_pubkey_t vote_account;
     121             :   fd_pubkey_t node_account;
     122             :   ulong       last_vote_slot;
     123             :   long        last_vote_timestamp;
     124             :   uchar       commission;
     125             : };
     126             : typedef struct fd_vote_state_ele fd_vote_state_ele_t;
     127             : 
     128             : /* Forward declare map iterator API generated by fd_map_chain.c */
     129             : typedef struct fd_vote_state_map_private fd_vote_state_map_t;
     130             : typedef struct fd_map_chain_iter fd_vote_state_map_iter_t;
     131             : struct fd_vote_states_iter {
     132             :   fd_vote_state_map_t *    map;
     133             :   fd_vote_state_ele_t *    pool;
     134             :   fd_vote_state_map_iter_t iter;
     135             : };
     136             : typedef struct fd_vote_states_iter fd_vote_states_iter_t;
     137             : 
     138             : struct __attribute__((aligned(FD_VOTE_STATES_ALIGN))) fd_vote_states {
     139             :   ulong magic;
     140             :   ulong max_vote_accounts_;
     141             :   ulong pool_offset_;
     142             :   ulong map_offset_;
     143             : };
     144             : typedef struct fd_vote_states fd_vote_states_t;
     145             : 
     146             : /* This guarantees that the pool element alignment is at most 128UL. */
     147             : FD_STATIC_ASSERT(alignof(fd_vote_state_ele_t)<=FD_VOTE_STATES_ALIGN, unexpected pool element alignment);
     148             : 
     149             : /* The static footprint of the vote states assumes that there are
     150             :    FD_RUNTIME_MAX_VOTE_ACCOUNTS. It also assumes worst case alignment
     151             :    for each struct. fd_vote_states_t is laid out as first the
     152             :    fd_vote_states_t struct, followed by a pool of fd_vote_state_ele_t
     153             :    structs, followed by a map of fd_vote_state_map_ele_t structs.
     154             :    The pool has FD_RUNTIME_MAX_VOTE_ACCOUNTS elements, and the map
     155             :    has a chain count deteremined by a call to
     156             :    fd_vote_states_chain_cnt_est.
     157             :    NOTE: the footprint is validated to be at least as large as the
     158             :    actual runtime-determined footprint (see test_vote_states.c) */
     159             : 
     160           0 : #define FD_VOTE_STATES_CHAIN_CNT_EST (32768UL)
     161             : #define FD_VOTE_STATES_FOOTPRINT                                                      \
     162             :   /* First, layout the struct with alignment */                                       \
     163           0 :   sizeof(fd_vote_states_t) + alignof(fd_vote_states_t) +                              \
     164           0 :   /* Now layout the pool's data footprint */                                          \
     165           0 :   FD_VOTE_STATES_ALIGN + sizeof(fd_vote_state_ele_t) * FD_RUNTIME_MAX_VOTE_ACCOUNTS + \
     166           0 :   /* Now layout the pool's meta footprint */                                          \
     167           0 :   FD_VOTE_STATES_ALIGN + 128UL /* POOL_ALIGN */ +                                     \
     168           0 :   /* Now layout the map.  We must make assumptions about the chain */                 \
     169           0 :   /* count to be equivalent to chain_cnt_est. */                                      \
     170           0 :   FD_VOTE_STATES_ALIGN + 128UL /* MAP_ALIGN */ + (FD_VOTE_STATES_CHAIN_CNT_EST * sizeof(ulong))
     171             : 
     172             : FD_PROTOTYPES_BEGIN
     173             : 
     174             : /* fd_vote_states_align returns the minimum alignment required for a
     175             :    vote states struct. */
     176             : 
     177             : FD_FN_CONST ulong
     178             : fd_vote_states_align( void );
     179             : 
     180             : /* fd_vote_states_footprint returns the footprint of the vote states
     181             :    struct for a given amount of max vote accounts. */
     182             : 
     183             : FD_FN_CONST ulong
     184             : fd_vote_states_footprint( ulong max_vote_accounts );
     185             : 
     186             : /* fd_vote_states_new creates a new vote states struct with a given
     187             :    number of max vote accounts and a seed. It formats a memory region
     188             :    which is sized based off of the number of vote accounts. */
     189             : 
     190             : void *
     191             : fd_vote_states_new( void * mem,
     192             :                     ulong  max_vote_accounts,
     193             :                     ulong  seed );
     194             : 
     195             : /* fd_vote_states_join joins a vote states struct from a
     196             :    memory region. There can be multiple valid joins for a given memory
     197             :    region but the caller is responsible for accessing memory in a
     198             :    thread-safe manner. */
     199             : 
     200             : fd_vote_states_t *
     201             : fd_vote_states_join( void * mem );
     202             : 
     203             : /* fd_vote_states_update inserts or updates the vote state corresponding
     204             :    to a given account. */
     205             : 
     206             : fd_vote_state_ele_t *
     207             : fd_vote_states_update( fd_vote_states_t *  vote_states,
     208             :                        fd_pubkey_t const * vote_account );
     209             : 
     210             : /* fd_vote_states_update_from_account inserts or updates the vote state
     211             :    corresponding to a valid vote account. This is the same as
     212             :    fd_vote_states_update but is also responsible for decoding the vote
     213             :    account data into a versioned vote state object and extracing the
     214             :    commission and credits. Kills the client if the vote state cannot
     215             :    be decoded. */
     216             : 
     217             : fd_vote_state_ele_t *
     218             : fd_vote_states_update_from_account( fd_vote_states_t *  vote_states,
     219             :                                     fd_pubkey_t const * vote_account,
     220             :                                     uchar const *       account_data,
     221             :                                     ulong               account_data_len );
     222             : 
     223             : /* fd_vote_states_reset_stakes_t resets the stakes to 0 for each of the
     224             :    vote accounts in fd_vote_states_t. */
     225             : 
     226             : void
     227             : fd_vote_states_reset_stakes( fd_vote_states_t * vote_states );
     228             : 
     229             : /* fd_vote_states_remove removes the vote state corresponding to a given
     230             :    account. Does nothing if the account does not exist. */
     231             : 
     232             : void
     233             : fd_vote_states_remove( fd_vote_states_t *  vote_states,
     234             :                        fd_pubkey_t const * vote_account );
     235             : 
     236             : /* fd_vote_states_query returns the vote state corresponding to a given
     237             :    account. Returns NULL if the account does not exist. This function is
     238             :    safe for concurrent reads, but the caller needs to synchronize
     239             :    concurrent writers to the fd_vote_state_ele_t. */
     240             : 
     241             : fd_vote_state_ele_t *
     242             : fd_vote_states_query( fd_vote_states_t const * vote_states,
     243             :                       fd_pubkey_t const *      vote_account );
     244             : 
     245             : /* fd_vote_states_query_const is the same as fd_vote_states but instead
     246             :    returns a const pointer. */
     247             : 
     248             : fd_vote_state_ele_t const *
     249             : fd_vote_states_query_const( fd_vote_states_t const * vote_states,
     250             :                             fd_pubkey_t const *      vote_account );
     251             : 
     252             : /* fd_vote_states_max returns the maximum number of vote accounts that
     253             :    the vote states struct can support. */
     254             : 
     255             : ulong
     256             : fd_vote_states_max( fd_vote_states_t const * vote_states );
     257             : 
     258             : /* fd_vote_states_cnt returns the number of vote states in the vote
     259             :    states struct. */
     260             : 
     261             : ulong
     262             : fd_vote_states_cnt( fd_vote_states_t const * vote_states );
     263             : 
     264             : /* Iterator API for vote states.  The iterator is initialized with a
     265             :    call to fd_vote_states_iter_init.  The caller is responsible for
     266             :    managing the memory for the iterator.  It is safe to call
     267             :    fd_vote_states_iter_next if the result of
     268             :    fd_vote_states_iter_done() ==0.  It is safe to call
     269             :    fd_vote_states_iter_ele() to get the current vote state. As a note,
     270             :    it is safe to modify the vote state acquired from
     271             :    fd_vote_states_iter_ele() as long as the next_ field is not modified
     272             :    (which the caller should never do).  It is unsafe to insert or remove
     273             :    fd_vote_state_ele_t from the vote states struct while iterating.
     274             : 
     275             :    Under the hood, the iterator is just a wrapper over the iterator in
     276             :    fd_map_chain.c.
     277             : 
     278             :    Example use:
     279             : 
     280             :    fd_vote_states_iter_t iter_[1];
     281             :    for( fd_vote_states_iter_t * iter = fd_vote_states_iter_init( vote_states, iter_ ); !fd_vote_states_iter_done( iter ); fd_vote_states_iter_next( iter ) ) {
     282             :      fd_vote_state_ele_t * vote_state = fd_vote_states_iter_ele( iter );
     283             :      // Do something with the vote state ...
     284             :    }
     285             : */
     286             : 
     287             : fd_vote_state_ele_t *
     288             : fd_vote_states_iter_ele( fd_vote_states_iter_t * iter );
     289             : 
     290             : fd_vote_states_iter_t *
     291             : fd_vote_states_iter_init( fd_vote_states_iter_t *  iter,
     292             :                           fd_vote_states_t const * vote_states );
     293             : 
     294             : int
     295             : fd_vote_states_iter_done( fd_vote_states_iter_t * iter );
     296             : 
     297             : void
     298             : fd_vote_states_iter_next( fd_vote_states_iter_t * iter );
     299             : 
     300             : FD_PROTOTYPES_END
     301             : 
     302             : #endif /* HEADER_fd_src_flamenco_stakes_fd_vote_states_h */

Generated by: LCOV version 1.14