Line data Source code
1 : #include "fd_ghost.h"
2 : #include "../../util/fd_hash32.h"
3 :
4 : #define POOL_NAME blk_pool
5 120 : #define POOL_T fd_ghost_blk_t
6 : #include "../../util/tmpl/fd_pool.c"
7 :
8 : #define MAP_NAME blk_map
9 : #define MAP_ELE_T fd_ghost_blk_t
10 1083 : #define MAP_KEY id
11 : #define MAP_KEY_T fd_hash_t
12 2133 : #define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1), sizeof(fd_hash_t)))
13 3231 : #define MAP_KEY_HASH(key,seed) (fd_hash32( (key)->uc, (seed) ))
14 3060 : #define MAP_NEXT next
15 : #include "../../util/tmpl/fd_map_chain.c"
16 :
17 : #define POOL_NAME vtr_pool
18 120 : #define POOL_T fd_ghost_vtr_t
19 : #include "../../util/tmpl/fd_pool.c"
20 :
21 : #define MAP_NAME vtr_map
22 6 : #define MAP_ELE_T fd_ghost_vtr_t
23 30 : #define MAP_KEY addr
24 : #define MAP_KEY_T fd_pubkey_t
25 15 : #define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1), sizeof(fd_pubkey_t)))
26 72 : #define MAP_KEY_HASH(key,seed) (fd_hash32( (key)->uc, (seed) ))
27 27 : #define MAP_PREV map.prev
28 33 : #define MAP_NEXT map.next
29 : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
30 : #include "../../util/tmpl/fd_map_chain.c"
31 :
32 : #define DLIST_NAME vtr_dlist
33 : #define DLIST_ELE_T fd_ghost_vtr_t
34 36 : #define DLIST_PREV dlist.prev
35 42 : #define DLIST_NEXT dlist.next
36 : #include "../../util/tmpl/fd_dlist.c"
37 :
38 : /* fd_ghost_t is the top-level structure that holds the root of the
39 : tree, as well as the memory pools and map structures for tracking
40 : ghost eles and votes.
41 :
42 : These structures are bump-allocated and laid out contiguously in
43 : memory from the fd_ghost_t * pointer which points to the beginning of
44 : the memory region.
45 :
46 : --------------------------- <- fd_ghost_t *
47 : | fd_ghost_t |
48 : ---------------------------
49 : | blk_pool |
50 : ---------------------------
51 : | blk_map |
52 : ---------------------------
53 : | vtr_pool |
54 : ---------------------------
55 : | vtr_map |
56 : ---------------------------
57 : | vtr_dlist for blk[0] |
58 : | vtr_dlist for blk[1] |
59 : | ... |
60 : | vtr_dlist for blk[N-1] |
61 : --------------------------- */
62 :
63 : struct __attribute__((aligned(128UL))) fd_ghost {
64 : ulong root; /* pool idx of the root tree element */
65 : ulong wksp_gaddr; /* wksp gaddr of fd_ghost in the backing wksp */
66 : ulong blk_pool_gaddr; /* memory offset of the blk_pool */
67 : ulong blk_map_gaddr; /* memory offset of the blk_map */
68 : ulong vtr_pool_gaddr; /* memory offset of the vtr_pool */
69 : ulong vtr_map_gaddr; /* memory offset of the vtr_map */
70 : ulong width; /* incrementally updated width of the fork tree */
71 : };
72 :
73 : typedef fd_ghost_blk_t blk_pool_t;
74 : typedef fd_ghost_vtr_t vtr_pool_t;
75 :
76 : /* wksp returns the local join to the wksp backing the
77 : ghost. The lifetime of the returned pointer is at least as
78 : long as the lifetime of the local join. Assumes ghost is a
79 : current local join. */
80 :
81 : FD_FN_PURE static inline fd_wksp_t *
82 8382 : wksp( fd_ghost_t const * ghost ) {
83 8382 : return (fd_wksp_t *)( ((ulong)ghost) - ghost->wksp_gaddr );
84 8382 : }
85 :
86 : static inline blk_pool_t *
87 5178 : blk_pool( fd_ghost_t * ghost ) {
88 5178 : return (blk_pool_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->blk_pool_gaddr );
89 5178 : }
90 :
91 : static inline blk_pool_t const *
92 0 : blk_pool_const( fd_ghost_t const * ghost ) {
93 0 : return (blk_pool_t const *)fd_wksp_laddr_fast( wksp( ghost ), ghost->blk_pool_gaddr );
94 0 : }
95 :
96 : static inline blk_map_t *
97 2874 : blk_map( fd_ghost_t * ghost ) {
98 2874 : return (blk_map_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->blk_map_gaddr );
99 2874 : }
100 :
101 : static inline vtr_pool_t *
102 183 : vtr_pool( fd_ghost_t * ghost ) {
103 183 : return (vtr_pool_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->vtr_pool_gaddr );
104 183 : }
105 :
106 : static inline vtr_map_t *
107 72 : vtr_map( fd_ghost_t * ghost ) {
108 72 : return (vtr_map_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->vtr_map_gaddr );
109 72 : }
110 :
111 : /* blk_vtr_dlist returns the local join to blk's vtr_dlist — the list of
112 : vtrs whose prev_block_id == blk->id. Each block slot is bound (by
113 : gaddr) to a dlist once in fd_ghost_new, so this is gaddr-safe across
114 : relocation / separate local joins. */
115 :
116 : static inline vtr_dlist_t *
117 75 : blk_vtr_dlist( fd_ghost_t * ghost, fd_ghost_blk_t const * blk ) {
118 75 : return (vtr_dlist_t *)fd_wksp_laddr_fast( wksp( ghost ), blk->vtr_dlist_gaddr );
119 75 : }
120 :
121 : ulong
122 624 : fd_ghost_align( void ) {
123 624 : return alignof(fd_ghost_t);
124 624 : }
125 :
126 : ulong
127 : fd_ghost_footprint( ulong blk_max,
128 120 : ulong vtr_max ) {
129 120 : blk_max = fd_ulong_pow2_up( blk_max );
130 120 : vtr_max = fd_ulong_pow2_up( vtr_max ) * 2; /* epoch boundary overlap — old epoch vtrs parked on live blocks while new epoch voters acquire */
131 120 : ulong blk_chain_cnt = blk_map_chain_cnt_est( blk_max );
132 120 : ulong vtr_chain_cnt = vtr_map_chain_cnt_est( vtr_max );
133 120 : return FD_LAYOUT_FINI(
134 120 : FD_LAYOUT_APPEND(
135 120 : FD_LAYOUT_APPEND(
136 120 : FD_LAYOUT_APPEND(
137 120 : FD_LAYOUT_APPEND(
138 120 : FD_LAYOUT_APPEND(
139 120 : FD_LAYOUT_APPEND(
140 120 : FD_LAYOUT_INIT,
141 120 : alignof(fd_ghost_t), sizeof(fd_ghost_t) ),
142 120 : blk_pool_align(), blk_pool_footprint( blk_max ) ),
143 120 : blk_map_align(), blk_map_footprint ( blk_chain_cnt ) ),
144 120 : vtr_pool_align(), vtr_pool_footprint( vtr_max ) ),
145 120 : vtr_map_align(), vtr_map_footprint ( vtr_chain_cnt ) ),
146 120 : vtr_dlist_align(), vtr_dlist_footprint() * blk_max ),
147 120 : fd_ghost_align() );
148 120 : }
149 :
150 : void *
151 : fd_ghost_new( void * shmem,
152 : ulong blk_max,
153 : ulong vtr_max,
154 60 : ulong seed ) {
155 :
156 60 : if( FD_UNLIKELY( !shmem ) ) {
157 0 : FD_LOG_WARNING(( "NULL mem" ));
158 0 : return NULL;
159 0 : }
160 :
161 60 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_ghost_align() ) ) ) {
162 0 : FD_LOG_WARNING(( "misaligned mem" ));
163 0 : return NULL;
164 0 : }
165 :
166 60 : ulong footprint = fd_ghost_footprint( blk_max, vtr_max );
167 :
168 60 : blk_max = fd_ulong_pow2_up( blk_max );
169 60 : vtr_max = fd_ulong_pow2_up( vtr_max ) * 2; /* epoch boundary overlap */
170 60 : if( FD_UNLIKELY( !footprint ) ) {
171 0 : FD_LOG_WARNING(( "bad blk_max (%lu)", blk_max ));
172 0 : return NULL;
173 0 : }
174 :
175 60 : fd_wksp_t * wksp = fd_wksp_containing( shmem );
176 60 : if( FD_UNLIKELY( !wksp ) ) {
177 0 : FD_LOG_WARNING(( "shmem must be part of a workspace" ));
178 0 : return NULL;
179 0 : }
180 :
181 60 : fd_memset( shmem, 0, footprint );
182 :
183 60 : ulong blk_chain_cnt = blk_map_chain_cnt_est( blk_max );
184 60 : ulong vtr_chain_cnt = vtr_map_chain_cnt_est( vtr_max );
185 :
186 60 : FD_SCRATCH_ALLOC_INIT( l, shmem );
187 60 : fd_ghost_t * ghost = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_ghost_t), sizeof(fd_ghost_t) );
188 60 : void * blk_pool = FD_SCRATCH_ALLOC_APPEND( l, blk_pool_align(), blk_pool_footprint( blk_max ) );
189 60 : void * blk_map = FD_SCRATCH_ALLOC_APPEND( l, blk_map_align(), blk_map_footprint ( blk_chain_cnt ) );
190 60 : void * vtr_pool = FD_SCRATCH_ALLOC_APPEND( l, vtr_pool_align(), vtr_pool_footprint( vtr_max ) );
191 60 : void * vtr_map = FD_SCRATCH_ALLOC_APPEND( l, vtr_map_align(), vtr_map_footprint ( vtr_chain_cnt ) );
192 60 : void * vtr_dlist = FD_SCRATCH_ALLOC_APPEND( l, vtr_dlist_align(), vtr_dlist_footprint() * blk_max );
193 60 : FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_ghost_align() ) == (ulong)shmem + footprint );
194 :
195 60 : ghost->root = ULONG_MAX;
196 60 : ghost->wksp_gaddr = fd_wksp_gaddr_fast( wksp, ghost );
197 60 : ghost->blk_pool_gaddr = fd_wksp_gaddr_fast( wksp, blk_pool_join( blk_pool_new ( blk_pool, blk_max ) ) );
198 60 : ghost->blk_map_gaddr = fd_wksp_gaddr_fast( wksp, blk_map_join ( blk_map_new ( blk_map, blk_chain_cnt, seed ) ) );
199 60 : ghost->vtr_pool_gaddr = fd_wksp_gaddr_fast( wksp, vtr_pool_join( vtr_pool_new ( vtr_pool, vtr_max ) ) );
200 60 : ghost->vtr_map_gaddr = fd_wksp_gaddr_fast( wksp, vtr_map_join ( vtr_map_new ( vtr_map, vtr_chain_cnt, seed ) ) );
201 :
202 : /* Format one vtr_dlist per block slot and bind it (by gaddr) to its
203 : pool element. The binding is permanent: acquire/release in
204 : insert()/fd_ghost_publish never changes blk->vtr_dlist_gaddr, and
205 : fd_ghost_publish drains a pruned block's dlist (leaving it empty)
206 : before releasing the slot, so a re-acquired block always starts with
207 : a clean list. */
208 :
209 60 : blk_pool_t * bp = (blk_pool_t *)fd_wksp_laddr_fast( wksp, ghost->blk_pool_gaddr ); /* blk_pool() accessor is shadowed by the local here */
210 2724 : for( ulong i=0UL; i<blk_max; i++ ) {
211 2664 : void * dl = vtr_dlist_join( vtr_dlist_new( (uchar *)vtr_dlist + i*vtr_dlist_footprint() ) );
212 2664 : blk_pool_ele( bp, i )->vtr_dlist_gaddr = fd_wksp_gaddr_fast( wksp, dl );
213 2664 : }
214 :
215 60 : return shmem;
216 60 : }
217 :
218 : fd_ghost_t *
219 60 : fd_ghost_join( void * shghost ) {
220 60 : fd_ghost_t * ghost = (fd_ghost_t *)shghost;
221 :
222 60 : if( FD_UNLIKELY( !ghost ) ) {
223 0 : FD_LOG_WARNING(( "NULL ghost" ));
224 0 : return NULL;
225 0 : }
226 :
227 60 : if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)ghost, fd_ghost_align() ) ) ) {
228 0 : FD_LOG_WARNING(( "misaligned ghost" ));
229 0 : return NULL;
230 0 : }
231 :
232 60 : return ghost;
233 60 : }
234 :
235 : void *
236 39 : fd_ghost_leave( fd_ghost_t const * ghost ) {
237 :
238 39 : if( FD_UNLIKELY( !ghost ) ) {
239 0 : FD_LOG_WARNING(( "NULL ghost" ));
240 0 : return NULL;
241 0 : }
242 :
243 39 : return (void *)ghost;
244 39 : }
245 :
246 : void *
247 39 : fd_ghost_delete( void * ghost ) {
248 :
249 39 : if( FD_UNLIKELY( !ghost ) ) {
250 0 : FD_LOG_WARNING(( "NULL ghost" ));
251 0 : return NULL;
252 0 : }
253 :
254 39 : if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)ghost, fd_ghost_align() ) ) ) {
255 0 : FD_LOG_WARNING(( "misaligned ghost" ));
256 0 : return NULL;
257 0 : }
258 :
259 39 : return ghost;
260 39 : }
261 :
262 : fd_ghost_blk_t *
263 126 : fd_ghost_root( fd_ghost_t * ghost ) {
264 126 : return blk_pool_ele( blk_pool( ghost ), ghost->root );
265 126 : }
266 :
267 : fd_ghost_blk_t *
268 0 : fd_ghost_parent( fd_ghost_t * ghost, fd_ghost_blk_t * blk ) {
269 0 : return blk_pool_ele( blk_pool( ghost ), blk->parent );
270 0 : }
271 :
272 : fd_ghost_blk_t *
273 : fd_ghost_query( fd_ghost_t * ghost,
274 258 : fd_hash_t const * block_id ) {
275 258 : return blk_map_ele_query( blk_map( ghost ), block_id, NULL, blk_pool( ghost ) );
276 258 : }
277 :
278 : fd_ghost_blk_t *
279 : fd_ghost_best( fd_ghost_t * ghost,
280 27 : fd_ghost_blk_t * root ) {
281 27 : blk_pool_t * pool = blk_pool( ghost );
282 27 : ulong null = blk_pool_idx_null( pool );
283 27 : fd_ghost_blk_t * best = root;
284 102 : while( FD_LIKELY( best->child != null ) ) {
285 75 : int valid = 0; /* at least one child is valid */
286 75 : fd_ghost_blk_t * child = blk_pool_ele( pool, best->child );
287 171 : while( FD_LIKELY( child ) ) { /* greedily pick the heaviest valid child */
288 96 : if( FD_LIKELY( child->valid ) ) {
289 90 : if( FD_LIKELY( !valid ) ) { /* this is the first valid child, so progress the head */
290 75 : best = child;
291 75 : valid = 1;
292 75 : }
293 :
294 : /* When stake is equal, tie-break by lower slot. Two valid
295 : children with equal stake and equal slot (ie. equivocating
296 : blocks) cannot occur: equivocating blocks are marked valid=0,
297 : so at most one of them would be valid unless multiple blocks
298 : for that slot are duplicate confirmed, which is a consensus
299 : invariant violation. */
300 :
301 90 : best = fd_ptr_if(
302 90 : fd_int_if(
303 90 : child->stake == best->stake, /* if the weights are equal */
304 90 : child->slot < best->slot, /* then tie-break by lower slot number */
305 90 : child->stake > best->stake ), /* else return heavier */
306 90 : child, best );
307 90 : }
308 96 : child = blk_pool_ele( pool, child->sibling );
309 96 : }
310 75 : if( FD_UNLIKELY( !valid ) ) break; /* no children are valid, so short-circuit traversal */
311 75 : }
312 27 : return best;
313 27 : }
314 :
315 : fd_ghost_blk_t *
316 : fd_ghost_deepest( fd_ghost_t * ghost,
317 18 : fd_ghost_blk_t * root ) {
318 18 : blk_pool_t * pool = blk_pool( ghost );
319 18 : ulong null = blk_pool_idx_null( pool );
320 18 : fd_ghost_blk_t * head = blk_map_ele_remove( blk_map( ghost ), &root->id, NULL, pool ); /* remove ele from map to reuse `.next` */
321 18 : fd_ghost_blk_t * tail = head;
322 18 : fd_ghost_blk_t * prev = NULL;
323 :
324 : /* Below is a level-order traversal (BFS), returning the last leaf
325 : which is guaranteed to return an element of the max depth.
326 :
327 : It temporarily removes elements of the map when pushing onto the
328 : BFS queue to reuse the .next pointer and then inserts back into
329 : the map on queue pop. */
330 :
331 18 : head->next = null;
332 99 : while( FD_LIKELY( head ) ) {
333 81 : fd_ghost_blk_t const * child = blk_pool_ele( pool, head->child );
334 144 : while( FD_LIKELY( child ) ) {
335 63 : FD_TEST( blk_map_ele_remove( blk_map( ghost ), &child->id, NULL, pool ) ); /* in the tree so must be in the map */
336 63 : tail->next = blk_pool_idx( pool, child );
337 63 : tail = blk_pool_ele( pool, tail->next );
338 63 : tail->next = blk_pool_idx_null( pool );
339 63 : child = blk_pool_ele( pool, child->sibling ); /* next sibling */
340 63 : }
341 81 : fd_ghost_blk_t * next = blk_pool_ele( pool, head->next ); /* pop prune queue head */
342 81 : blk_map_ele_insert( blk_map( ghost ), head, pool ); /* re-insert head into map */
343 81 : prev = head;
344 81 : head = next;
345 81 : }
346 18 : return prev;
347 18 : }
348 :
349 12 : #define PREDICATE_ANCESTOR( predicate ) do { \
350 12 : fd_ghost_blk_t * ancestor = descendant; \
351 36 : while( FD_LIKELY( ancestor ) ) { \
352 30 : if( FD_LIKELY( predicate ) ) return ancestor; \
353 30 : ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent ); \
354 24 : } \
355 12 : return NULL; \
356 12 : } while(0)
357 :
358 : fd_ghost_blk_t *
359 : fd_ghost_ancestor( fd_ghost_t * ghost,
360 : fd_ghost_blk_t * descendant,
361 6 : fd_hash_t const * ancestor_id ) {
362 6 : PREDICATE_ANCESTOR( 0==memcmp( &ancestor->id, ancestor_id, sizeof(fd_hash_t) ) );
363 6 : }
364 :
365 : fd_ghost_blk_t *
366 : fd_ghost_slot_ancestor( fd_ghost_t * ghost,
367 : fd_ghost_blk_t * descendant,
368 0 : ulong slot ) {
369 0 : PREDICATE_ANCESTOR( ancestor->slot == slot );
370 0 : }
371 :
372 : fd_ghost_blk_t *
373 : fd_ghost_invalid_ancestor( fd_ghost_t * ghost,
374 6 : fd_ghost_blk_t * descendant ) {
375 6 : PREDICATE_ANCESTOR( !ancestor->valid );
376 6 : }
377 :
378 : static fd_ghost_blk_t *
379 : insert( fd_ghost_t * ghost,
380 : ulong bank_seq,
381 : ulong slot,
382 414 : fd_hash_t const * block_id ) {
383 414 : fd_ghost_blk_t * pool = blk_pool( ghost );
384 414 : ulong null = blk_pool_idx_null( pool );
385 414 : fd_ghost_blk_t * blk = blk_map_ele_query( blk_map( ghost ), block_id, NULL, pool );
386 :
387 414 : FD_TEST( !blk ); /* duplicate insert */
388 414 : FD_TEST( blk_pool_free( pool ) ); /* ghost full */
389 :
390 414 : blk = blk_pool_ele_acquire( pool );
391 414 : blk->id = *block_id;
392 414 : blk->slot = slot;
393 414 : blk->next = null;
394 414 : blk->parent = null;
395 414 : blk->child = null;
396 414 : blk->sibling = null;
397 414 : blk->stake = 0;
398 414 : blk->total_stake = 0;
399 414 : blk->valid = 1;
400 414 : blk->bank_seq = bank_seq;
401 414 : blk_map_ele_insert( blk_map( ghost ), blk, pool );
402 414 : return blk;
403 414 : }
404 :
405 : fd_ghost_blk_t *
406 : fd_ghost_init( fd_ghost_t * ghost,
407 : ulong bank_seq,
408 : ulong slot,
409 60 : fd_hash_t const * block_id ) {
410 60 : fd_ghost_blk_t * blk = insert( ghost, bank_seq, slot, block_id );
411 60 : ghost->root = blk_pool_idx( blk_pool( ghost ), blk );
412 60 : ghost->width = 1;
413 60 : return blk;
414 60 : }
415 :
416 : fd_ghost_blk_t *
417 : fd_ghost_insert( fd_ghost_t * ghost,
418 : ulong bank_seq,
419 : ulong slot,
420 : fd_hash_t const * block_id,
421 354 : fd_hash_t const * parent_block_id ) {
422 354 : fd_ghost_blk_t * blk = insert( ghost, bank_seq, slot, block_id );
423 354 : fd_ghost_blk_t * pool = blk_pool( ghost );
424 354 : ulong null = blk_pool_idx_null( pool );
425 354 : fd_ghost_blk_t * parent = blk_map_ele_query( blk_map( ghost ), parent_block_id, NULL, pool );
426 354 : FD_TEST( parent ); /* parent must exist be in ghost */
427 354 : blk->parent = blk_pool_idx( pool, parent );
428 354 : if( FD_LIKELY( parent->child == null ) ) {
429 270 : parent->child = blk_pool_idx( pool, blk ); /* left-child */
430 270 : } else {
431 84 : fd_ghost_blk_t * sibling = blk_pool_ele( pool, parent->child );
432 93 : while( sibling->sibling != null ) sibling = blk_pool_ele( pool, sibling->sibling );
433 84 : sibling->sibling = blk_pool_idx( pool, blk ); /* right-sibling */
434 84 : ghost->width++;
435 84 : }
436 :
437 354 : return blk;
438 354 : }
439 :
440 : int
441 : fd_ghost_count_vote( fd_ghost_t * ghost,
442 : fd_ghost_blk_t * blk,
443 : fd_pubkey_t const * vote_acc,
444 : ulong stake,
445 33 : ulong slot ) {
446 :
447 33 : fd_ghost_blk_t const * root = fd_ghost_root( ghost );
448 33 : fd_ghost_vtr_t * vtr = vtr_map_ele_query( vtr_map( ghost ), vote_acc, NULL, vtr_pool( ghost ) );
449 :
450 33 : if( FD_UNLIKELY( slot==ULONG_MAX ) ) return FD_GHOST_ERR_NOT_VOTED;
451 33 : if( FD_UNLIKELY( slot< root->slot ) ) return FD_GHOST_ERR_VOTE_TOO_OLD;
452 :
453 33 : if( FD_UNLIKELY( !vtr ) ) {
454 :
455 : /* This vote account address has not previously voted, so add it to
456 : the map of voters. */
457 :
458 24 : vtr = vtr_pool_ele_acquire( vtr_pool( ghost ) );
459 24 : vtr->addr = *vote_acc;
460 24 : vtr_map_ele_insert( vtr_map( ghost ), vtr, vtr_pool( ghost ) );
461 :
462 24 : } else {
463 :
464 : /* Only process the vote if it is not the same as the previous vote
465 : and also that the vote slot is most recent. It's possible for
466 : ghost to process votes out of order because votes happen in
467 : replay order which is concurrent across different forks.
468 :
469 : For example, if a voter votes for 3 then switches to 5, we might
470 : observe the vote for 5 before the vote for 3. */
471 :
472 9 : if( FD_UNLIKELY( !( slot > vtr->prev_slot ) ) ) return FD_GHOST_ERR_ALREADY_VOTED;
473 :
474 : /* The voter is switching off their previous vote. Remove the vtr
475 : from the previous block's dlist; it is re-pushed onto the new
476 : block's dlist below. By the dlist invariant, a vtr that is still
477 : in the vtr_map has not had its previous block pruned, so prev is
478 : non-NULL. */
479 :
480 6 : fd_ghost_blk_t * prev = blk_map_ele_query( blk_map( ghost ), &vtr->prev_block_id, NULL, blk_pool( ghost ) );
481 6 : if( FD_LIKELY( prev ) ) vtr_dlist_ele_remove( blk_vtr_dlist( ghost, prev ), vtr, vtr_pool( ghost ) );
482 :
483 : /* LMD-rule: subtract the voter's stake from the entire fork they
484 : previously voted for. */
485 :
486 : /* TODO can optimize this if they're voting for the same fork */
487 :
488 6 : fd_ghost_blk_t * ancestor = prev;
489 24 : while( FD_LIKELY( ancestor ) ) {
490 18 : int cf = __builtin_usubl_overflow( ancestor->stake, vtr->prev_stake, &ancestor->stake );
491 18 : if( FD_UNLIKELY( cf ) ) {
492 0 : FD_BASE58_ENCODE_32_BYTES( ancestor->id.key, ancestor_id_b58 );
493 0 : FD_LOG_CRIT(( "[%s] overflow (after): %lu. subtracted: %lu. (slot %lu, block_id: %s)", __func__, ancestor->stake, vtr->prev_stake, ancestor->slot, ancestor_id_b58 ));
494 0 : }
495 18 : ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent );
496 18 : }
497 6 : }
498 :
499 : /* Park the vtr on the dlist of the block it is now voting for, so that
500 : it is released back to the vtr_pool when that block is pruned. */
501 :
502 30 : vtr_dlist_ele_push_tail( blk_vtr_dlist( ghost, blk ), vtr, vtr_pool( ghost ) );
503 :
504 : /* Add voter's stake to the entire fork they are voting for. Propagate
505 : the vote stake up the ancestry. We do this for all cases we exited
506 : above: this vote is the first vote we've seen from a pubkey, this
507 : vote is switched from a previous vote that was on a missing ele
508 : (pruned), or the regular case. */
509 :
510 30 : fd_ghost_blk_t * ancestor = blk;
511 129 : while( FD_LIKELY( ancestor ) ) {
512 99 : int cf = __builtin_uaddl_overflow( ancestor->stake, stake, &ancestor->stake );
513 99 : if( FD_UNLIKELY( cf ) ) {
514 0 : FD_BASE58_ENCODE_32_BYTES( ancestor->id.key, ancestor_id_b58 );
515 0 : FD_LOG_CRIT(( "[%s] overflow (after): %lu. added: %lu. (slot %lu, block_id: %s)", __func__, ancestor->stake, stake, ancestor->slot, ancestor_id_b58 ));
516 0 : }
517 99 : ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent );
518 99 : }
519 30 : vtr->prev_stake = stake;
520 30 : vtr->prev_slot = slot;
521 30 : vtr->prev_block_id = blk->id;
522 30 : return FD_GHOST_SUCCESS;
523 30 : }
524 :
525 : void
526 : fd_ghost_publish( fd_ghost_t * ghost,
527 9 : fd_ghost_blk_t * newr ) {
528 :
529 9 : fd_ghost_blk_t * pool = blk_pool( ghost );
530 9 : ulong null = blk_pool_idx_null( pool );
531 9 : fd_ghost_blk_t * oldr = fd_ghost_root( ghost );
532 :
533 9 : if( FD_UNLIKELY( oldr==newr ) ) return;
534 :
535 : /* First, remove the previous root, and add it to the prune list. In
536 : this context, head is the list head (not to be confused with the
537 : ghost head.) */
538 :
539 9 : fd_ghost_blk_t * head = blk_map_ele_remove( blk_map( ghost ), &oldr->id, NULL, pool ); /* remove ele from map to reuse `.next` */
540 9 : fd_ghost_blk_t * tail = head;
541 :
542 : /* Second, BFS down the tree, pruning all of root's ancestors and also
543 : any descendants of those ancestors.
544 :
545 : oldr
546 : |
547 : X
548 : / \
549 : newr Y
550 : |
551 : Z
552 :
553 : ...
554 :
555 : newr
556 :
557 : BFS starts with oldr. Its child is X. X != newr, so X gets
558 : enqueued. oldr is released. Next head = X. X's children are newr
559 : and Y. newr is skipped. Y gets enqueued. X is released. Next
560 : head = Y. Y's child Z gets enqueued. Y released. Z released.
561 : Queue is empty, loop ends.
562 :
563 : oldr
564 : / \
565 : A newr
566 : / \
567 : B C
568 :
569 : ...
570 :
571 : newr
572 : / \
573 : B C
574 :
575 :
576 : The BFS starts with oldr. Its children are A and newr. A gets
577 : enqueued for pruning. newr is skipped (line 374). Then oldr is
578 : released. Next, head = A. A has no children. A is released.
579 : Queue is empty, loop ends. */
580 :
581 9 : head->next = null;
582 48 : while( FD_LIKELY( head ) ) {
583 39 : fd_ghost_blk_t * child = blk_pool_ele( blk_pool( ghost ), head->child );
584 78 : while( FD_LIKELY( child ) ) { /* iterate over children */
585 39 : if( FD_LIKELY( child != newr ) ) { /* stop at new root */
586 30 : tail->next = blk_map_idx_remove( blk_map( ghost ), &child->id, null, pool ); /* remove ele from map to reuse `.next` */
587 30 : FD_BASE58_ENCODE_32_BYTES( child->id.key, block_id_cstr );
588 30 : tail = blk_pool_ele( blk_pool( ghost ), tail->next ); /* push onto prune queue (so descendants can be pruned) */
589 30 : tail->next = blk_pool_idx_null( blk_pool( ghost ) );
590 30 : }
591 39 : child = blk_pool_ele( blk_pool( ghost ), child->sibling ); /* next sibling */
592 39 : ghost->width -= !!child; /* has a sibling == a fork to be pruned */
593 39 : }
594 39 : fd_ghost_blk_t * next = blk_pool_ele( blk_pool( ghost ), head->next ); /* pop prune queue head */
595 :
596 : /* Release every vtr parked on this pruned block's dlist back to the
597 : vtr_pool. Their previous stake lived on this pruned subtree (which
598 : is being discarded along with the block), so there is nothing to
599 : unwind; if those voters vote again on a surviving block,
600 : fd_ghost_count_vote acquires a fresh vtr. This is what bounds the
601 : vtr_pool to the set of voters on the live tree. */
602 :
603 39 : vtr_dlist_t * dlist = blk_vtr_dlist( ghost, head );
604 45 : while( FD_LIKELY( !vtr_dlist_is_empty( dlist, vtr_pool( ghost ) ) ) ) {
605 6 : fd_ghost_vtr_t * vtr = vtr_dlist_ele_pop_head( dlist, vtr_pool( ghost ) );
606 6 : vtr_map_ele_remove_fast( vtr_map( ghost ), vtr, vtr_pool( ghost ) );
607 6 : vtr_pool_ele_release( vtr_pool( ghost ), vtr );
608 6 : }
609 :
610 39 : blk_pool_ele_release( blk_pool( ghost ), head ); /* free prune queue head */
611 39 : head = next; /* move prune queue head forward */
612 39 : }
613 9 : newr->parent = null; /* unlink old root */
614 9 : ghost->root = blk_pool_idx( blk_pool( ghost ), newr ); /* replace with new root */
615 9 : }
616 :
617 : /* mark_invalid marks the entire subtree beginning from root as invalid.
618 : Implementation is iterative pre-order traversal using O(1) space. */
619 :
620 : static void
621 : mark_invalid( fd_ghost_t * ghost,
622 6 : fd_ghost_blk_t * root ) {
623 6 : fd_ghost_blk_t * pool = blk_pool( ghost );
624 6 : fd_ghost_blk_t * curr = root;
625 :
626 : /* Loop invariant: curr has not been visited.
627 :
628 : Before: curr = root, which has not been visited. Trivially true.
629 :
630 : After: curr is set to either a child (step 2) or a right sibling of
631 : an ancestor found during backtracking (step 3). Preorder visits
632 : parents before children and left before right, so neither has been
633 : visited yet. If backtracking reaches root (step 4), loop exits. */
634 :
635 18 : for(;;) {
636 :
637 : /* 1. Visit: mark the current curr invalid. */
638 :
639 18 : curr->valid = 0;
640 :
641 : /* 2. Descend: if the curr has a child, pivot to it. */
642 :
643 18 : fd_ghost_blk_t * child = blk_pool_ele( pool, curr->child );
644 18 : if( FD_LIKELY( child ) ) { curr = child; continue; }
645 :
646 : /* 3. Backtrack: if the curr is a leaf, traverse up until we find an
647 : ancestor with a right sibling, then pivot to that sibling. */
648 :
649 18 : while( FD_LIKELY( curr!=root ) ) {
650 12 : fd_ghost_blk_t * sibling = blk_pool_ele( pool, curr->sibling );
651 12 : if( FD_LIKELY( sibling ) ) { curr = sibling; break; }
652 12 : curr = blk_pool_ele( pool, curr->parent );
653 12 : }
654 :
655 : /* 4. Terminate: if we backtrack all the way to root, the traversal
656 : is complete. */
657 :
658 6 : if( FD_UNLIKELY( curr==root ) ) break;
659 6 : }
660 6 : }
661 :
662 : void
663 : fd_ghost_confirm( fd_ghost_t * ghost,
664 0 : fd_hash_t const * confirmed_block_id ) {
665 0 : fd_ghost_blk_t * pool = blk_pool( ghost );
666 0 : fd_ghost_blk_t * blk = blk_map_ele_query( blk_map( ghost ), confirmed_block_id, NULL, pool );
667 0 : if( FD_UNLIKELY( !blk ) ) return;
668 :
669 : /* Mark the confirmed block and its ancestors as valid, short-
670 : circuiting at the first ancestor that is already valid. */
671 :
672 0 : fd_ghost_blk_t * anc = blk;
673 0 : while( FD_LIKELY( anc ) ) {
674 0 : if( FD_LIKELY( anc->valid ) ) break;
675 0 : anc->valid = 1;
676 0 : anc = blk_pool_ele( pool, anc->parent );
677 0 : }
678 0 : }
679 :
680 : void
681 : fd_ghost_eqvoc( fd_ghost_t * ghost,
682 6 : fd_hash_t const * block_id ) {
683 6 : fd_ghost_blk_t * pool = blk_pool( ghost );
684 6 : fd_ghost_blk_t * blk = blk_map_ele_query( blk_map( ghost ), block_id, NULL, pool );
685 6 : if( FD_UNLIKELY( !blk ) ) return;
686 6 : mark_invalid( ghost, blk );
687 6 : }
688 :
689 : ulong
690 0 : fd_ghost_width( fd_ghost_t * ghost ) {
691 0 : return ghost->width;
692 0 : }
693 :
694 : fd_ghost_blk_t *
695 : fd_ghost_blk_map_remove( fd_ghost_t * ghost,
696 588 : fd_ghost_blk_t * blk ) {
697 588 : return blk_map_ele_remove( blk_map( ghost ), &blk->id, NULL, blk_pool( ghost ) );
698 588 : }
699 :
700 : void
701 : fd_ghost_blk_map_insert( fd_ghost_t * ghost,
702 588 : fd_ghost_blk_t * blk ) {
703 588 : blk_map_ele_insert( blk_map( ghost ), blk, blk_pool( ghost ) );
704 588 : }
705 :
706 : fd_ghost_blk_t *
707 : fd_ghost_blk_child( fd_ghost_t * ghost,
708 564 : fd_ghost_blk_t * blk ) {
709 564 : return blk_pool_ele( blk_pool( ghost ), blk->child );
710 564 : }
711 :
712 : fd_ghost_blk_t *
713 : fd_ghost_blk_sibling( fd_ghost_t * ghost,
714 543 : fd_ghost_blk_t * blk ) {
715 543 : return blk_pool_ele( blk_pool( ghost ), blk->sibling );
716 543 : }
717 :
718 : fd_ghost_blk_t *
719 : fd_ghost_blk_next( fd_ghost_t * ghost,
720 588 : fd_ghost_blk_t * blk ) {
721 588 : return blk_pool_ele( blk_pool( ghost ), blk->next );
722 588 : }
723 :
724 : ulong
725 : fd_ghost_blk_idx( fd_ghost_t * ghost,
726 537 : fd_ghost_blk_t * blk ) {
727 537 : return blk_pool_idx( blk_pool( ghost ), blk );
728 537 : }
729 :
730 : ulong
731 51 : fd_ghost_blk_idx_null( fd_ghost_t * ghost ) {
732 51 : return blk_pool_idx_null( blk_pool( ghost ) );
733 51 : }
734 :
735 : int
736 45 : fd_ghost_verify( fd_ghost_t * ghost ) {
737 45 : if( FD_UNLIKELY( !ghost ) ) {
738 0 : FD_LOG_WARNING(( "NULL ghost" ));
739 0 : return -1;
740 0 : }
741 :
742 45 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)ghost, fd_ghost_align() ) ) ) {
743 0 : FD_LOG_WARNING(( "misaligned ghost" ));
744 0 : return -1;
745 0 : }
746 :
747 45 : fd_wksp_t * wksp = fd_wksp_containing( ghost );
748 45 : if( FD_UNLIKELY( !wksp ) ) {
749 0 : FD_LOG_WARNING(( "ghost must be part of a workspace" ));
750 0 : return -1;
751 0 : }
752 :
753 45 : fd_ghost_blk_t const * pool = blk_pool( ghost );
754 :
755 : /* Check every ele that exists in pool exists in map. */
756 :
757 45 : if( blk_map_verify( blk_map( ghost ), blk_pool_max( pool ), pool ) ) return -1;
758 :
759 45 : return 0;
760 45 : }
761 :
762 : #include <stdio.h>
763 : #include <string.h>
764 :
765 : #define BUF_MAX 4096
766 : #define DEPTH_MAX 512
767 :
768 : static void
769 : to_cstr( fd_ghost_t const * ghost,
770 : fd_ghost_blk_t const * ele,
771 : ulong total_stake,
772 : int space,
773 : const char * prefix,
774 : char * cstr,
775 : ulong len,
776 : ulong * off,
777 0 : ulong depth ) {
778 0 : if( FD_UNLIKELY( depth>DEPTH_MAX ) ) return;
779 :
780 0 : fd_ghost_blk_t const * pool = blk_pool_const( ghost );
781 0 : int n;
782 :
783 0 : if( FD_UNLIKELY( ele == NULL ) ) return;
784 :
785 0 : if( FD_LIKELY( space > 0 ) && *off < len ) {
786 0 : cstr[(*off)++] = '\n';
787 0 : }
788 :
789 0 : for( int i = 0; i < space && *off < len; i++ ) {
790 0 : cstr[(*off)++] = ' ';
791 0 : }
792 :
793 0 : if( FD_UNLIKELY( ele->stake > 100 ) ) {
794 0 : }
795 :
796 0 : if( FD_UNLIKELY( total_stake == 0 ) ) {
797 0 : if( *off < len ) {
798 0 : n = snprintf( cstr + *off, len - *off, "%s%lu (%lu)", prefix, ele->slot, ele->stake );
799 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
800 0 : *off += (ulong)n;
801 0 : }
802 0 : } else {
803 0 : double pct = ( (double)ele->stake / (double)total_stake ) * 100;
804 0 : if( FD_UNLIKELY( pct < 0.99 ) ) {
805 0 : if( *off < len ) {
806 0 : n = snprintf( cstr + *off, len - *off, "%s%lu (%.0lf%%, %lu)", prefix, ele->slot, pct, ele->stake );
807 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
808 0 : *off += (ulong)n;
809 0 : }
810 0 : } else {
811 0 : if( *off < len ) {
812 0 : n = snprintf( cstr + *off, len - *off, "%s%lu (%.0lf%%)", prefix, ele->slot, pct );
813 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
814 0 : *off += (ulong)n;
815 0 : }
816 0 : }
817 0 : }
818 :
819 0 : fd_ghost_blk_t const * curr = blk_pool_ele_const( pool, ele->child );
820 :
821 0 : while( curr ) {
822 0 : char const * next_prefix = blk_pool_ele_const( pool, curr->sibling ) ? "├── " : "└── ";
823 0 : to_cstr( ghost, curr, total_stake, space + 4, next_prefix, cstr, len, off, depth + 1 ); /* TODO remove recursion */
824 0 : curr = blk_pool_ele_const( pool, curr->sibling );
825 0 : }
826 0 : }
827 :
828 : char *
829 : fd_ghost_to_cstr( fd_ghost_t const * ghost,
830 : fd_ghost_blk_t const * root,
831 : char * cstr,
832 : ulong cstr_max,
833 0 : ulong * cstr_len ) {
834 :
835 0 : ulong off = 0;
836 :
837 0 : int n = snprintf( cstr + off, cstr_max - off, "[Ghost]\n\n" );
838 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
839 0 : off += (ulong)n;
840 :
841 0 : to_cstr( ghost, root, root->total_stake, 0, "", cstr, cstr_max, &off, 0 );
842 :
843 0 : if( off < cstr_max ) {
844 0 : n = snprintf( cstr + off, cstr_max - off, "\n\n" );
845 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
846 0 : off += (ulong)n;
847 0 : }
848 :
849 0 : cstr[fd_ulong_min( off++, cstr_max - 1 )] = '\0';
850 0 : *cstr_len = fd_ulong_min( off, cstr_max );
851 0 : return cstr;
852 0 : }
|