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