Line data Source code
1 : #include "fd_txncache_shmem.h"
2 : #include "fd_txncache_private.h"
3 :
4 : #define POOL_NAME blockcache_pool
5 189 : #define POOL_T fd_txncache_blockcache_shmem_t
6 : #define POOL_IDX_T ulong
7 662421 : #define POOL_NEXT pool.next
8 : #define POOL_IMPL_STYLE 2
9 : #include "../../util/tmpl/fd_pool.c"
10 :
11 : #define MAP_NAME blockhash_map
12 4299 : #define MAP_KEY blockhash
13 0 : #define MAP_ELE_T fd_txncache_blockcache_shmem_t
14 : #define MAP_KEY_T fd_hash_t
15 4299 : #define MAP_PREV blockhash_map.prev
16 4299 : #define MAP_NEXT blockhash_map.next
17 : #define MAP_KEY_EQ(k0,k1) fd_hash_eq( k0, k1 )
18 : #define MAP_KEY_HASH(key,seed) (__extension__({ (void)(seed); fd_ulong_load_8_fast( (key)->uc ); }))
19 : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
20 : #define MAP_MULTI 1
21 : #define MAP_IMPL_STYLE 2
22 : #include "../../util/tmpl/fd_map_chain.c"
23 :
24 : #define SLIST_NAME root_slist
25 : #define SLIST_ELE_T fd_txncache_blockcache_shmem_t
26 : #define SLIST_IDX_T ulong
27 0 : #define SLIST_NEXT slist.next
28 : #define SLIST_IMPL_STYLE 2
29 : #include "../../util/tmpl/fd_slist.c"
30 :
31 : FD_FN_CONST ushort
32 : fd_txncache_max_txnpages( ulong max_active_slots,
33 : ulong max_txn_per_slot,
34 366 : int larger_max_cost_per_block ) {
35 : /* The pool must hold the worst case set of simultaneously live
36 : transactions. Entries stay live until their inserting fork's
37 : blockcache is removed, and rooted forks are retained for
38 : FD_TXNCACHE_MAX_BLOCKHASH_DISTANCE further root advances, so up to
39 : max_active_slots forks' insertions are live at once. The worst
40 : case is the boot transient: the snapshot root (holding the full
41 : staged snapshot load) plus every other pool fork full of live
42 : inserts.
43 :
44 : The snapshot transient retains at most 151 slot deltas at
45 : max_txn_per_slot entries each. max_txn_per_slot is 2x the actual
46 : per slot transaction limit, because Agave snapshots contain each
47 : transaction twice (once keyed by signature, once by message hash,
48 : indistinguishably; see FD_PACK_MAX_TXNCACHE_TXN_PER_SLOT). Live
49 : inserts are 1x, so each non-snapshot fork holds at most
50 : max_txn_per_slot/2 entries.
51 :
52 : We count pages, not transactions, so pages might be wasted. The
53 : maximum page wastage occurs when every blockcache has a partially
54 : full tail page, adding max_active_slots-1 pages beyond the global
55 : transaction count bound (one blockcache's tail is already counted
56 : by the ceiling division). */
57 :
58 366 : ulong result;
59 366 : if( FD_UNLIKELY( larger_max_cost_per_block ) ) {
60 : /* Raised block cost limits invalidate the 1x live bound. Fall
61 : back to every active slot simultaneously full. */
62 0 : result = max_active_slots-1UL+max_active_slots*(1UL+(max_txn_per_slot-1UL)/FD_TXNCACHE_TXNS_PER_PAGE);
63 366 : } else {
64 366 : ulong snapshot_budget = FD_TXNCACHE_MAX_SLOT_DELTAS*max_txn_per_slot;
65 366 : ulong live_budget = (max_active_slots-1UL)*((max_txn_per_slot+1UL)/2UL);
66 366 : result = max_active_slots-1UL
67 366 : + 1UL+(snapshot_budget+live_budget-1UL)/FD_TXNCACHE_TXNS_PER_PAGE;
68 366 : }
69 366 : if( FD_UNLIKELY( result>USHORT_MAX-2UL ) ) return 0; /* MAX is the invalid flag, MAX-1 is the xbusy flag. */
70 366 : return (ushort)result;
71 366 : }
72 :
73 : FD_FN_CONST ushort
74 : fd_txncache_max_txnpages_per_blockhash( ulong max_active_slots,
75 : ulong max_txn_per_slot,
76 183 : int larger_max_cost_per_block ) {
77 : /* The maximum number of transaction pages we might need to store all
78 : the transactions that could be seen in a blockhash.
79 :
80 : In the worst case, every transaction in every live bank refers to
81 : the same blockhash, but a blockhash can never hold more pages than
82 : exist in the pool. */
83 :
84 183 : ulong max_txnpages = fd_txncache_max_txnpages( max_active_slots, max_txn_per_slot, larger_max_cost_per_block );
85 183 : if( FD_UNLIKELY( !max_txnpages ) ) return 0;
86 :
87 183 : ulong result = 1UL+(max_txn_per_slot*max_active_slots)/FD_TXNCACHE_TXNS_PER_PAGE;
88 183 : result = fd_ulong_min( result, max_txnpages );
89 183 : if( FD_UNLIKELY( result>USHORT_MAX-2UL ) ) return 0; /* MAX is the invalid flag, MAX-1 is the xbusy flag. */
90 183 : return (ushort)result;
91 183 : }
92 :
93 : FD_FN_CONST ulong
94 249 : fd_txncache_shmem_align( void ) {
95 249 : return FD_TXNCACHE_SHMEM_ALIGN;
96 249 : }
97 :
98 : FD_FN_CONST ulong
99 : fd_txncache_shmem_footprint( ulong max_live_slots,
100 : ulong max_txn_per_slot,
101 120 : int larger_max_cost_per_block ) {
102 120 : if( FD_UNLIKELY( max_live_slots<1UL ) ) return 0UL;
103 120 : if( FD_UNLIKELY( max_txn_per_slot<1UL ) ) return 0UL;
104 :
105 120 : ulong max_active_slots = FD_TXNCACHE_MAX_BLOCKHASH_DISTANCE+max_live_slots;
106 120 : ulong blockhash_map_chains = fd_ulong_pow2_up( 2UL*max_active_slots );
107 120 : ulong bucket_cnt = fd_txncache_bucket_cnt( max_txn_per_slot );
108 :
109 : /* To save memory, txnpages are referenced as ushort which is enough
110 : to support mainnet parameters without overflow. */
111 120 : ushort _max_txnpages = fd_txncache_max_txnpages( max_active_slots, max_txn_per_slot, larger_max_cost_per_block );
112 120 : if( FD_UNLIKELY( !_max_txnpages ) ) return 0UL;
113 :
114 120 : ulong _max_txnpages_per_blockhash = fd_txncache_max_txnpages_per_blockhash( max_active_slots, max_txn_per_slot, larger_max_cost_per_block );
115 120 : if( FD_UNLIKELY( !_max_txnpages_per_blockhash ) ) return 0UL;
116 :
117 120 : ulong _descends_footprint = descends_set_footprint( max_active_slots );
118 120 : if( FD_UNLIKELY( !_descends_footprint ) ) return 0UL;
119 :
120 120 : ulong l;
121 120 : l = FD_LAYOUT_INIT;
122 120 : l = FD_LAYOUT_APPEND( l, FD_TXNCACHE_SHMEM_ALIGN, sizeof(fd_txncache_shmem_t) );
123 120 : l = FD_LAYOUT_APPEND( l, blockhash_map_align(), blockhash_map_footprint( blockhash_map_chains ) );
124 120 : l = FD_LAYOUT_APPEND( l, blockcache_pool_align(), blockcache_pool_footprint( max_active_slots ) );
125 120 : l = FD_LAYOUT_APPEND( l, alignof(ushort), max_active_slots*_max_txnpages_per_blockhash*sizeof(ushort) ); /* blockcache->pages */
126 120 : l = FD_LAYOUT_APPEND( l, alignof(uint), max_active_slots*bucket_cnt*sizeof(uint) ); /* blockcache->heads */
127 120 : l = FD_LAYOUT_APPEND( l, descends_set_align(), max_active_slots*_descends_footprint ); /* blockcache->descends */
128 120 : l = FD_LAYOUT_APPEND( l, alignof(ushort), _max_txnpages*sizeof(ushort) ); /* txnpages_free */
129 120 : l = FD_LAYOUT_APPEND( l, alignof(fd_txncache_txnpage_t), _max_txnpages*sizeof(fd_txncache_txnpage_t) ); /* txnpages */
130 120 : l = FD_LAYOUT_APPEND( l, alignof(ushort), _max_txnpages_per_blockhash*sizeof(ushort) ); /* scratchpad txnpage pointer array for purge stale */
131 120 : l = FD_LAYOUT_APPEND( l, alignof(uint), bucket_cnt*sizeof(uint) ); /* scratchpad heads for purge stale */
132 120 : l = FD_LAYOUT_APPEND( l, alignof(fd_txncache_txnpage_t), sizeof(fd_txncache_txnpage_t) ); /* scratchpad txnpage for purge stale */
133 120 : return FD_LAYOUT_FINI( l, FD_TXNCACHE_SHMEM_ALIGN );
134 120 : }
135 :
136 : void *
137 : fd_txncache_shmem_new( void * shmem,
138 : ulong max_live_slots,
139 : ulong max_txn_per_slot,
140 : int larger_max_cost_per_block,
141 63 : ulong seed ) {
142 63 : if( FD_UNLIKELY( !shmem ) ) {
143 0 : FD_LOG_WARNING(( "NULL shmem" ));
144 0 : return NULL;
145 0 : }
146 :
147 63 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_txncache_shmem_align() ) ) ) {
148 0 : FD_LOG_WARNING(( "misaligned shmem" ));
149 0 : return NULL;
150 0 : }
151 :
152 63 : if( FD_UNLIKELY( !max_live_slots ) ) return NULL;
153 63 : if( FD_UNLIKELY( !max_txn_per_slot ) ) return NULL;
154 :
155 63 : ulong max_active_slots = FD_TXNCACHE_MAX_BLOCKHASH_DISTANCE+max_live_slots;
156 63 : ulong blockhash_map_chains = fd_ulong_pow2_up( 2UL*max_active_slots );
157 63 : ulong bucket_cnt = fd_txncache_bucket_cnt( max_txn_per_slot );
158 :
159 63 : ushort _max_txnpages = fd_txncache_max_txnpages( max_active_slots, max_txn_per_slot, larger_max_cost_per_block );
160 63 : ushort _max_txnpages_per_blockhash = fd_txncache_max_txnpages_per_blockhash( max_active_slots, max_txn_per_slot, larger_max_cost_per_block );
161 :
162 63 : if( FD_UNLIKELY( !_max_txnpages ) ) return NULL;
163 63 : if( FD_UNLIKELY( !_max_txnpages_per_blockhash ) ) return NULL;
164 :
165 63 : ulong _descends_footprint = descends_set_footprint( max_active_slots );
166 63 : if( FD_UNLIKELY( !_descends_footprint ) ) return NULL;
167 :
168 63 : FD_SCRATCH_ALLOC_INIT( l, shmem );
169 63 : fd_txncache_shmem_t * tc = FD_SCRATCH_ALLOC_APPEND( l, FD_TXNCACHE_SHMEM_ALIGN, sizeof(fd_txncache_shmem_t) );
170 63 : void * _blockhash_map = FD_SCRATCH_ALLOC_APPEND( l, blockhash_map_align(), blockhash_map_footprint( blockhash_map_chains ) );
171 63 : void * _blockcache_pool = FD_SCRATCH_ALLOC_APPEND( l, blockcache_pool_align(), blockcache_pool_footprint( max_active_slots ) );
172 63 : FD_SCRATCH_ALLOC_APPEND( l, alignof(ushort), max_active_slots*_max_txnpages_per_blockhash*sizeof(ushort) );
173 63 : FD_SCRATCH_ALLOC_APPEND( l, alignof(uint), max_active_slots*bucket_cnt*sizeof(uint) );
174 63 : void * _blockcache_descends = FD_SCRATCH_ALLOC_APPEND( l, descends_set_align(), max_active_slots*_descends_footprint );
175 63 : void * _txnpages_free = FD_SCRATCH_ALLOC_APPEND( l, alignof(ushort), _max_txnpages*sizeof(ushort) );
176 63 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txncache_txnpage_t), _max_txnpages*sizeof(fd_txncache_txnpage_t) );
177 63 : FD_SCRATCH_ALLOC_APPEND( l, alignof(ushort), _max_txnpages_per_blockhash*sizeof(ushort) );
178 63 : FD_SCRATCH_ALLOC_APPEND( l, alignof(uint), bucket_cnt*sizeof(uint) );
179 63 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txncache_txnpage_t), sizeof(fd_txncache_txnpage_t) );
180 :
181 63 : fd_txncache_blockcache_shmem_t * blockcache_pool = blockcache_pool_join( blockcache_pool_new( _blockcache_pool, max_active_slots ) );
182 63 : FD_TEST( blockcache_pool );
183 10683 : for( ulong i=0UL; i<max_active_slots; i++ ) blockcache_pool[ i ].frozen = -1;
184 :
185 63 : blockhash_map_t * blockhash_map = blockhash_map_join( blockhash_map_new( _blockhash_map, blockhash_map_chains, 0UL /* seed not used */ ) );
186 63 : FD_TEST( blockhash_map );
187 :
188 10683 : for( ulong i=0UL; i<max_active_slots; i++ ) {
189 10620 : descends_set_t * descends_set = descends_set_join( descends_set_new( (uchar *)_blockcache_descends + i*_descends_footprint, max_active_slots ) );
190 10620 : FD_TEST( descends_set );
191 10620 : }
192 :
193 63 : tc->root_cnt = 0UL;
194 63 : FD_TEST( root_slist_join( root_slist_new( tc->root_ll ) ) );
195 :
196 63 : tc->lock->value = 0;
197 :
198 63 : tc->txn_per_slot_max = max_txn_per_slot;
199 63 : tc->active_slots_max = max_active_slots;
200 63 : tc->bucket_cnt = bucket_cnt;
201 63 : tc->txnpages_per_blockhash_max = _max_txnpages_per_blockhash;
202 63 : tc->max_txnpages = _max_txnpages;
203 :
204 63 : tc->blockcache_generation = 0U;
205 63 : tc->txnpages_free_cnt = _max_txnpages;
206 63 : ushort * txnpages_free = (ushort *)_txnpages_free;
207 11175 : for( ushort i=0; i<_max_txnpages; i++ ) txnpages_free[ i ] = i;
208 :
209 63 : tc->seed = seed;
210 :
211 63 : FD_COMPILER_MFENCE();
212 63 : FD_VOLATILE( tc->magic ) = FD_TXNCACHE_SHMEM_MAGIC;
213 63 : FD_COMPILER_MFENCE();
214 :
215 63 : return (void *)tc;
216 63 : }
217 :
218 : fd_txncache_shmem_t *
219 63 : fd_txncache_shmem_join( void * shtc ) {
220 63 : if( FD_UNLIKELY( !shtc ) ) {
221 0 : FD_LOG_WARNING(( "NULL shtc" ));
222 0 : return NULL;
223 0 : }
224 :
225 63 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shtc, fd_txncache_shmem_align() ) ) ) {
226 0 : FD_LOG_WARNING(( "misaligned shtc" ));
227 0 : return NULL;
228 0 : }
229 :
230 63 : fd_txncache_shmem_t * tc = (fd_txncache_shmem_t *)shtc;
231 :
232 63 : if( FD_UNLIKELY( tc->magic!=FD_TXNCACHE_SHMEM_MAGIC ) ) {
233 0 : FD_LOG_WARNING(( "bad magic" ));
234 0 : return NULL;
235 0 : }
236 :
237 63 : return tc;
238 63 : }
|