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 */