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 594 : #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 18438 : #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 4122 : #define FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_025 (0)
81 207 : #define FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_009 (1)
82 276 : #define FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_025 (0.25)
83 321 : #define FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_009 (0.09)
84 :
85 : /* fd_stake_warmup_cooldown_rate gives the warmup/cooldown rate enum
86 : for a given epoch. In Agave, the per-delegation warmup_cooldown_rate
87 : field was deprecated (since v1.16.7) and unused in calculations.
88 : The rate is always determined by the epoch. */
89 :
90 : static inline uchar
91 69 : fd_stake_warmup_cooldown_rate( ulong current_epoch, ulong * new_rate_activation_epoch ) {
92 69 : ulong activation_epoch = new_rate_activation_epoch ? *new_rate_activation_epoch : ULONG_MAX;
93 69 : return current_epoch<activation_epoch
94 69 : ? (uchar)FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_025
95 69 : : (uchar)FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_009;
96 69 : }
97 :
98 : struct fd_stake_delegation {
99 : fd_pubkey_t stake_account;
100 : fd_pubkey_t vote_account;
101 : ulong stake;
102 : ulong credits_observed;
103 : uint next_; /* Internal pool/map/dlist usage */
104 :
105 : union {
106 : uint prev_; /* Internal dlist usage for delta */
107 : uint delta_idx; /* Tracking for stake delegation iteration */
108 : };
109 : ushort activation_epoch;
110 : ushort deactivation_epoch;
111 : union {
112 : uchar is_tombstone; /* Internal dlist/delta usage */
113 : uchar dne_in_root; /* Tracking for stake delegation iteration */
114 : };
115 : uchar warmup_cooldown_rate; /* enum representing 0.25 or 0.09 */
116 : };
117 : typedef struct fd_stake_delegation fd_stake_delegation_t;
118 :
119 : struct fd_stake_delegations {
120 : ulong magic;
121 : ulong expected_stake_accounts_;
122 : ulong max_stake_accounts_;
123 :
124 : /* Root map + pool */
125 : ulong map_offset_;
126 : ulong pool_offset_;
127 :
128 : /* Delta pool + fork */
129 : ulong delta_pool_offset_;
130 : ulong fork_pool_offset_;
131 : ulong dlist_offsets_[ FD_STAKE_DELEGATIONS_FORK_MAX ];
132 : fd_rwlock_t delta_lock;
133 :
134 : /* Stake totals for the current root. */
135 : ulong effective_stake;
136 : ulong activating_stake;
137 : ulong deactivating_stake;
138 : };
139 : typedef struct fd_stake_delegations fd_stake_delegations_t;
140 :
141 : /* Forward declare map iterator API generated by fd_map_chain.c */
142 : typedef struct root_map_private root_map_t;
143 : typedef struct fd_map_chain_iter fd_stake_delegation_map_iter_t;
144 : struct fd_stake_delegations_iter {
145 : root_map_t * root_map;
146 : fd_stake_delegation_t * root_pool;
147 : fd_stake_delegation_t * delta_pool;
148 : fd_stake_delegation_map_iter_t iter;
149 : };
150 : typedef struct fd_stake_delegations_iter fd_stake_delegations_iter_t;
151 :
152 : FD_PROTOTYPES_BEGIN
153 :
154 : static inline double
155 597 : fd_stake_delegations_warmup_cooldown_rate_to_double( uchar warmup_cooldown_rate ) {
156 597 : 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;
157 597 : }
158 :
159 :
160 : /* fd_stake_delegations_align returns the alignment of the stake
161 : delegations struct. */
162 :
163 : ulong
164 : fd_stake_delegations_align( void );
165 :
166 : /* fd_stake_delegations_footprint returns the footprint of the stake
167 : delegations struct for a given amount of max stake accounts,
168 : expected stake accounts, and max live slots. */
169 :
170 : ulong
171 : fd_stake_delegations_footprint( ulong max_stake_accounts,
172 : ulong expected_stake_accounts,
173 : ulong max_live_slots );
174 :
175 : /* fd_stake_delegations_new creates a new stake delegations struct
176 : with a given amount of max and expected stake accounts and max live
177 : slots. It formats a memory region which is sized based off the pool
178 : capacity, expected map occupancy, and per-fork delta structures. */
179 :
180 : void *
181 : fd_stake_delegations_new( void * mem,
182 : ulong seed,
183 : ulong max_stake_accounts,
184 : ulong expected_stake_accounts,
185 : ulong max_live_slots );
186 :
187 : /* fd_stake_delegations_join joins a stake delegations struct from a
188 : memory region. There can be multiple valid joins for a given memory
189 : region but the caller is responsible for accessing memory in a
190 : thread-safe manner. */
191 :
192 : fd_stake_delegations_t *
193 : fd_stake_delegations_join( void * mem );
194 :
195 : /* fd_stake_delegations_reset resets delegations to the post-new state. */
196 :
197 : void
198 : fd_stake_delegations_reset( fd_stake_delegations_t * stake_delegations );
199 :
200 : /* fd_stake_delegation_root_query looks up the stake delegation for the
201 : given stake account in the root map. */
202 :
203 : fd_stake_delegation_t const *
204 : fd_stake_delegation_root_query( fd_stake_delegations_t const * stake_delegations,
205 : fd_pubkey_t const * stake_account );
206 :
207 : /* fd_stake_delegations_root_update will either insert a new stake
208 : delegation if the pubkey doesn't exist yet, or it will update the
209 : stake delegation for the pubkey if already in the map, overriding any
210 : previous data. fd_stake_delegations_t must be a valid local join. */
211 :
212 : void
213 : fd_stake_delegations_root_update( fd_stake_delegations_t * stake_delegations,
214 : fd_pubkey_t const * stake_account,
215 : fd_pubkey_t const * vote_account,
216 : ulong stake,
217 : ulong activation_epoch,
218 : ulong deactivation_epoch,
219 : ulong credits_observed,
220 : uchar warmup_cooldown_rate );
221 :
222 : /* fd_stake_delegations_refresh is used to refresh the stake
223 : delegations stored in fd_stake_delegations_t which is owned by
224 : the bank. For a given database handle, read in the state of all
225 : stake accounts, decode their state, and update each stake delegation.
226 : This is meant to be called before any slots are executed, but after
227 : the snapshot has finished loading.
228 :
229 : Before this function is called, there are some important assumptions
230 : made about the state of the stake delegations:
231 : 1. fd_stake_delegations_t is not missing any valid entries
232 : 2. fd_stake_delegations_t may have some invalid entries that should
233 : be removed
234 :
235 : fd_stake_delegations_refresh will remove all of the invalid entries
236 : that are detected. An entry is considered invalid if the stake
237 : account does not exist (e.g. zero balance or no record) or if it
238 : has invalid state (e.g. not a stake account or invalid bincode data).
239 : No new entries are added to the struct at this point. */
240 :
241 : void
242 : fd_stake_delegations_refresh( fd_stake_delegations_t * stake_delegations,
243 : ulong epoch,
244 : fd_stake_history_t const * stake_history,
245 : ulong * warmup_cooldown_rate_epoch,
246 : fd_accdb_user_t * accdb,
247 : fd_funk_txn_xid_t const * xid );
248 :
249 : /* fd_stake_delegations_cnt returns the number of stake delegations
250 : in the base of stake delegations struct. */
251 :
252 : ulong
253 : fd_stake_delegations_cnt( fd_stake_delegations_t const * stake_delegations );
254 :
255 : /* fd_stake_delegations_new_fork allocates a new fork index for the
256 : stake delegations. The fork index is returned to the caller. */
257 :
258 : ushort
259 : fd_stake_delegations_new_fork( fd_stake_delegations_t * stake_delegations );
260 :
261 : /* fd_stake_delegations_fork_update will insert a new stake delegation
262 : delta for the fork. If an entry already exists in the fork, a new
263 : one will be inserted without removing the old one.
264 :
265 : TODO: Add a per fork map so multiple entries aren't needed for the
266 : same stake account. */
267 :
268 : void
269 : fd_stake_delegations_fork_update( fd_stake_delegations_t * stake_delegations,
270 : ushort fork_idx,
271 : fd_pubkey_t const * stake_account,
272 : fd_pubkey_t const * vote_account,
273 : ulong stake,
274 : ulong activation_epoch,
275 : ulong deactivation_epoch,
276 : ulong credits_observed,
277 : uchar warmup_cooldown_rate );
278 :
279 : /* fd_stake_delegations_fork_remove inserts a tombstone stake delegation
280 : entry for the given fork. The function will not actually remove or
281 : free any resources corresponding to the stake account. The reason a
282 : tombstone is stored is because each fork corresponds to a set of
283 : stake delegation deltas for a given slot. This function may insert a
284 : 'duplicate' entry for the same stake account but it will be resolved
285 : by the time the delta is applied to a base stake delegations
286 : object. */
287 :
288 : void
289 : fd_stake_delegations_fork_remove( fd_stake_delegations_t * stake_delegations,
290 : ushort fork_idx,
291 : fd_pubkey_t const * stake_account );
292 :
293 : /* fd_stake_delegations_evict_fork removes/frees all stake delegation
294 : entries for a given fork. After this function is called it is no
295 : longer safe to have any references to the fork index (until it is
296 : reused via a call to fd_stake_delegations_new_fork). The caller is
297 : responsible for making sure references to this fork index are not
298 : being held. */
299 :
300 : void
301 : fd_stake_delegations_evict_fork( fd_stake_delegations_t * stake_delegations,
302 : ushort fork_idx );
303 :
304 : /* fd_stake_delegations_apply_fork_delta merges all stake delegation
305 : entries for fork_idx into the root map: non-tombstone entries are
306 : applied via fd_stake_delegations_root_update; tombstone entries remove
307 : the corresponding stake account from the root map. Caller must
308 : ensure no concurrent iteration on stake_delegations for this fork. */
309 :
310 : void
311 : fd_stake_delegations_apply_fork_delta( ulong epoch,
312 : fd_stake_history_t const * stake_history,
313 : ulong * warmup_cooldown_rate_epoch,
314 : fd_stake_delegations_t * stake_delegations,
315 : ushort fork_idx );
316 :
317 : /* fd_stake_delegations_{mark,unmark}_delta are used to temporarily
318 : tag delta elements from a given fork in the base/root stake
319 : delegation map/pool. This allows the caller to then iterator over
320 : the stake delegations for a given bank using just the deltas and the
321 : root without creating a copy. Each delta that is marked, must be
322 : unmarked after the caller is done iterating over the stake
323 : delegations.
324 :
325 : Under the hood, it reuses internal pointers for elements in the root
326 : map to point to the corresponding delta element. If the element is
327 : removed by a delta another field will be reused to ignore it during
328 : iteration. If an element is inserted by a delta, it will be
329 : temporarily added to the root, but will be removed with a call to
330 : unmark_delta. These functions are also used to temporarily update
331 : (and then unwind) the stake totals for the current root. */
332 :
333 : void
334 : fd_stake_delegations_mark_delta( fd_stake_delegations_t * stake_delegations,
335 : ulong epoch,
336 : fd_stake_history_t const * stake_history,
337 : ulong * warmup_cooldown_rate_epoch,
338 : ushort fork_idx );
339 :
340 : void
341 : fd_stake_delegations_unmark_delta( fd_stake_delegations_t * stake_delegations,
342 : ulong epoch,
343 : fd_stake_history_t const * stake_history,
344 : ulong * warmup_cooldown_rate_epoch,
345 : ushort fork_idx );
346 :
347 : /* Iterator API for stake delegations. The iterator is initialized with
348 : a call to fd_stake_delegations_iter_init. The caller is responsible
349 : for managing the memory for the iterator. It is safe to call
350 : fd_stake_delegations_iter_next if the result of
351 : fd_stake_delegations_iter_done()==0. It is safe to call
352 : fd_stake_delegations_iter_ele() to get the current stake delegation
353 : or fd_stake_delegations_iter_idx() to get the index of the current
354 : stake delegation. It is not safe to modify the stake delegation
355 : while iterating through it.
356 :
357 : Under the hood, the iterator is just a wrapper over the iterator in
358 : fd_map_chain.c.
359 :
360 : Example use:
361 :
362 : fd_stake_delegations_iter_t iter_[1];
363 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
364 : !fd_stake_delegations_iter_done( iter );
365 : fd_stake_delegations_iter_next( iter ) ) {
366 : fd_stake_delegation_t * stake_delegation = fd_stake_delegations_iter_ele( iter );
367 : }
368 : */
369 :
370 : fd_stake_delegation_t const *
371 : fd_stake_delegations_iter_ele( fd_stake_delegations_iter_t * iter );
372 :
373 : ulong
374 : fd_stake_delegations_iter_idx( fd_stake_delegations_iter_t * iter );
375 :
376 : fd_stake_delegations_iter_t *
377 : fd_stake_delegations_iter_init( fd_stake_delegations_iter_t * iter,
378 : fd_stake_delegations_t const * stake_delegations );
379 :
380 : void
381 : fd_stake_delegations_iter_next( fd_stake_delegations_iter_t * iter );
382 :
383 : int
384 : fd_stake_delegations_iter_done( fd_stake_delegations_iter_t * iter );
385 :
386 : FD_PROTOTYPES_END
387 :
388 : #endif /* HEADER_fd_src_flamenco_stakes_fd_stake_delegations_h */
|