Line data Source code
1 : #ifndef HEADER_fd_src_flamenco_runtime_fd_bank_h
2 : #define HEADER_fd_src_flamenco_runtime_fd_bank_h
3 :
4 : #include "../types/fd_types.h"
5 : #include "../leaders/fd_leaders.h"
6 : #include "../features/fd_features.h"
7 : #include "../rewards/fd_epoch_rewards.h"
8 : #include "../fd_rwlock.h"
9 : #include "fd_runtime_const.h"
10 : #include "fd_blockhashes.h"
11 : #include "sysvar/fd_sysvar_cache.h"
12 :
13 : FD_PROTOTYPES_BEGIN
14 :
15 6 : #define FD_BANKS_MAGIC 0X99999AA9999UL
16 :
17 : /* TODO: Some optimizations, cleanups, future work:
18 : 1. Simple data types (ulong, int, etc) should be stored as their
19 : underlying type instead of a byte array.
20 : 2. For some of the more complex types in the bank, we should provide
21 : a way to layout the data ahead of time instead of manually.
22 : calculating the offsets/layout of the offset-based struct.
23 : This could likely be emitted from fd_types
24 : 3. Perhaps make the query/modify scoping more explicit. Right now,
25 : the caller is free to use the API wrong if there are no locks.
26 : Maybe just expose a different API if there are no locks?
27 : 4. Rename locks to suffix with _query_locking and _query_locking_end
28 : */
29 :
30 : /* A fd_bank_t struct is the representation of the bank state on Solana
31 : for a given slot. More specifically, the bank state corresponds to
32 : all information needed during execution that is not stored on-chain,
33 : but is instead cached in a validator's memory. Each of these bank
34 : fields are repesented by a member of the fd_bank_t struct.
35 :
36 : Management of fd_bank_t structs must be fork-aware: the state of each
37 : fd_bank_t must be based on the fd_bank_t of it's parent slot. This
38 : state is managed by the fd_banks_t struct.
39 :
40 : In order to support fork-awareness, there are a few key features
41 : that fd_banks_t and fd_bank_t MUST support:
42 : 1. Query for any non-rooted slot's bank: create a fast lookup
43 : from slot to bank
44 : 2. Be able to create a new bank for a given slot from the bank of
45 : that slot's parent and maintain some tree-like structure to
46 : track the parent-child relationships: copy the contents from a
47 : parent bank into a child bank.
48 : 3. Prune the set of active banks to keep the root updated as the
49 : network progresses: free resources of fd_bank_t structs that
50 : are are not direct descendants of the root bank (remove parents
51 : and any competing lineages).
52 : 4. Each bank will have field(s) that are concurrently read/write
53 : from multiple threads: add read-write locks to the fields that are
54 : concurrently written to.
55 : 5. In practice, a bank state for a given slot can be very large and
56 : not all of the fields are written to every slot. Therefore, it can
57 : be very expensive to copy the entire bank state for a given slot
58 : each time a bank is created. In order to avoid large memcpys, we
59 : can use a CoW mechanism for certain fields.
60 :
61 : Each field of a fd_bank_t has a pre-specified set of fields including
62 : - name: the name of the field
63 : - footprint: the size of the field in bytes
64 : - align: the alignment of the field
65 : - CoW: whether the field is CoW
66 : - has_lock: whether the field has a rw-lock
67 : - type: type of the field
68 :
69 : fd_banks_t is represented by a left-child, right-sibling n-ary tree
70 : (inspired by fd_ghost) to keep track of the parent-child fork tree.
71 : The underlying data structure is a map of fd_bank_t structs that is
72 : keyed by slot. This map is backed by a simple memory pool.
73 :
74 : Each field in fd_bank_t that is not CoW is laid out contiguously in
75 : the fd_bank_t struct as simple uchar buffers. This allows for a simple
76 : memcpy to clone the bank state from a parent to a child.
77 :
78 : Each field that is CoW has its own memory pool. The memory
79 : corresponding to the field is not located in the fd_bank_t struct and
80 : is instead represented by a pool index and a dirty flag. If the field
81 : is modified, then the dirty flag is set, and an element of the pool
82 : is acquired and the data is copied over from the parent pool idx.
83 :
84 : fd_bank_t also holds all of the rw-locks for the fields that have
85 : rw-locks.
86 :
87 : So, when a bank is cloned from a parent, the non CoW fields are copied
88 : over and the CoW fields just copy over a pool index. The CoW behavior
89 : is completely abstracted away from the caller as callers have to
90 : query/modify fields using specific APIs.
91 :
92 : The memory for the banks is based off of two bounds:
93 : 1. the max number of unrooted slots at any given time. Most fields can
94 : be bounded by this value.
95 : 2. the max number of forks that execute through any 1 slot. We bound
96 : fields that are only written to at the epoch boundary by
97 : the max fork width that can execute through the boundary instead of
98 : by the max number of banks. See fd_banks_footprint() for more
99 : details.
100 :
101 : NOTE: An important invariant is that if a field is CoW, then it must
102 : have a rw-lock.
103 :
104 : NOTE: Another important invariant is that if a field is limiting its
105 : fork width, then it must be CoW.
106 :
107 : The usage pattern is as follows:
108 :
109 : To create an initial bank:
110 : fd_bank_t * bank_init = fd_bank_init_bank( banks, slot );
111 :
112 : To clone bank from parent banks:
113 : fd_bank_t * bank_clone = fd_banks_clone_from_parent( banks, slot, parent_slot );
114 :
115 : To publish a bank (aka update the root bank):
116 : fd_bank_t * bank_publish = fd_banks_publish( banks, slot );
117 :
118 : To query some arbitrary bank:
119 : fd_bank_t * bank_query = fd_banks_get_bank( banks, slot );
120 :
121 : To access fields in the bank if a field does not have a lock:
122 :
123 : fd_struct_t const * field = fd_bank_field_query( bank );
124 : OR
125 : fd_struct field = fd_bank_field_get( bank );
126 :
127 : To modify fields in the bank if a field does not have a lock:
128 :
129 : fd_struct_t * field = fd_bank_field_modify( bank );
130 : OR
131 : fd_bank_field_set( bank, value );
132 :
133 : IMPORTANT SAFETY NOTE: fd_banks_t assumes that there is only one bank
134 : being executed against at a time. However, it is safe to call
135 : fd_banks_publish while threads are executing against a bank.
136 :
137 : */
138 :
139 : /* Define additional fields to the bank struct here. If trying to add
140 : a CoW field to the bank, define a pool for it as done below. */
141 :
142 : #define FD_BANKS_ITER(X) \
143 : /* type, name, footprint, align, CoW, limit fork width, has lock */ \
144 102 : X(fd_clock_timestamp_votes_global_t, clock_timestamp_votes, 5000000UL, 128UL, 1, 0, 1 ) /* TODO: This needs to get sized out */ \
145 102 : X(fd_account_keys_global_t, stake_account_keys, 100000000UL, 128UL, 1, 0, 1 ) /* Supports roughly 3M stake accounts */ \
146 102 : X(fd_account_keys_global_t, vote_account_keys, 3200000UL, 128UL, 1, 0, 1 ) /* Supports roughly 100k vote accounts */ \
147 39 : X(fd_blockhashes_t, block_hash_queue, sizeof(fd_blockhashes_t), alignof(fd_blockhashes_t), 0, 0, 0 ) /* Block hash queue */ \
148 39 : X(fd_fee_rate_governor_t, fee_rate_governor, sizeof(fd_fee_rate_governor_t), alignof(fd_fee_rate_governor_t), 0, 0, 0 ) /* Fee rate governor */ \
149 39 : X(ulong, capitalization, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Capitalization */ \
150 39 : X(ulong, lamports_per_signature, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Lamports per signature */ \
151 39 : X(ulong, prev_lamports_per_signature, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Previous lamports per signature */ \
152 39 : X(ulong, transaction_count, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Transaction count */ \
153 39 : X(ulong, parent_signature_cnt, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Parent signature count */ \
154 39 : X(ulong, tick_height, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Tick height */ \
155 39 : X(ulong, max_tick_height, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Max tick height */ \
156 39 : X(ulong, hashes_per_tick, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Hashes per tick */ \
157 39 : X(uint128, ns_per_slot, sizeof(uint128), alignof(uint128), 0, 0, 0 ) /* NS per slot */ \
158 39 : X(ulong, ticks_per_slot, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Ticks per slot */ \
159 39 : X(ulong, genesis_creation_time, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Genesis creation time */ \
160 39 : X(double, slots_per_year, sizeof(double), alignof(double), 0, 0, 0 ) /* Slots per year */ \
161 39 : X(fd_inflation_t, inflation, sizeof(fd_inflation_t), alignof(fd_inflation_t), 0, 0, 0 ) /* Inflation */ \
162 39 : X(ulong, total_epoch_stake, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Total epoch stake */ \
163 30 : /* This is only used for the get_epoch_stake syscall. */ \
164 30 : /* If we are executing in epoch E, this is the total */ \
165 30 : /* stake at the end of epoch E-1. */ \
166 39 : X(ulong, eah_start_slot, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* EAH start slot */ \
167 39 : X(ulong, eah_stop_slot, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* EAH stop slot */ \
168 39 : X(ulong, eah_interval, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* EAH interval */ \
169 39 : X(ulong, block_height, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Block height */ \
170 39 : X(fd_hash_t, epoch_account_hash, sizeof(fd_hash_t), alignof(fd_hash_t), 0, 0, 0 ) /* Epoch account hash */ \
171 39 : X(ulong, execution_fees, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Execution fees */ \
172 39 : X(ulong, priority_fees, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Priority fees */ \
173 39 : X(ulong, signature_count, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Signature count */ \
174 39 : X(ulong, use_prev_epoch_stake, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Use prev epoch stake */ \
175 39 : X(fd_hash_t, poh, sizeof(fd_hash_t), alignof(fd_hash_t), 0, 0, 0 ) /* PoH */ \
176 39 : X(fd_sol_sysvar_last_restart_slot_t, last_restart_slot, sizeof(fd_sol_sysvar_last_restart_slot_t), alignof(fd_sol_sysvar_last_restart_slot_t), 0, 0, 0 ) /* Last restart slot */ \
177 39 : X(fd_cluster_version_t, cluster_version, sizeof(fd_cluster_version_t), alignof(fd_cluster_version_t), 0, 0, 0 ) /* Cluster version */ \
178 39 : X(ulong, parent_slot, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Previous slot */ \
179 39 : X(fd_hash_t, bank_hash, sizeof(fd_hash_t), alignof(fd_hash_t), 0, 0, 0 ) /* Bank hash */ \
180 39 : X(fd_hash_t, prev_bank_hash, sizeof(fd_hash_t), alignof(fd_hash_t), 0, 0, 0 ) /* Previous bank hash */ \
181 39 : X(fd_hash_t, block_id, sizeof(fd_hash_t), alignof(fd_hash_t), 0, 0, 0 ) /* Block id, merkle root of the last FEC set */ \
182 39 : X(fd_hash_t, genesis_hash, sizeof(fd_hash_t), alignof(fd_hash_t), 0, 0, 0 ) /* Genesis hash */ \
183 39 : X(fd_epoch_schedule_t, epoch_schedule, sizeof(fd_epoch_schedule_t), alignof(fd_epoch_schedule_t), 0, 0, 0 ) /* Epoch schedule */ \
184 39 : X(fd_rent_t, rent, sizeof(fd_rent_t), alignof(fd_rent_t), 0, 0, 0 ) /* Rent */ \
185 39 : X(fd_slot_lthash_t, lthash, sizeof(fd_slot_lthash_t), alignof(fd_slot_lthash_t), 0, 0, 0 ) /* LTHash */ \
186 39 : X(fd_sysvar_cache_t, sysvar_cache, sizeof(fd_sysvar_cache_t), alignof(fd_sysvar_cache_t), 0, 0, 0 ) /* Sysvar cache */ \
187 102 : X(fd_vote_accounts_global_t, next_epoch_stakes, 200000000UL, 128UL, 1, 0, 1 ) /* Next epoch stakes, ~4K per account * 50k vote accounts */ \
188 30 : /* These are the stakes that determine the leader */ \
189 30 : /* schedule for the upcoming epoch. If we are executing */ \
190 30 : /* in epoch E, these are the stakes at the end of epoch */ \
191 30 : /* E-1 and they determined the leader schedule for epoch */ \
192 30 : /* E+1. */ \
193 102 : X(fd_vote_accounts_global_t, epoch_stakes, 200000000UL, 128UL, 1, 0, 1 ) /* Epoch stakes ~4K per account * 50k vote accounts */ \
194 102 : X(fd_epoch_rewards_t, epoch_rewards, FD_EPOCH_REWARDS_FOOTPRINT, FD_EPOCH_REWARDS_ALIGN, 1, 1, 1 ) /* Epoch rewards */ \
195 102 : X(fd_epoch_leaders_t, epoch_leaders, FD_RUNTIME_MAX_EPOCH_LEADERS, FD_EPOCH_LEADERS_ALIGN, 1, 1, 1 ) /* Epoch leaders. If our system supports 100k vote accs, */ \
196 30 : /* then there can be 100k unique leaders in the worst */ \
197 30 : /* case. We also can assume 432k slots per epoch. */ \
198 102 : X(fd_stakes_global_t, stakes, 400000000UL, 128UL, 1, 0, 1 ) /* Stakes */ \
199 39 : X(fd_features_t, features, sizeof(fd_features_t), alignof(fd_features_t), 0, 0, 0 ) /* Features */ \
200 39 : X(ulong, txn_count, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Transaction count */ \
201 39 : X(ulong, nonvote_txn_count, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Nonvote transaction count */ \
202 39 : X(ulong, failed_txn_count, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Failed transaction count */ \
203 39 : X(ulong, nonvote_failed_txn_count, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Nonvote failed transaction count */ \
204 39 : X(ulong, total_compute_units_used, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Total compute units used */ \
205 39 : X(ulong, part_width, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Part width */ \
206 39 : X(ulong, slots_per_epoch, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Slots per epoch */ \
207 39 : X(ulong, shred_cnt, sizeof(ulong), alignof(ulong), 0, 0, 0 ) /* Shred count */ \
208 39 : X(int, enable_exec_recording, sizeof(int), alignof(int), 0, 0, 0 ) /* Enable exec recording */
209 :
210 : /* Invariant Every CoW field must have a rw-lock */
211 : #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
212 : FD_STATIC_ASSERT( (cow == 1 && has_lock == 1 ) || (cow == 0 ), CoW fields must have a rw-lock ); \
213 : FD_STATIC_ASSERT( (cow == 1 && limit_fork_width == 1) || (limit_fork_width == 0), CoW must be 1 if limit_fork_width is 1 );
214 : FD_BANKS_ITER(X)
215 : #undef X
216 :
217 : /* If a member of the bank is CoW then it needs a corresponding pool
218 : which is defined here. If a type if not a CoW then it does not need
219 : to be in a pool and is laid out contigiously in the bank struct. */
220 :
221 : /* Declare a pool object wrapper for all CoW fields. */
222 : #define HAS_COW_1(name, footprint, align) \
223 : static const ulong fd_bank_##name##_align = align; \
224 : static const ulong fd_bank_##name##_footprint = footprint; \
225 : \
226 : struct fd_bank_##name { \
227 : ulong next; \
228 : uchar data[footprint]__attribute__((aligned(align))); \
229 : }; \
230 : typedef struct fd_bank_##name fd_bank_##name##_t;
231 :
232 : /* Do nothing if CoW is not enabled. */
233 : #define HAS_COW_0(name, footprint, align)
234 :
235 : #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
236 : HAS_COW_##cow(name, footprint, align)
237 : FD_BANKS_ITER(X)
238 :
239 : #undef X
240 : #undef HAS_COW_0
241 : #undef HAS_COW_1
242 :
243 : #define POOL_NAME fd_bank_clock_timestamp_votes_pool
244 84 : #define POOL_T fd_bank_clock_timestamp_votes_t
245 : #include "../../util/tmpl/fd_pool.c"
246 : #undef POOL_NAME
247 : #undef POOL_T
248 :
249 : #define POOL_NAME fd_bank_stake_account_keys_pool
250 84 : #define POOL_T fd_bank_stake_account_keys_t
251 : #include "../../util/tmpl/fd_pool.c"
252 : #undef POOL_NAME
253 : #undef POOL_T
254 :
255 : #define POOL_NAME fd_bank_vote_account_keys_pool
256 90 : #define POOL_T fd_bank_vote_account_keys_t
257 : #include "../../util/tmpl/fd_pool.c"
258 : #undef POOL_NAME
259 : #undef POOL_T
260 :
261 : #define POOL_NAME fd_bank_next_epoch_stakes_pool
262 72 : #define POOL_T fd_bank_next_epoch_stakes_t
263 : #include "../../util/tmpl/fd_pool.c"
264 : #undef POOL_NAME
265 : #undef POOL_T
266 :
267 : #define POOL_NAME fd_bank_epoch_stakes_pool
268 72 : #define POOL_T fd_bank_epoch_stakes_t
269 : #include "../../util/tmpl/fd_pool.c"
270 : #undef POOL_NAME
271 : #undef POOL_T
272 :
273 : #define POOL_NAME fd_bank_epoch_leaders_pool
274 93 : #define POOL_T fd_bank_epoch_leaders_t
275 : #include "../../util/tmpl/fd_pool.c"
276 : #undef POOL_NAME
277 : #undef POOL_T
278 :
279 : #define POOL_NAME fd_bank_stakes_pool
280 72 : #define POOL_T fd_bank_stakes_t
281 : #include "../../util/tmpl/fd_pool.c"
282 : #undef POOL_NAME
283 : #undef POOL_T
284 :
285 : #define POOL_NAME fd_bank_epoch_rewards_pool
286 72 : #define POOL_T fd_bank_epoch_rewards_t
287 : #include "../../util/tmpl/fd_pool.c"
288 : #undef POOL_NAME
289 : #undef POOL_T
290 :
291 : /* As mentioned above, the overall layout of the bank struct:
292 : - Fields used for internal pool/bank management
293 : - Non-Cow fields
294 : - CoW fields
295 : - Locks for CoW fields
296 :
297 : The CoW fields are laid out contiguously in the bank struct.
298 : The locks for the CoW fields are laid out contiguously after the
299 : CoW fields.
300 : */
301 :
302 : struct fd_bank {
303 90 : #define FD_BANK_HEADER_SIZE (40UL)
304 :
305 : /* Fields used for internal pool and bank management */
306 : ulong slot_; /* slot this node is tracking, also the map key */
307 : ulong next; /* reserved for internal use by fd_pool_para, fd_map_chain_para and fd_banks_publish */
308 : ulong parent_idx; /* index of the parent in the node pool */
309 : ulong child_idx; /* index of the left-child in the node pool */
310 : ulong sibling_idx; /* index of the right-sibling in the node pool */
311 :
312 : /* First, layout all non-CoW fields contiguously. This is done to
313 : allow for cloning the bank state with a simple memcpy. Each
314 : non-CoW field is just represented as a byte array. */
315 :
316 : #define HAS_COW_1(type, name, footprint, align)
317 :
318 : #define HAS_COW_0(type, name, footprint, align) \
319 : uchar name[footprint] __attribute__((aligned(align)));
320 :
321 : #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
322 : HAS_COW_##cow(type, name, footprint, align)
323 : FD_BANKS_ITER(X)
324 : #undef X
325 : #undef HAS_COW_0
326 : #undef HAS_COW_1
327 :
328 : /* Now, layout all information needed for CoW fields. These are only
329 : copied when explicitly requested by the caller. The field's data
330 : is located at teh pool idx in the pool. If the dirty flag has been
331 : set, then the element has been copied over for this bank. */
332 :
333 : #define HAS_COW_1(type, name, footprint, align) \
334 : int name##_dirty; \
335 : ulong name##_pool_idx; \
336 : ulong name##_pool_offset;
337 :
338 : #define HAS_COW_0(type, name, footprint, align)
339 :
340 : #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
341 : HAS_COW_##cow(type, name, footprint, align)
342 : FD_BANKS_ITER(X)
343 : #undef X
344 : #undef HAS_COW_0
345 : #undef HAS_COW_1
346 :
347 : /* Now emit locks for all fields that need a rwlock. */
348 :
349 : #define HAS_LOCK_1(type, name, footprint, align) \
350 : fd_rwlock_t name##_lock;
351 :
352 : #define HAS_LOCK_0(type, name, footprint, align) /* Do nothing for these. */
353 :
354 : #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
355 : HAS_LOCK_##has_lock(type, name, footprint, align)
356 : FD_BANKS_ITER(X)
357 : #undef X
358 : #undef HAS_LOCK_0
359 : #undef HAS_LOCK_1
360 :
361 : };
362 : typedef struct fd_bank fd_bank_t;
363 :
364 : #define HAS_COW_1(type, name, footprint, align) \
365 : static inline void \
366 288 : fd_bank_set_##name##_pool( fd_bank_t * bank, fd_bank_##name##_t * bank_pool ) { \
367 288 : void * bank_pool_mem = fd_bank_##name##_pool_leave( bank_pool ); \
368 288 : if( FD_UNLIKELY( !bank_pool_mem ) ) { \
369 0 : FD_LOG_CRIT(( "Failed to leave bank pool" )); \
370 0 : } \
371 288 : bank->name##_pool_offset = (ulong)bank_pool_mem - (ulong)bank; \
372 288 : } \
373 : static inline fd_bank_##name##_t * \
374 84 : fd_bank_get_##name##_pool( fd_bank_t * bank ) { \
375 84 : return fd_bank_##name##_pool_join( (uchar *)bank + bank->name##_pool_offset ); \
376 84 : }
377 : #define HAS_COW_0(type, name, footprint, align) /* Do nothing for these. */
378 :
379 : #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
380 : HAS_COW_##cow(type, name, footprint, align)
381 : FD_BANKS_ITER(X)
382 : #undef X
383 : #undef HAS_COW_0
384 : #undef HAS_COW_1
385 :
386 : /* fd_bank_t is the alignment for the bank state. */
387 :
388 : ulong
389 : fd_bank_align( void );
390 :
391 : /* fd_bank_t is the footprint for the bank state. This does NOT
392 : include the footprint for the CoW state. */
393 :
394 : ulong
395 : fd_bank_footprint( void );
396 :
397 : /**********************************************************************/
398 : /* fd_banks_t is the main struct used to manage the bank state. It can
399 : be used to query/modify/clone/publish the bank state.
400 :
401 : fd_banks_t contains some metadata a map/pool pair to manage the banks.
402 : It also contains pointers to the CoW pools.
403 :
404 : The data is laid out contigiously in memory starting from fd_banks_t;
405 : this can be seen in fd_banks_footprint(). */
406 :
407 : #define POOL_NAME fd_banks_pool
408 141 : #define POOL_T fd_bank_t
409 : #include "../../util/tmpl/fd_pool.c"
410 : #undef POOL_NAME
411 : #undef POOL_T
412 :
413 : #define MAP_NAME fd_banks_map
414 : #define MAP_ELE_T fd_bank_t
415 45 : #define MAP_KEY slot_
416 : #include "../../util/tmpl/fd_map_chain.c"
417 : #undef MAP_NAME
418 : #undef MAP_ELE_T
419 : #undef MAP_KEY
420 :
421 : struct fd_banks {
422 : ulong magic; /* ==FD_BANKS_MAGIC */
423 : ulong max_total_banks; /* Maximum number of banks */
424 : ulong max_fork_width; /* Maximum fork width executing
425 : through any given slot. */
426 : ulong root; /* root slot */
427 : ulong root_idx; /* root idx */
428 :
429 : fd_rwlock_t rwlock; /* rwlock for fd_banks_t */
430 :
431 : ulong pool_offset; /* offset of pool from banks */
432 : ulong map_offset; /* offset of map from banks */
433 :
434 : /* Layout all CoW pools. */
435 :
436 : #define HAS_COW_1(type, name, footprint, align) \
437 : ulong name##_pool_offset; /* offset of pool from banks */
438 :
439 : #define HAS_COW_0(type, name, footprint, align) /* Do nothing for these. */
440 :
441 : #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
442 : HAS_COW_##cow(type, name, footprint, align)
443 : FD_BANKS_ITER(X)
444 : #undef X
445 : #undef HAS_COW_0
446 : #undef HAS_COW_1
447 : };
448 : typedef struct fd_banks fd_banks_t;
449 :
450 : /* Bank accesssors. Different accessors are emitted for different types
451 : depending on if the field has a lock or not. */
452 :
453 : #define HAS_LOCK_1(type, name) \
454 : type const * fd_bank_##name##_locking_query( fd_bank_t * bank ); \
455 : void fd_bank_##name##_end_locking_query( fd_bank_t * bank ); \
456 : type * fd_bank_##name##_locking_modify( fd_bank_t * bank ); \
457 : void fd_bank_##name##_end_locking_modify( fd_bank_t * bank );
458 :
459 : #define HAS_LOCK_0(type, name) \
460 : type const * fd_bank_##name##_query( fd_bank_t const * bank ); \
461 : type * fd_bank_##name##_modify( fd_bank_t * bank );
462 :
463 : #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
464 : void fd_bank_##name##_set( fd_bank_t * bank, type value ); \
465 : type fd_bank_##name##_get( fd_bank_t const * bank ); \
466 : HAS_LOCK_##has_lock(type, name)
467 : FD_BANKS_ITER(X)
468 : #undef X
469 :
470 : #undef HAS_LOCK_0
471 : #undef HAS_LOCK_1
472 :
473 : static inline ulong
474 669 : fd_bank_slot_get( fd_bank_t const * bank ) {
475 669 : return bank->slot_;
476 669 : }
477 :
478 : ulong
479 : fd_bank_epoch_get( fd_bank_t const * bank );
480 :
481 : /* Simple getters and setters for members of fd_banks_t.*/
482 :
483 : static inline fd_bank_t *
484 120 : fd_banks_get_bank_pool( fd_banks_t const * banks ) {
485 120 : return fd_banks_pool_join( ((uchar *)banks + banks->pool_offset) );
486 120 : }
487 :
488 : static inline fd_banks_map_t *
489 84 : fd_banks_get_bank_map( fd_banks_t const * banks ) {
490 84 : return fd_banks_map_join( ((uchar *)banks + banks->map_offset) );
491 84 : }
492 :
493 : static inline void
494 6 : fd_banks_set_bank_pool( fd_banks_t * banks, fd_bank_t * bank_pool ) {
495 6 : void * bank_pool_mem = fd_banks_pool_leave( bank_pool );
496 6 : if( FD_UNLIKELY( !bank_pool_mem ) ) {
497 0 : FD_LOG_CRIT(( "Failed to leave bank pool" ));
498 0 : }
499 6 : banks->pool_offset = (ulong)bank_pool_mem - (ulong)banks;
500 6 : }
501 :
502 : static inline void
503 6 : fd_banks_set_bank_map( fd_banks_t * banks, fd_banks_map_t * bank_map ) {
504 6 : void * bank_map_mem = fd_banks_map_leave( bank_map );
505 6 : if( FD_UNLIKELY( !bank_map_mem ) ) {
506 0 : FD_LOG_CRIT(( "Failed to leave bank map" ));
507 0 : }
508 6 : banks->map_offset = (ulong)bank_map_mem - (ulong)banks;
509 6 : }
510 :
511 : #define HAS_COW_1(type, name, footprint, align) \
512 : static inline fd_bank_##name##_t * \
513 387 : fd_banks_get_##name##_pool( fd_banks_t * banks ) { \
514 387 : return fd_bank_##name##_pool_join( (uchar *)banks + banks->name##_pool_offset ); \
515 387 : } \
516 : static inline void \
517 48 : fd_banks_set_##name##_pool( fd_banks_t * banks, fd_bank_##name##_t * bank_pool ) { \
518 48 : void * bank_pool_mem = fd_bank_##name##_pool_leave( bank_pool ); \
519 48 : if( FD_UNLIKELY( !bank_pool_mem ) ) { \
520 0 : FD_LOG_CRIT(( "Failed to leave bank pool" )); \
521 0 : } \
522 48 : banks->name##_pool_offset = (ulong)bank_pool_mem - (ulong)banks; \
523 48 : }
524 :
525 : #define HAS_COW_0(type, name, footprint, align) /* Do nothing for these. */
526 :
527 : #define X(type, name, footprint, align, cow, limit_fork_width, has_lock) \
528 : HAS_COW_##cow(type, name, footprint, align)
529 : FD_BANKS_ITER(X)
530 : #undef X
531 : #undef HAS_COW_0
532 : #undef HAS_COW_1
533 :
534 : /* fd_banks_lock() and fd_banks_unlock() are locks to be acquired and
535 : freed around accessing or modifying a specific bank. This is only
536 : required if there is concurrent access to a bank while operations on
537 : its underlying map are being performed.
538 :
539 : Under the hood, this is a wrapper around fd_banks_t's rwlock.
540 : This is done so a caller can safely read/write a specific bank.
541 : Otherwise, we run the risk of accessing/modifying a bank that may be
542 : freed. This is acquiring and freeing a read lock around fd_banks_t. */
543 :
544 : static inline void
545 0 : fd_banks_lock( fd_banks_t * banks ) {
546 0 : fd_rwlock_read( &banks->rwlock );
547 0 : }
548 :
549 : static inline void
550 0 : fd_banks_unlock( fd_banks_t * banks ) {
551 0 : fd_rwlock_unread( &banks->rwlock );
552 0 : }
553 :
554 : /* fd_banks_root returns the current root slot for the bank. */
555 :
556 : FD_FN_PURE static inline fd_bank_t const *
557 6 : fd_banks_root( fd_banks_t const * banks ) {
558 6 : return fd_banks_pool_ele_const( fd_banks_get_bank_pool( banks ), banks->root_idx );
559 6 : }
560 :
561 : /* fd_banks_align() returns the alignment of fd_banks_t */
562 :
563 : ulong
564 : fd_banks_align( void );
565 :
566 : /* fd_banks_footprint() returns the footprint of fd_banks_t. This
567 : includes the struct itself but also the footprint for all of the
568 : pools.
569 :
570 : The footprint of fd_banks_t is determined by the total number
571 : of banks that the bank manages. This is an analog for the max number
572 : of unrooted slots the bank can manage at any given time.
573 :
574 : We can also further bound the memory footprint of the banks by the
575 : max width of forks that can exist at any given time. The reason for
576 : this is that there are several large CoW structs that are only
577 : written to during the epoch boundary (e.g. epoch_rewards,
578 : epoch_stakes, etc.). These structs are read-only afterwards. This
579 : means if we also bound the max number of forks that can execute
580 : through the epoch boundary, we can bound the memory footprint of
581 : the banks. */
582 :
583 : ulong
584 : fd_banks_footprint( ulong max_total_banks, ulong max_fork_width );
585 :
586 : /* fd_banks_new() creates a new fd_banks_t struct. This function lays
587 : out the memory for all of the constituent fd_bank_t structs and
588 : pools depending on the max_total_banks and the max_fork_width for s
589 : given slot. */
590 :
591 : void *
592 : fd_banks_new( void * mem, ulong max_total_banks, ulong max_fork_width );
593 :
594 : /* fd_banks_join() joins a new fd_banks_t struct. */
595 :
596 : fd_banks_t *
597 : fd_banks_join( void * mem );
598 :
599 : /* fd_banks_leave() leaves a bank. */
600 :
601 : void *
602 : fd_banks_leave( fd_banks_t * banks );
603 :
604 : /* fd_banks_delete() deletes a bank. */
605 :
606 : void *
607 : fd_banks_delete( void * shmem );
608 :
609 : /* fd_banks_init_bank() initializes a new bank in the bank manager.
610 : This should only be used during bootup. This returns an initial
611 : fd_bank_t with the corresponding slot. */
612 :
613 : fd_bank_t *
614 : fd_banks_init_bank( fd_banks_t * banks, ulong slot );
615 :
616 : /* fd_bank_get_bank() returns a bank for a given slot. If said bank
617 : does not exist, NULL is returned. */
618 :
619 : fd_bank_t *
620 : fd_banks_get_bank( fd_banks_t * banks, ulong slot );
621 :
622 : /* fd_banks_clone_from_parent() clones a bank from a parent bank.
623 : If the bank corresponding to the parent slot does not exist,
624 : NULL is returned. If a bank is not able to be created, NULL is
625 : returned. The data from the parent bank will copied over into
626 : the new bank.
627 :
628 : A more detailed note: not all of the data is copied over and this
629 : is a shallow clone. All of the CoW fields are not copied over and
630 : will only be done so if the caller explicitly calls
631 : fd_bank_{*}_modify(). This naming was chosen to emulate the
632 : semantics of the Agave client. */
633 :
634 : fd_bank_t *
635 : fd_banks_clone_from_parent( fd_banks_t * banks,
636 : ulong slot,
637 : ulong parent_slot );
638 :
639 : /* fd_banks_publish() publishes a bank to the bank manager. This
640 : should only be used when a bank is no longer needed. This will
641 : prune off the bank from the bank manager. It returns the new root
642 : bank.
643 :
644 : All banks that are ancestors or siblings of the slot will be
645 : cancelled and their resources will be released back to the pool. */
646 :
647 : fd_bank_t const *
648 : fd_banks_publish( fd_banks_t * banks, ulong slot );
649 :
650 : /* fd_bank_clear_bank() clears the contents of a bank. This should ONLY
651 : be used with banks that have no children.
652 :
653 : This function will memset all non-CoW fields to 0.
654 :
655 : For all non-CoW fields, we will reset the indices to its parent. */
656 :
657 : void
658 : fd_banks_clear_bank( fd_banks_t * banks, fd_bank_t * bank );
659 :
660 : /* fd_banks_rekey_root_bank() will change the key of the current root
661 : bank to a caller-specified slot. This function returns the root bank
662 : with the new key.
663 :
664 : This should NOT be called once the root bank has child banks.
665 :
666 : This is useful for snapshot loading where there are an unknown amount
667 : of banks to load and the latest snapshot is the root slot. This
668 : effectively lowers the memory used by snapshot loading. */
669 :
670 : fd_bank_t *
671 : fd_banks_rekey_root_bank( fd_banks_t * banks, ulong slot );
672 :
673 : FD_PROTOTYPES_END
674 :
675 : #endif /* HEADER_fd_src_flamenco_runtime_fd_bank_h */
|