LCOV - code coverage report
Current view: top level - flamenco/stakes - fd_stake_delegations.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 15 17 88.2 %
Date: 2026-03-31 06:22:16 Functions: 3 176 1.7 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_flamenco_stakes_fd_stake_delegations_h
       2             : #define HEADER_fd_src_flamenco_stakes_fd_stake_delegations_h
       3             : 
       4             : #include "../rewards/fd_rewards_base.h"
       5             : #include "../runtime/fd_cost_tracker.h"
       6             : #include "../../disco/pack/fd_pack.h" /* TODO: Layering violation */
       7             : #include "../../disco/pack/fd_pack_cost.h"
       8             : #include "../../util/tmpl/fd_map.h"
       9             : 
      10         396 : #define FD_STAKE_DELEGATIONS_MAGIC (0xF17EDA2CE757A3E0) /* FIREDANCER STAKE V0 */
      11             : 
      12             : /* fd_stake_delegations_t is a cache of stake accounts mapping the
      13             :    pubkey of the stake account to various information including
      14             :    stake, activation/deactivation epoch, corresponding vote_account,
      15             :    credits observed, and warmup cooldown rate. This is used to quickly
      16             :    iterate through all of the stake delegations in the system during
      17             :    epoch boundary reward calculations.
      18             : 
      19             :    The implementation of fd_stake_delegations_t is split into two:
      20             :    1. The entire set of stake delegations are stored in the root as a
      21             :       map/pool pair.  This root state is setup at boot (on snapshot
      22             :       load) and is not directly modified after that point.
      23             :    2. As banks/forks execute, they will maintain a delta-based
      24             :       representation of the stake delegations.  Each fork will hold its
      25             :       own set of deltas.  These are then applied to the root set when
      26             :       the fork is finalized.  This is implemented as each bank having
      27             :       its own dlist of deltas which are allocated from a pool which is
      28             :       shared across all stake delegation forks.  The caller is expected
      29             :       to create a new fork index for each bank and add deltas to it.
      30             : 
      31             :    There are some important invariants wrt fd_stake_delegations_t:
      32             :    1. After execution has started, there will be no invalid stake
      33             :       accounts in the stake delegations struct.
      34             :    2. The stake delegations struct can have valid delegations for vote
      35             :       accounts which no longer exist.
      36             :    3. There are no stake accounts which are valid delegations which
      37             :       exist in the accounts database but not in fd_stake_delegations_t.
      38             : 
      39             :    In practice, fd_stake_delegations_t are updated in 3 cases:
      40             :    1. During bootup when the snapshot manifest is loaded in. The cache
      41             :       is also refreshed during the bootup process to ensure that the
      42             :       states are valid and up-to-date.
      43             : 
      44             :       The reason we can't populate the stake accounts from the cache
      45             :       is because the cache in the manifest is partially incomplete:
      46             :       all of the expected keys are there, but the values are not.
      47             :       Notably, the credits_observed field is not available until all of
      48             :       the accounts are loaded into the database.
      49             : 
      50             :       https://github.com/anza-xyz/agave/blob/v2.3.6/runtime/src/bank.rs#L1780-L1806
      51             : 
      52             :    2. After transaction execution. If an update is made to a stake
      53             :       account, the updated state is reflected in the cache (or the entry
      54             :       is evicted).
      55             :    3. During rewards distribution. Stake accounts are partitioned over
      56             :       several hundred slots where their rewards are distributed. In this
      57             :       case, the cache is updated to reflect each stake account post
      58             :       reward distribution.
      59             :    The stake accounts are read-only during the epoch boundary.
      60             : 
      61             :    The concurrency model is limited: most operations are not allowed to
      62             :    be concurrent with each other with the exception of operations that
      63             :    operate on the stake delegations's delta pool:
      64             :     fd_stake_delegations_fork_update()
      65             :     fd_stake_delegations_fork_remove()
      66             :     fd_stake_delegations_evict_fork()
      67             :    These operations are internally synchronized with a read-write lock
      68             :    because multiple executor tiles may be trying to call
      69             :    stake_delegations_fork_update() at the same time, and the replay tile
      70             :    can simulatenously be calling fd_stake_delegations_evict_fork()
      71             :    */
      72             : 
      73       12192 : #define FD_STAKE_DELEGATIONS_ALIGN (128UL)
      74             : 
      75             : #define FD_STAKE_DELEGATIONS_FORK_MAX (4096UL)
      76             : 
      77             : /* The warmup cooldown rate can only be one of two values: 0.25 or 0.09.
      78             :    The reason that the double is mapped to an enum is to save space in
      79             :    the stake delegations struct. */
      80         525 : #define FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_025 (0)
      81         132 : #define FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_009 (1)
      82         123 : #define FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_025      (0.25)
      83         348 : #define FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_009      (0.09)
      84             : 
      85             : struct fd_stake_delegation {
      86             :   fd_pubkey_t stake_account;
      87             :   fd_pubkey_t vote_account;
      88             :   ulong       stake;
      89             :   ulong       credits_observed;
      90             :   uint        next_; /* Internal pool/map/dlist usage */
      91             : 
      92             :   union {
      93             :     uint      prev_; /* Internal dlist usage for delta  */
      94             :     uint      delta_idx; /* Tracking for stake delegation iteration */
      95             :   };
      96             :   ushort      activation_epoch;
      97             :   ushort      deactivation_epoch;
      98             :   union {
      99             :     uchar     is_tombstone; /* Internal dlist/delta usage */
     100             :     uchar     dne_in_root;  /* Tracking for stake delegation iteration */
     101             :   };
     102             :   uchar       warmup_cooldown_rate; /* enum representing 0.25 or 0.09 */
     103             : };
     104             : typedef struct fd_stake_delegation fd_stake_delegation_t;
     105             : 
     106             : struct fd_stake_delegations {
     107             :   ulong magic;
     108             :   ulong expected_stake_accounts_;
     109             :   ulong max_stake_accounts_;
     110             : 
     111             :   /* Root map + pool */
     112             :   ulong map_offset_;
     113             :   ulong pool_offset_;
     114             : 
     115             :   /* Delta pool + fork  */
     116             :   ulong       delta_pool_offset_;
     117             :   ulong       fork_pool_offset_;
     118             :   ulong       dlist_offsets_[ FD_STAKE_DELEGATIONS_FORK_MAX ];
     119             :   fd_rwlock_t delta_lock;
     120             : 
     121             :   /* Stake totals for the current root. */
     122             :   ulong effective_stake;
     123             :   ulong activating_stake;
     124             :   ulong deactivating_stake;
     125             : };
     126             : typedef struct fd_stake_delegations fd_stake_delegations_t;
     127             : 
     128             : /* Forward declare map iterator API generated by fd_map_chain.c */
     129             : typedef struct root_map_private root_map_t;
     130             : typedef struct fd_map_chain_iter fd_stake_delegation_map_iter_t;
     131             : struct fd_stake_delegations_iter {
     132             :   root_map_t *                   root_map;
     133             :   fd_stake_delegation_t *        root_pool;
     134             :   fd_stake_delegation_t *        delta_pool;
     135             :   fd_stake_delegation_map_iter_t iter;
     136             : };
     137             : typedef struct fd_stake_delegations_iter fd_stake_delegations_iter_t;
     138             : 
     139             : FD_PROTOTYPES_BEGIN
     140             : 
     141             : static inline double
     142         462 : fd_stake_delegations_warmup_cooldown_rate_to_double( uchar warmup_cooldown_rate ) {
     143         462 :   return warmup_cooldown_rate==FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_025 ? FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_025 : FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_009;
     144         462 : }
     145             : 
     146             : static inline uchar
     147         159 : fd_stake_delegations_warmup_cooldown_rate_enum( double warmup_cooldown_rate ) {
     148             :   /* TODO: Replace with fd_double_eq */
     149         159 :   if( FD_LIKELY( warmup_cooldown_rate==FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_025 ) ) {
     150          60 :     return FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_025;
     151          99 :   } else if( FD_LIKELY( warmup_cooldown_rate==FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_009 ) ) {
     152          99 :     return FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_009;
     153          99 :   }
     154           0 :   FD_LOG_CRIT(( "Invalid warmup cooldown rate %f", warmup_cooldown_rate ));
     155           0 : }
     156             : 
     157             : /* fd_stake_delegations_align returns the alignment of the stake
     158             :    delegations struct. */
     159             : 
     160             : ulong
     161             : fd_stake_delegations_align( void );
     162             : 
     163             : /* fd_stake_delegations_footprint returns the footprint of the stake
     164             :    delegations struct for a given amount of max stake accounts,
     165             :    expected stake accounts, and max live slots. */
     166             : 
     167             : ulong
     168             : fd_stake_delegations_footprint( ulong max_stake_accounts,
     169             :                                 ulong expected_stake_accounts,
     170             :                                 ulong max_live_slots );
     171             : 
     172             : /* fd_stake_delegations_new creates a new stake delegations struct
     173             :    with a given amount of max and expected stake accounts and max live
     174             :    slots.  It formats a memory region which is sized based off the pool
     175             :    capacity, expected map occupancy, and per-fork delta structures. */
     176             : 
     177             : void *
     178             : fd_stake_delegations_new( void * mem,
     179             :                           ulong  seed,
     180             :                           ulong  max_stake_accounts,
     181             :                           ulong  expected_stake_accounts,
     182             :                           ulong  max_live_slots );
     183             : 
     184             : /* fd_stake_delegations_join joins a stake delegations struct from a
     185             :    memory region. There can be multiple valid joins for a given memory
     186             :    region but the caller is responsible for accessing memory in a
     187             :    thread-safe manner. */
     188             : 
     189             : fd_stake_delegations_t *
     190             : fd_stake_delegations_join( void * mem );
     191             : 
     192             : /* fd_stake_delegations_init resets the state of a valid join of a
     193             :    stake delegations struct.  Specifically, it only resets the root
     194             :    state, leaving the deltas intact. */
     195             : 
     196             : void
     197             : fd_stake_delegations_init( fd_stake_delegations_t * stake_delegations );
     198             : 
     199             : /* fd_stake_delegation_root_query looks up the stake delegation for the
     200             :    given stake account in the root map. */
     201             : 
     202             : fd_stake_delegation_t const *
     203             : fd_stake_delegation_root_query( fd_stake_delegations_t const * stake_delegations,
     204             :                                 fd_pubkey_t const *            stake_account );
     205             : 
     206             : /* fd_stake_delegations_root_update will either insert a new stake
     207             :    delegation if the pubkey doesn't exist yet, or it will update the
     208             :    stake delegation for the pubkey if already in the map, overriding any
     209             :    previous data. fd_stake_delegations_t must be a valid local join. */
     210             : 
     211             : void
     212             : fd_stake_delegations_root_update( fd_stake_delegations_t * stake_delegations,
     213             :                                   fd_pubkey_t const *      stake_account,
     214             :                                   fd_pubkey_t const *      vote_account,
     215             :                                   ulong                    stake,
     216             :                                   ulong                    activation_epoch,
     217             :                                   ulong                    deactivation_epoch,
     218             :                                   ulong                    credits_observed,
     219             :                                   double                   warmup_cooldown_rate );
     220             : 
     221             : /* fd_stake_delegations_refresh is used to refresh the stake
     222             :    delegations stored in fd_stake_delegations_t which is owned by
     223             :    the bank. For a given database handle, read in the state of all
     224             :    stake accounts, decode their state, and update each stake delegation.
     225             :    This is meant to be called before any slots are executed, but after
     226             :    the snapshot has finished loading.
     227             : 
     228             :    Before this function is called, there are some important assumptions
     229             :    made about the state of the stake delegations:
     230             :    1. fd_stake_delegations_t is not missing any valid entries
     231             :    2. fd_stake_delegations_t may have some invalid entries that should
     232             :       be removed
     233             : 
     234             :    fd_stake_delegations_refresh will remove all of the invalid entries
     235             :    that are detected. An entry is considered invalid if the stake
     236             :    account does not exist (e.g. zero balance or no record) or if it
     237             :    has invalid state (e.g. not a stake account or invalid bincode data).
     238             :    No new entries are added to the struct at this point. */
     239             : 
     240             : void
     241             : fd_stake_delegations_refresh( fd_stake_delegations_t *   stake_delegations,
     242             :                               ulong                      epoch,
     243             :                               fd_stake_history_t const * stake_history,
     244             :                               ulong *                    warmup_cooldown_rate_epoch,
     245             :                               fd_accdb_user_t *          accdb,
     246             :                               fd_funk_txn_xid_t const *  xid );
     247             : 
     248             : /* fd_stake_delegations_cnt returns the number of stake delegations
     249             :    in the base of stake delegations struct. */
     250             : 
     251             : ulong
     252             : fd_stake_delegations_cnt( fd_stake_delegations_t const * stake_delegations );
     253             : 
     254             : /* fd_stake_delegations_new_fork allocates a new fork index for the
     255             :    stake delegations.  The fork index is returned to the caller. */
     256             : 
     257             : ushort
     258             : fd_stake_delegations_new_fork( fd_stake_delegations_t * stake_delegations );
     259             : 
     260             : /* fd_stake_delegations_fork_update will insert a new stake delegation
     261             :    delta for the fork.  If an entry already exists in the fork, a new
     262             :    one will be inserted without removing the old one.
     263             : 
     264             :    TODO: Add a per fork map so multiple entries aren't needed for the
     265             :    same stake account. */
     266             : 
     267             : void
     268             : fd_stake_delegations_fork_update( fd_stake_delegations_t * stake_delegations,
     269             :                                   ushort                   fork_idx,
     270             :                                   fd_pubkey_t const *      stake_account,
     271             :                                   fd_pubkey_t const *      vote_account,
     272             :                                   ulong                    stake,
     273             :                                   ulong                    activation_epoch,
     274             :                                   ulong                    deactivation_epoch,
     275             :                                   ulong                    credits_observed,
     276             :                                   double                   warmup_cooldown_rate );
     277             : 
     278             : /* fd_stake_delegations_fork_remove inserts a tombstone stake delegation
     279             :    entry for the given fork.  The function will not actually remove or
     280             :    free any resources corresponding to the stake account.  The reason a
     281             :    tombstone is stored is because each fork corresponds to a set of
     282             :    stake delegation deltas for a given slot.  This function may insert a
     283             :    'duplicate' entry for the same stake account but it will be resolved
     284             :    by the time the delta is applied to a base stake delegations
     285             :    object. */
     286             : 
     287             : void
     288             : fd_stake_delegations_fork_remove( fd_stake_delegations_t * stake_delegations,
     289             :                                   ushort                   fork_idx,
     290             :                                   fd_pubkey_t const *      stake_account );
     291             : 
     292             : /* fd_stake_delegations_evict_fork removes/frees all stake delegation
     293             :    entries for a given fork.  After this function is called it is no
     294             :    longer safe to have any references to the fork index (until it is
     295             :    reused via a call to fd_stake_delegations_new_fork).  The caller is
     296             :    responsible for making sure references to this fork index are not
     297             :    being held. */
     298             : 
     299             : void
     300             : fd_stake_delegations_evict_fork( fd_stake_delegations_t * stake_delegations,
     301             :                                  ushort                   fork_idx );
     302             : 
     303             : /* fd_stake_delegations_apply_fork_delta merges all stake delegation
     304             :    entries for fork_idx into the root map: non-tombstone entries are
     305             :    applied via fd_stake_delegations_root_update; tombstone entries remove
     306             :    the corresponding stake account from the root map.  Caller must
     307             :    ensure no concurrent iteration on stake_delegations for this fork. */
     308             : 
     309             : void
     310             : fd_stake_delegations_apply_fork_delta( ulong                      epoch,
     311             :                                        fd_stake_history_t const * stake_history,
     312             :                                        ulong *                    warmup_cooldown_rate_epoch,
     313             :                                        fd_stake_delegations_t *   stake_delegations,
     314             :                                        ushort                     fork_idx );
     315             : 
     316             : /* fd_stake_delegations_{mark,unmark}_delta are used to temporarily
     317             :    tag delta elements from a given fork in the base/root stake
     318             :    delegation map/pool.  This allows the caller to then iterator over
     319             :    the stake delegations for a given bank using just the deltas and the
     320             :    root without creating a copy.  Each delta that is marked, must be
     321             :    unmarked after the caller is done iterating over the stake
     322             :    delegations.
     323             : 
     324             :    Under the hood, it reuses internal pointers for elements in the root
     325             :    map to point to the corresponding delta element.  If the element is
     326             :    removed by a delta another field will be reused to ignore it during
     327             :    iteration.  If an element is inserted by a delta, it will be
     328             :    temporarily added to the root, but will be removed with a call to
     329             :    unmark_delta.  These functions are also used to temporarily update
     330             :    (and then unwind) the stake totals for the current root. */
     331             : 
     332             : void
     333             : fd_stake_delegations_mark_delta( fd_stake_delegations_t *   stake_delegations,
     334             :                                  ulong                      epoch,
     335             :                                  fd_stake_history_t const * stake_history,
     336             :                                  ulong *                    warmup_cooldown_rate_epoch,
     337             :                                  ushort                     fork_idx );
     338             : 
     339             : void
     340             : fd_stake_delegations_unmark_delta( fd_stake_delegations_t *   stake_delegations,
     341             :                                    ulong                      epoch,
     342             :                                    fd_stake_history_t const * stake_history,
     343             :                                    ulong *                    warmup_cooldown_rate_epoch,
     344             :                                    ushort                     fork_idx );
     345             : 
     346             : /* Iterator API for stake delegations.  The iterator is initialized with
     347             :    a call to fd_stake_delegations_iter_init.  The caller is responsible
     348             :    for managing the memory for the iterator.  It is safe to call
     349             :    fd_stake_delegations_iter_next if the result of
     350             :    fd_stake_delegations_iter_done()==0.  It is safe to call
     351             :    fd_stake_delegations_iter_ele() to get the current stake delegation
     352             :    or fd_stake_delegations_iter_idx() to get the index of the current
     353             :    stake delegation.  It is not safe to modify the stake delegation
     354             :    while iterating through it.
     355             : 
     356             :    Under the hood, the iterator is just a wrapper over the iterator in
     357             :    fd_map_chain.c.
     358             : 
     359             :    Example use:
     360             : 
     361             :    fd_stake_delegations_iter_t iter_[1];
     362             :    for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
     363             :         !fd_stake_delegations_iter_done( iter );
     364             :         fd_stake_delegations_iter_next( iter ) ) {
     365             :      fd_stake_delegation_t * stake_delegation = fd_stake_delegations_iter_ele( iter );
     366             :    }
     367             : */
     368             : 
     369             : fd_stake_delegation_t const *
     370             : fd_stake_delegations_iter_ele( fd_stake_delegations_iter_t * iter );
     371             : 
     372             : ulong
     373             : fd_stake_delegations_iter_idx( fd_stake_delegations_iter_t * iter );
     374             : 
     375             : fd_stake_delegations_iter_t *
     376             : fd_stake_delegations_iter_init( fd_stake_delegations_iter_t *  iter,
     377             :                                 fd_stake_delegations_t const * stake_delegations );
     378             : 
     379             : void
     380             : fd_stake_delegations_iter_next( fd_stake_delegations_iter_t * iter );
     381             : 
     382             : int
     383             : fd_stake_delegations_iter_done( fd_stake_delegations_iter_t * iter );
     384             : 
     385             : FD_PROTOTYPES_END
     386             : 
     387             : #endif /* HEADER_fd_src_flamenco_stakes_fd_stake_delegations_h */

Generated by: LCOV version 1.14