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-01-19 05:27:37 Functions: 1 91 1.1 %

          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         684 : #define FD_STAKE_DELEGATIONS_MAGIC (0xF17EDA2CE757A3E0) /* FIREDANCER STAKE V0 */
      11             : 
      12             : /* fd_stakes_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_stakes_delegations_t is a hash map which
      20             :    is backed by a memory pool. Callers are allowed to insert, replace,
      21             :    and remove entries from the map.
      22             : 
      23             :    fd_stakes_delegations_t can also exist in two modes: with and without
      24             :    tombstones. The mode is determined by the leave_tombstones flag
      25             :    passed to fd_stake_delegations_new. If tombstones are enabled, then
      26             :    calling fd_stake_delegations_remove will not remove the entry from
      27             :    the map, but rather set the is_tombstone flag to true. This is
      28             :    useful for delta updates where we want to keep the entry in the map
      29             :    for future reference. In practice, this struct is used in both modes
      30             :    by the bank. The stake delegations corresponding to each slot are
      31             :    stored in a delta struct which is used to update the main cache.
      32             : 
      33             :    There are some important invariants wrt fd_stake_delegations_t:
      34             :    1. After execution has started, there will be no invalid stake
      35             :       accounts in the stake delegations struct.
      36             :    2. The stake delegations struct can have valid delegations for vote
      37             :       accounts which no longer exist.
      38             :    3. There are no stake accounts which are valid delegations which
      39             :       exist in the accounts database but not in fd_stake_delegations_t.
      40             : 
      41             :    In practice, fd_stakes_delegations_t are updated in 3 cases:
      42             :    1. During bootup when the snapshot manifest is loaded in. The cache
      43             :       is also refreshed during the bootup process to ensure that the
      44             :       states are valid and up-to-date.
      45             : 
      46             :       The reason we can't populate the stake accounts from the cache
      47             :       is because the cache in the manifest is partially incomplete:
      48             :       all of the expected keys are there, but the values are not.
      49             :       Notably, the credits_observed field is not available until all of
      50             :       the accounts are loaded into the database.
      51             : 
      52             :       https://github.com/anza-xyz/agave/blob/v2.3.6/runtime/src/bank.rs#L1780-L1806
      53             : 
      54             :    2. After transaction execution. If an update is made to a stake
      55             :       account, the updated state is reflected in the cache (or the entry
      56             :       is evicted).
      57             :    3. During rewards distribution. Stake accounts are partitioned over
      58             :       several hundred slots where their rewards are distributed. In this
      59             :       case, the cache is updated to reflect each stake account post
      60             :       reward distribution.
      61             :    The stake accounts are read-only during the epoch boundary. */
      62             : 
      63             : /* The max number of stake accounts that can be modified in a current
      64             :    slot from transactions can be bounded based on CU consumption. The
      65             :    best strategy to maximize the max number of stake accounts modified
      66             :    in a single transaction.  A stake program instruction can either
      67             :    modify one or two stake accounts.  Stake program instructions that
      68             :    modify two stake accounts (merge/split) are assumed to be at least 2x
      69             :    as expensive as a stake program instruction that modifies one stake
      70             :    account.  So we will assume that the most efficient strategy is to
      71             :    modify one stake account per instruction and have as many instruction
      72             :    as posssible in this transaction.  We can have 63 stake program
      73             :    instructions in this transaction because one account will be the fee
      74             :    payer/signature and the other 63 are free to be writable accounts.
      75             : 
      76             :    Given the above:
      77             :    100000000 - CUs per slot
      78             :    64 - Max number of writable accounts per transaction.
      79             :    63 - Max number of writable stake accounts per transaction.
      80             :    720 - Cost of a signature
      81             :    300 - Cost of a writable account lock.
      82             :    6000 - Cost of a stake program instruction.
      83             : 
      84             :    We can have (100000000 / (720 + 300 * 64 + 6000 * 63)) = 251
      85             :    optimal stake program transactions per slot.  With 63 stake accounts
      86             :    per transaction, we can have 251 * 63 = 15813 stake accounts modified
      87             :    in a slot. */
      88             : 
      89             : #define MAX_OPTIMAL_STAKE_ACCOUNTS_POSSIBLE_IN_TXN (FD_MAX_BLOCK_UNITS_SIMD_0286/(FD_WRITE_LOCK_UNITS * FD_RUNTIME_MAX_WRITABLE_ACCOUNTS_PER_TRANSACTION + FD_RUNTIME_MIN_STAKE_INSN_CUS * (FD_RUNTIME_MAX_WRITABLE_ACCOUNTS_PER_TRANSACTION - 1UL) + FD_PACK_COST_PER_SIGNATURE))
      90             : FD_STATIC_ASSERT(MAX_OPTIMAL_STAKE_ACCOUNTS_POSSIBLE_IN_TXN==251, "Incorrect MAX_STAKE_ACCOUNTS_POSSIBLE_IN_TXN");
      91             : #define MAX_STAKE_ACCOUNTS_POSSIBLE_IN_SLOT_FROM_TXNS (MAX_OPTIMAL_STAKE_ACCOUNTS_POSSIBLE_IN_TXN * (FD_RUNTIME_MAX_WRITABLE_ACCOUNTS_PER_TRANSACTION - 1UL))
      92             : FD_STATIC_ASSERT(MAX_STAKE_ACCOUNTS_POSSIBLE_IN_SLOT_FROM_TXNS==15813, "Incorrect MAX_STAKE_ACCOUNTS_PER_SLOT");
      93             : 
      94             : /* The static footprint of fd_stake_delegations_t when it is a delta
      95             :    is determined by the max total number of stake accounts that can get
      96             :    changed in a single slot. Stake accounts can get modified in two ways:
      97             :    1. Through transactions. This bound is calculated using CU
      98             :       consumption as described above.
      99             :    2. Through epoch rewards. This is a protocol-level bound is defined
     100             :       in fd_rewards_base.h and is the max number of stake accounts that
     101             :       can reside in a single reward partition. */
     102             : 
     103             : #define FD_STAKE_DELEGATIONS_MAX_PER_SLOT (MAX_STAKE_ACCOUNTS_POSSIBLE_IN_SLOT_FROM_TXNS + STAKE_ACCOUNT_STORES_PER_BLOCK)
     104             : 
     105             : /* The static footprint of the vote states assumes that there are
     106             :    FD_RUNTIME_MAX_STAKE_ACCOUNTS. It also assumes worst case alignment
     107             :    for each struct. fd_stake_delegations_t is laid out as first the
     108             :    fd_stake_delegations_t struct, followed by a pool of
     109             :    fd_stake_delegation_t structs, followed by a map of
     110             :    fd_stake_delegation_map_ele_t structs. The pool has
     111             :    FD_RUNTIME_MAX_STAKE_ACCOUNTS elements, and the map has a chain count
     112             :    determined by a call to fd_stake_delegations_chain_cnt_est.
     113             :    NOTE: the footprint is validated to be at least as large as the
     114             :    actual runtime-determined footprint (see test_stake_delegations.c) */
     115             : 
     116          36 : #define FD_STAKE_DELEGATIONS_CHAIN_CNT_EST (2097152UL)
     117             : #define FD_STAKE_DELEGATIONS_FOOTPRINT                                                         \
     118             :   /* First, layout the struct with alignment */                                                \
     119          36 :   sizeof(fd_stake_delegations_t) + alignof(fd_stake_delegations_t) +                           \
     120          36 :   /* Now layout the pool's data footprint */                                                   \
     121          36 :   FD_STAKE_DELEGATIONS_ALIGN + sizeof(fd_stake_delegation_t) * FD_RUNTIME_MAX_STAKE_ACCOUNTS + \
     122          36 :   /* Now layout the pool's meta footprint */                                                   \
     123          36 :   FD_STAKE_DELEGATIONS_ALIGN + 128UL /* POOL_ALIGN */ +                                        \
     124          36 :   /* Now layout the map.  We must make assumptions about the chain */                          \
     125          36 :   /* count to be equivalent to chain_cnt_est. */                                               \
     126          36 :   FD_STAKE_DELEGATIONS_ALIGN + 128UL /* MAP_ALIGN */ + (FD_STAKE_DELEGATIONS_CHAIN_CNT_EST * sizeof(ulong))
     127             : 
     128             : /* We need a footprint for the max amount of stake delegations that
     129             :    can be added in a single slot. We know that there can be up to
     130             :    8192 writable accounts in a slot (bound determined from the cost
     131             :    tracker). Using the same calculation as above, we get 120 bytes per
     132             :    stake delegation with up to ~19K delegations we have a total
     133             :    footprint of ~2.5MB. */
     134             : 
     135             : #define FD_STAKE_DELEGATIONS_DELTA_CHAIN_CNT_EST (16384UL)
     136             : #define FD_STAKE_DELEGATIONS_DELTA_FOOTPRINT                                                       \
     137             :   /* First, layout the struct with alignment */                                                    \
     138             :   sizeof(fd_stake_delegations_t) + alignof(fd_stake_delegations_t) +                               \
     139             :   /* Now layout the pool's data footprint */                                                       \
     140             :   FD_STAKE_DELEGATIONS_ALIGN + sizeof(fd_stake_delegation_t) * FD_STAKE_DELEGATIONS_MAX_PER_SLOT + \
     141             :   /* Now layout the pool's meta footprint */                                                       \
     142             :   FD_STAKE_DELEGATIONS_ALIGN + 128UL /* POOL_ALIGN */ +                                            \
     143             :   /* Now layout the map.  We must make assumptions about the chain */                              \
     144             :   /* count to be equivalent to chain_cnt_est. */                                                   \
     145             :   FD_STAKE_DELEGATIONS_ALIGN + 128UL /* MAP_ALIGN */ + (FD_STAKE_DELEGATIONS_DELTA_CHAIN_CNT_EST * sizeof(ulong))
     146             : 
     147         108 : #define FD_STAKE_DELEGATIONS_ALIGN (128UL)
     148             : 
     149             : struct fd_stake_delegation {
     150             :   fd_pubkey_t stake_account;
     151             :   fd_pubkey_t vote_account;
     152             :   ulong       next_; /* Only for internal pool/map usage */
     153             :   ulong       stake;
     154             :   ulong       activation_epoch;
     155             :   ulong       deactivation_epoch;
     156             :   ulong       credits_observed;
     157             :   double      warmup_cooldown_rate;
     158             :   int         is_tombstone;
     159             : };
     160             : typedef struct fd_stake_delegation fd_stake_delegation_t;
     161             : 
     162             : struct fd_stake_delegations {
     163             :   ulong magic;
     164             :   ulong map_offset_;
     165             :   ulong pool_offset_;
     166             :   ulong max_stake_accounts_;
     167             :   int   leave_tombstones_;
     168             : };
     169             : typedef struct fd_stake_delegations fd_stake_delegations_t;
     170             : 
     171             : /* Forward declare map iterator API generated by fd_map_chain.c */
     172             : typedef struct fd_stake_delegation_map_private fd_stake_delegation_map_t;
     173             : typedef struct fd_map_chain_iter fd_stake_delegation_map_iter_t;
     174             : struct fd_stake_delegations_iter {
     175             :   fd_stake_delegation_map_t *    map;
     176             :   fd_stake_delegation_t *        pool;
     177             :   fd_stake_delegation_map_iter_t iter;
     178             : };
     179             : typedef struct fd_stake_delegations_iter fd_stake_delegations_iter_t;
     180             : 
     181             : FD_PROTOTYPES_BEGIN
     182             : 
     183             : /* fd_stake_delegations_align returns the alignment of the stake
     184             :    delegations struct. */
     185             : 
     186             : ulong
     187             : fd_stake_delegations_align( void );
     188             : 
     189             : /* fd_stake_delegations_footprint returns the footprint of the stake
     190             :    delegations struct for a given amount of max stake accounts. */
     191             : 
     192             : ulong
     193             : fd_stake_delegations_footprint( ulong max_stake_accounts );
     194             : 
     195             : /* fd_stake_delegations_new creates a new stake delegations struct
     196             :    with a given amount of max stake accounts. It formats a memory region
     197             :    which is sized based off of the number of stake accounts. The struct
     198             :    can optionally be configured to leave tombstones in the map. This is
     199             :    useful if fd_stake_delegations is being used as a delta. */
     200             : 
     201             : void *
     202             : fd_stake_delegations_new( void * mem,
     203             :                           ulong  seed,
     204             :                           ulong  max_stake_accounts,
     205             :                           int    leave_tombstones );
     206             : 
     207             : /* fd_stake_delegations_join joins a stake delegations struct from a
     208             :    memory region. There can be multiple valid joins for a given memory
     209             :    region but the caller is responsible for accessing memory in a
     210             :    thread-safe manner. */
     211             : 
     212             : fd_stake_delegations_t *
     213             : fd_stake_delegations_join( void * mem );
     214             : 
     215             : /* fd_stake_delegations_leave returns the stake delegations struct
     216             :    from a memory region. */
     217             : 
     218             : void *
     219             : fd_stake_delegations_leave( fd_stake_delegations_t * self );
     220             : 
     221             : /* fd_stake_delegations_delete unformats a memory region that was
     222             :    formatted by fd_stake_delegations_new. */
     223             : 
     224             : void *
     225             : fd_stake_delegations_delete( void * mem );
     226             : 
     227             : /* fd_stake_delegations_init resets the state of a valid join of a
     228             :    stake delegations struct. */
     229             : 
     230             : void
     231             : fd_stake_delegations_init( fd_stake_delegations_t * stake_delegations );
     232             : 
     233             : /* fd_stake_delegations_update will either insert a new stake delegation
     234             :    if the pubkey doesn't exist yet, or it will update the stake
     235             :    delegation for the pubkey if already in the map, overriding any
     236             :    previous data. fd_stake_delegations_t must be a valid local join.
     237             : 
     238             :    NOTE: This function CAN be called while iterating over the map, but
     239             :    ONLY for keys which already exist in the map. */
     240             : 
     241             : void
     242             : fd_stake_delegations_update( fd_stake_delegations_t * stake_delegations,
     243             :                              fd_pubkey_t const *      stake_account,
     244             :                              fd_pubkey_t const *      vote_account,
     245             :                              ulong                    stake,
     246             :                              ulong                    activation_epoch,
     247             :                              ulong                    deactivation_epoch,
     248             :                              ulong                    credits_observed,
     249             :                              double                   warmup_cooldown_rate );
     250             : 
     251             : /* fd_stake_delegations_remove removes a stake delegation corresponding
     252             :    to a stake account's pubkey if one exists. Nothing happens if the
     253             :    key doesn't exist in the stake delegations. fd_stake_delegations_t
     254             :    must be a valid local join.
     255             : 
     256             :    NOTE: If the leave_tombstones flag is set, then the entry is not
     257             :    removed from the map, but rather set to a tombstone. If the
     258             :    delegation does not exist in the map, then a tombstone is actually
     259             :    inserted into the struct. */
     260             : 
     261             : void
     262             : fd_stake_delegations_remove( fd_stake_delegations_t * stake_delegations,
     263             :                              fd_pubkey_t const *      stake_account );
     264             : 
     265             : 
     266             : /* fd_stake_delegations_query returns the stake delegation for a
     267             :    stake account's pubkey if one exists. If one does not exist, returns
     268             :    NULL. fd_stake_delegations_t must be a valid local join. */
     269             : 
     270             : fd_stake_delegation_t const *
     271             : fd_stake_delegations_query( fd_stake_delegations_t const * stake_delegations,
     272             :                             fd_pubkey_t const *            stake_account );
     273             : 
     274             : /* fd_stake_delegations_refresh is used to refresh the stake
     275             :    delegations stored in fd_stake_delegations_t which is owned by
     276             :    the bank. For a given database handle, read in the state of all
     277             :    stake accounts, decode their state, and update each stake delegation.
     278             :    This is meant to be called before any slots are executed, but after
     279             :    the snapshot has finished loading.
     280             : 
     281             :    Before this function is called, there are some important assumptions
     282             :    made about the state of the stake delegations that are enforced by
     283             :    the Agave client:
     284             :    1. fd_stake_delegations_t is not missing any valid entries
     285             :    2. fd_stake_delegations_t may have some invalid entries that should
     286             :       be removed
     287             : 
     288             :    fd_stake_delegations_refresh will remove all of the invalid entries
     289             :    that are detected. An entry is considered invalid if the stake
     290             :    account does not exist (e.g. zero balance or no record) or if it
     291             :    has invalid state (e.g. not a stake account or invalid bincode data).
     292             :    No new entries are added to the struct at this point. */
     293             : 
     294             : void
     295             : fd_stake_delegations_refresh( fd_stake_delegations_t *  stake_delegations,
     296             :                               fd_accdb_user_t *         accdb,
     297             :                               fd_funk_txn_xid_t const * xid );
     298             : 
     299             : /* fd_stake_delegations_cnt returns the number of stake delegations
     300             :    in the stake delegations struct. fd_stake_delegations_t must be a
     301             :    valid local join.
     302             : 
     303             :    NOTE: The cnt will return the number of stake delegations that are
     304             :    in the underlying map. This number includes tombstones if the
     305             :    leave_tombstones flag is set. */
     306             : 
     307             : ulong
     308             : fd_stake_delegations_cnt( fd_stake_delegations_t const * stake_delegations );
     309             : 
     310             : static inline ulong
     311           3 : fd_stake_delegations_max( fd_stake_delegations_t const * stake_delegations ) {
     312           3 :   if( FD_UNLIKELY( !stake_delegations ) ) {
     313           0 :     FD_LOG_CRIT(( "NULL stake_delegations" ));
     314           0 :   }
     315             : 
     316           3 :   return stake_delegations->max_stake_accounts_;
     317           3 : }
     318             : 
     319             : /* Iterator API for stake delegations. The iterator is initialized with
     320             :    a call to fd_stake_delegations_iter_init. The caller is responsible
     321             :    for managing the memory for the iterator. It is safe to call
     322             :    fd_stake_delegations_iter_next if the result of
     323             :    fd_stake_delegations_iter_done() ==0. It is safe to call
     324             :    fd_stake_delegations_iter_ele() to get the current stake delegation.
     325             :    As a note, it is safe to modify the stake delegation acquired from
     326             :    fd_stake_delegations_iter_ele() as long as the next_ field is not
     327             :    modified (which the caller should never do). It is unsafe to insert
     328             :    or remove fd_stake_delegation_t from the stake delegations struct
     329             :    while iterating.
     330             : 
     331             :    Under the hood, the iterator is just a wrapper over the iterator in
     332             :    fd_map_chain.c.
     333             : 
     334             :    Example use:
     335             : 
     336             :    fd_stake_delegations_iter_t iter_[1];
     337             :    for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations ); !fd_stake_delegations_iter_done( iter ); fd_stake_delegations_iter_next( iter ) ) {
     338             :      fd_stake_delegation_t * stake_delegation = fd_stake_delegations_iter_ele( iter );
     339             :      // Do something with the stake delegation ...
     340             :    }
     341             : */
     342             : 
     343             : fd_stake_delegation_t *
     344             : fd_stake_delegations_iter_ele( fd_stake_delegations_iter_t * iter );
     345             : 
     346             : fd_stake_delegations_iter_t *
     347             : fd_stake_delegations_iter_init( fd_stake_delegations_iter_t *  iter,
     348             :                                 fd_stake_delegations_t const * stake_delegations );
     349             : 
     350             : void
     351             : fd_stake_delegations_iter_next( fd_stake_delegations_iter_t * iter );
     352             : 
     353             : int
     354             : fd_stake_delegations_iter_done( fd_stake_delegations_iter_t * iter );
     355             : 
     356             : FD_PROTOTYPES_END
     357             : 
     358             : #endif /* HEADER_fd_src_flamenco_stakes_fd_stake_delegations_h */

Generated by: LCOV version 1.14