Line data Source code
1 : #include "fd_ghost.h"
2 : #include "fd_ghost_private.h"
3 :
4 : #define LOGGING 0
5 :
6 : ulong
7 135 : fd_ghost_align( void ) {
8 135 : return alignof(fd_ghost_t);
9 135 : }
10 :
11 : ulong
12 : fd_ghost_footprint( ulong blk_max,
13 24 : ulong vtr_max ) {
14 24 : return FD_LAYOUT_FINI(
15 24 : FD_LAYOUT_APPEND(
16 24 : FD_LAYOUT_APPEND(
17 24 : FD_LAYOUT_APPEND(
18 24 : FD_LAYOUT_APPEND(
19 24 : FD_LAYOUT_APPEND(
20 24 : FD_LAYOUT_INIT,
21 24 : alignof(fd_ghost_t), sizeof(fd_ghost_t) ),
22 24 : blk_pool_align(), blk_pool_footprint( blk_max ) ),
23 24 : blk_map_align(), blk_map_footprint ( blk_max ) ),
24 24 : vtr_pool_align(), vtr_pool_footprint( vtr_max ) ),
25 24 : vtr_map_align(), vtr_map_footprint ( vtr_max ) ),
26 24 : fd_ghost_align() );
27 24 : }
28 :
29 : void *
30 : fd_ghost_new( void * shmem,
31 : ulong blk_max,
32 : ulong vtr_max,
33 12 : ulong seed ) {
34 :
35 12 : if( FD_UNLIKELY( !shmem ) ) {
36 0 : FD_LOG_WARNING(( "NULL mem" ));
37 0 : return NULL;
38 0 : }
39 :
40 12 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_ghost_align() ) ) ) {
41 0 : FD_LOG_WARNING(( "misaligned mem" ));
42 0 : return NULL;
43 0 : }
44 :
45 12 : ulong footprint = fd_ghost_footprint( blk_max, vtr_max );
46 12 : if( FD_UNLIKELY( !footprint ) ) {
47 0 : FD_LOG_WARNING(( "bad blk_max (%lu)", blk_max ));
48 0 : return NULL;
49 0 : }
50 :
51 12 : fd_wksp_t * wksp = fd_wksp_containing( shmem );
52 12 : if( FD_UNLIKELY( !wksp ) ) {
53 0 : FD_LOG_WARNING(( "shmem must be part of a workspace" ));
54 0 : return NULL;
55 0 : }
56 :
57 12 : fd_memset( shmem, 0, footprint );
58 :
59 12 : FD_SCRATCH_ALLOC_INIT( l, shmem );
60 12 : fd_ghost_t * ghost = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_ghost_t), sizeof(fd_ghost_t) );
61 12 : void * blk_pool = FD_SCRATCH_ALLOC_APPEND( l, blk_pool_align(), blk_pool_footprint( blk_max ) );
62 12 : void * blk_map = FD_SCRATCH_ALLOC_APPEND( l, blk_map_align(), blk_map_footprint ( blk_max ) );
63 12 : void * vtr_pool = FD_SCRATCH_ALLOC_APPEND( l, vtr_pool_align(), vtr_pool_footprint( vtr_max ) );
64 12 : void * vtr_map = FD_SCRATCH_ALLOC_APPEND( l, vtr_map_align(), vtr_map_footprint ( vtr_max ) );
65 12 : FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_ghost_align() ) == (ulong)shmem + footprint );
66 :
67 12 : ghost->root = ULONG_MAX;
68 12 : ghost->ghost_gaddr = fd_wksp_gaddr_fast( wksp, ghost );
69 12 : ghost->blk_pool_gaddr = fd_wksp_gaddr_fast( wksp, blk_pool_join( blk_pool_new ( blk_pool, blk_max ) ) );
70 12 : ghost->blk_map_gaddr = fd_wksp_gaddr_fast( wksp, blk_map_join ( blk_map_new ( blk_map, blk_max, seed ) ) );
71 12 : ghost->vtr_pool_gaddr = fd_wksp_gaddr_fast( wksp, vtr_pool_join( vtr_pool_new ( vtr_pool, vtr_max ) ) );
72 12 : ghost->vtr_map_gaddr = fd_wksp_gaddr_fast( wksp, vtr_map_join ( vtr_map_new ( vtr_map, vtr_max, seed ) ) );
73 :
74 12 : return shmem;
75 12 : }
76 :
77 : fd_ghost_t *
78 12 : fd_ghost_join( void * shghost ) {
79 12 : fd_ghost_t * ghost = (fd_ghost_t *)shghost;
80 :
81 12 : if( FD_UNLIKELY( !ghost ) ) {
82 0 : FD_LOG_WARNING(( "NULL ghost" ));
83 0 : return NULL;
84 0 : }
85 :
86 12 : if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)ghost, fd_ghost_align() ) ) ) {
87 0 : FD_LOG_WARNING(( "misaligned ghost" ));
88 0 : return NULL;
89 0 : }
90 :
91 12 : return ghost;
92 12 : }
93 :
94 : void *
95 12 : fd_ghost_leave( fd_ghost_t const * ghost ) {
96 :
97 12 : if( FD_UNLIKELY( !ghost ) ) {
98 0 : FD_LOG_WARNING(( "NULL ghost" ));
99 0 : return NULL;
100 0 : }
101 :
102 12 : return (void *)ghost;
103 12 : }
104 :
105 : void *
106 12 : fd_ghost_delete( void * ghost ) {
107 :
108 12 : if( FD_UNLIKELY( !ghost ) ) {
109 0 : FD_LOG_WARNING(( "NULL ghost" ));
110 0 : return NULL;
111 0 : }
112 :
113 12 : if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)ghost, fd_ghost_align() ) ) ) {
114 0 : FD_LOG_WARNING(( "misaligned ghost" ));
115 0 : return NULL;
116 0 : }
117 :
118 12 : return ghost;
119 12 : }
120 :
121 : ulong
122 0 : fd_ghost_gaddr( fd_ghost_t const * ghost ) {
123 0 : return ghost->ghost_gaddr;
124 0 : }
125 :
126 : fd_ghost_blk_t *
127 54 : fd_ghost_root( fd_ghost_t * ghost ) {
128 54 : return blk_pool_ele( blk_pool( ghost ), ghost->root );
129 54 : }
130 :
131 : fd_ghost_blk_t *
132 : fd_ghost_query( fd_ghost_t * ghost,
133 102 : fd_hash_t const * block_id ) {
134 102 : return blk_map_ele_query( blk_map( ghost ), block_id, NULL, blk_pool( ghost ) );
135 102 : }
136 :
137 : fd_ghost_blk_t *
138 : fd_ghost_best( fd_ghost_t * ghost,
139 15 : fd_ghost_blk_t * root ) {
140 15 : blk_pool_t * pool = blk_pool( ghost );
141 15 : ulong null = blk_pool_idx_null( pool );
142 15 : fd_ghost_blk_t * best = root;
143 54 : while( FD_LIKELY( best->child != null ) ) {
144 39 : int valid = 0; /* at least one child is valid */
145 39 : fd_ghost_blk_t * child = blk_pool_ele( pool, best->child );
146 87 : while( FD_LIKELY( child ) ) { /* greedily pick the heaviest valid child */
147 48 : if( FD_LIKELY( child->valid ) ) {
148 48 : if( FD_LIKELY( !valid ) ) { /* this is the first valid child, so progress the head */
149 39 : best = child;
150 39 : valid = 1;
151 39 : }
152 48 : best = fd_ptr_if(
153 48 : fd_int_if(
154 48 : child->stake == best->stake, /* if the weights are equal */
155 48 : child->slot < best->slot, /* then tie-break by lower slot number */
156 48 : child->stake > best->stake ), /* else return heavier */
157 48 : child, best );
158 48 : }
159 48 : child = blk_pool_ele( pool, child->sibling );
160 48 : }
161 39 : if( FD_UNLIKELY( !valid ) ) break; /* no children are valid, so short-circuit traversal */
162 39 : }
163 15 : return best;
164 15 : }
165 :
166 : fd_ghost_blk_t *
167 : fd_ghost_deepest( fd_ghost_t * ghost,
168 15 : fd_ghost_blk_t * root ) {
169 15 : blk_pool_t * pool = blk_pool( ghost );
170 15 : ulong null = blk_pool_idx_null( pool );
171 15 : fd_ghost_blk_t * head = blk_map_ele_remove( blk_map( ghost ), &root->id, NULL, pool ); /* remove ele from map to reuse `.next` */
172 15 : fd_ghost_blk_t * tail = head;
173 15 : fd_ghost_blk_t * prev = NULL;
174 :
175 : /* Below is a level-order traversal (BFS), returning the last leaf
176 : which is guaranteed to return an element of the max depth.
177 :
178 : It temporarily removes elements of the map when pushing onto the
179 : BFS queue to reuse the .next pointer and then inserts back into
180 : the map on queue pop. */
181 :
182 15 : head->next = null;
183 93 : while( FD_LIKELY( head ) ) {
184 78 : fd_ghost_blk_t const * child = blk_pool_ele( pool, head->child );
185 141 : while( FD_LIKELY( child ) ) {
186 63 : tail->next = blk_pool_idx( pool, blk_map_ele_remove( blk_map( ghost ), &child->id, NULL, pool ) );
187 63 : tail = blk_pool_ele( pool, tail->next );
188 63 : tail->next = blk_pool_idx_null( pool );
189 63 : child = blk_pool_ele( pool, child->sibling ); /* next sibling */
190 63 : }
191 78 : fd_ghost_blk_t * next = blk_pool_ele( pool, head->next ); /* pop prune queue head */
192 78 : blk_map_ele_insert( blk_map( ghost ), head, pool ); /* re-insert head into map */
193 78 : prev = head;
194 78 : head = next;
195 78 : }
196 15 : return prev;
197 15 : }
198 :
199 0 : #define PREDICATE_ANCESTOR( predicate ) do { \
200 0 : fd_ghost_blk_t * ancestor = descendant; \
201 0 : while( FD_LIKELY( ancestor ) ) { \
202 0 : if( FD_LIKELY( predicate ) ) return ancestor; \
203 0 : ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent ); \
204 0 : } \
205 0 : return NULL; \
206 0 : } while(0)
207 :
208 : fd_ghost_blk_t *
209 : fd_ghost_ancestor( fd_ghost_t * ghost,
210 : fd_ghost_blk_t * descendant,
211 0 : fd_hash_t const * ancestor_id ) {
212 0 : PREDICATE_ANCESTOR( 0==memcmp( &ancestor->id, ancestor_id, sizeof(fd_hash_t) ) );
213 0 : }
214 :
215 : fd_ghost_blk_t *
216 : fd_ghost_slot_ancestor( fd_ghost_t * ghost,
217 : fd_ghost_blk_t * descendant,
218 0 : ulong slot ) {
219 0 : PREDICATE_ANCESTOR( ancestor->slot == slot );
220 0 : }
221 :
222 : fd_ghost_blk_t *
223 : fd_ghost_invalid_ancestor( fd_ghost_t * ghost,
224 0 : fd_ghost_blk_t * descendant ) {
225 0 : PREDICATE_ANCESTOR( !ancestor->valid );
226 0 : }
227 :
228 : fd_ghost_blk_t *
229 : fd_ghost_insert( fd_ghost_t * ghost,
230 : fd_hash_t const * block_id,
231 : fd_hash_t const * parent_block_id,
232 84 : ulong slot ) {
233 :
234 84 : fd_ghost_blk_t * pool = blk_pool( ghost );
235 84 : ulong null = blk_pool_idx_null( pool );
236 84 : fd_ghost_blk_t * blk = blk_map_ele_query( blk_map( ghost ), block_id, NULL, pool );
237 :
238 84 : # if FD_GHOST_USE_HANDHOLDING
239 84 : if( FD_UNLIKELY( blk ) ) {
240 0 : FD_BASE58_ENCODE_32_BYTES( block_id->key, block_id_b58 );
241 0 : FD_LOG_WARNING(( "[%s] hash %s already in ghost", __func__, block_id_b58 ));
242 0 : return NULL;
243 0 : }
244 84 : if( FD_UNLIKELY( !blk_pool_free( pool ) ) ) { FD_LOG_WARNING(( "[%s] ghost full", __func__ )); return NULL; }
245 84 : # endif
246 :
247 84 : blk = blk_pool_ele_acquire( pool );
248 84 : blk->id = *block_id;
249 84 : blk->slot = slot;
250 84 : blk->next = null;
251 84 : blk->parent = null;
252 84 : blk->child = null;
253 84 : blk->sibling = null;
254 84 : blk->stake = 0;
255 84 : blk->total_stake = 0;
256 84 : blk->eqvoc = 0;
257 84 : blk->conf = 0;
258 84 : blk->valid = 1;
259 84 : blk_map_ele_insert( blk_map( ghost ), blk, pool );
260 :
261 84 : if( FD_UNLIKELY( !parent_block_id ) ) {
262 12 : ghost->root = blk_pool_idx( pool, blk );
263 12 : return blk;
264 12 : }
265 :
266 72 : fd_ghost_blk_t * parent = blk_map_ele_query( blk_map( ghost ), parent_block_id, NULL, pool );
267 72 : FD_TEST( parent ); /* parent must exist if this is not the first insertion */
268 72 : blk->parent = blk_pool_idx( pool, parent );
269 72 : if( FD_LIKELY( parent->child == null ) ) {
270 60 : parent->child = blk_pool_idx( pool, blk ); /* left-child */
271 60 : } else {
272 12 : fd_ghost_blk_t * sibling = blk_pool_ele( pool, parent->child );
273 12 : while( sibling->sibling != null ) sibling = blk_pool_ele( pool, sibling->sibling );
274 12 : sibling->sibling = blk_pool_idx( pool, blk ); /* right-sibling */
275 12 : }
276 :
277 72 : return blk;
278 72 : }
279 :
280 : void
281 : fd_ghost_count_vote( fd_ghost_t * ghost,
282 : fd_ghost_blk_t * blk,
283 : fd_pubkey_t const * vote_acc,
284 : ulong stake,
285 12 : ulong slot ) {
286 :
287 12 : fd_ghost_blk_t const * root = fd_ghost_root( ghost );
288 12 : fd_ghost_vtr_t * vtr = vtr_map_ele_query( vtr_map( ghost ), vote_acc, NULL, vtr_pool( ghost ) );
289 :
290 12 : if( FD_UNLIKELY( slot == ULONG_MAX ) ) return; /* hasn't voted */
291 12 : if( FD_UNLIKELY( slot < root->slot ) ) return; /* vote older than root */
292 :
293 12 : if( FD_UNLIKELY( !vtr ) ) {
294 :
295 : /* This vote account address has not previously voted, so add it to
296 : the map of voters. */
297 :
298 6 : vtr = vtr_pool_ele_acquire( vtr_pool( ghost ) );
299 6 : vtr->addr = *vote_acc;
300 6 : vtr_map_ele_insert( vtr_map( ghost ), vtr, vtr_pool( ghost ) );
301 :
302 6 : } else {
303 :
304 : /* Only process the vote if it is not the same as the previous vote
305 : and also that the vote slot is most recent. It's possible for
306 : ghost to process votes out of order because votes happen in
307 : replay order which is concurrent across different forks.
308 :
309 : For example, if a voter votes for 3 then switches to 5, we might
310 : observe the vote for 5 before the vote for 3. */
311 :
312 6 : if( FD_UNLIKELY( !( slot > vtr->prev_slot ) ) ) return;
313 :
314 : /* LMD-rule: subtract the voter's stake from the entire fork they
315 : previously voted for. */
316 :
317 : /* TODO can optimize this if they're voting for the same fork */
318 :
319 6 : fd_ghost_blk_t * ancestor = blk_map_ele_query( blk_map( ghost ), &vtr->prev_block_id, NULL, blk_pool( ghost ) );
320 24 : while( FD_LIKELY( ancestor ) ) {
321 18 : int cf = __builtin_usubl_overflow( ancestor->stake, vtr->prev_stake, &ancestor->stake );
322 18 : if( FD_UNLIKELY( cf ) ) {
323 0 : FD_BASE58_ENCODE_32_BYTES( ancestor->id.key, ancestor_id_b58 );
324 0 : FD_LOG_CRIT(( "[%s] overflow: %lu - %lu. (slot %lu, block_id: %s)", __func__, ancestor->stake, vtr->prev_stake, ancestor->slot, ancestor_id_b58 ));
325 0 : }
326 18 : ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent );
327 18 : }
328 6 : }
329 :
330 : /* Add voter's stake to the entire fork they are voting for. Propagate
331 : the vote stake up the ancestry. We do this for all cases we exited
332 : above: this vote is the first vote we've seen from a pubkey, this
333 : vote is switched from a previous vote that was on a missing ele
334 : (pruned), or the regular case. */
335 :
336 12 : fd_ghost_blk_t * ancestor = blk;
337 54 : while( FD_LIKELY( ancestor ) ) {
338 42 : int cf = __builtin_uaddl_overflow( ancestor->stake, stake, &ancestor->stake );
339 42 : if( FD_UNLIKELY( cf ) ) {
340 0 : FD_BASE58_ENCODE_32_BYTES( ancestor->id.key, ancestor_id_b58 );
341 0 : FD_LOG_CRIT(( "[%s] overflow: %lu + %lu. (slot %lu, block_id: %s)", __func__, ancestor->stake, stake, ancestor->slot, ancestor_id_b58 ));
342 0 : }
343 42 : ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent );
344 42 : }
345 12 : vtr->prev_block_id = blk->id;
346 12 : vtr->prev_stake = stake;
347 12 : }
348 :
349 : void
350 : fd_ghost_publish( fd_ghost_t * ghost,
351 6 : fd_ghost_blk_t * newr ) {
352 :
353 6 : fd_ghost_blk_t * pool = blk_pool( ghost );
354 6 : ulong null = blk_pool_idx_null( pool );
355 6 : fd_ghost_blk_t * oldr = fd_ghost_root( ghost );
356 :
357 6 : if( FD_UNLIKELY( oldr==newr ) ) return;
358 :
359 : /* First, remove the previous root, and add it to the prune list. In
360 : this context, head is the list head (not to be confused with the
361 : ghost head.) */
362 :
363 6 : fd_ghost_blk_t * head = blk_map_ele_remove( blk_map( ghost ), &oldr->id, NULL, pool ); /* remove ele from map to reuse `.next` */
364 6 : fd_ghost_blk_t * tail = head;
365 :
366 : /* Second, BFS down the tree, pruning all of root's ancestors and also
367 : any descendants of those ancestors. */
368 :
369 6 : head->next = null;
370 33 : while( FD_LIKELY( head ) ) {
371 27 : fd_ghost_blk_t * child = blk_pool_ele( blk_pool( ghost ), head->child );
372 54 : while( FD_LIKELY( child ) ) { /* iterate over children */
373 27 : if( FD_LIKELY( child != newr ) ) { /* stop at new root */
374 21 : tail->next = blk_map_idx_remove( blk_map( ghost ), &child->id, null, pool ); /* remove ele from map to reuse `.next` */
375 21 : tail = blk_pool_ele( blk_pool( ghost ), tail->next ); /* push onto prune queue (so descendants can be pruned) */
376 21 : tail->next = blk_pool_idx_null( blk_pool( ghost ) );
377 21 : }
378 27 : child = blk_pool_ele( blk_pool( ghost ), child->sibling ); /* next sibling */
379 27 : }
380 27 : fd_ghost_blk_t * next = blk_pool_ele( blk_pool( ghost ), head->next ); /* pop prune queue head */
381 27 : blk_pool_ele_release( blk_pool( ghost ), head ); /* free prune queue head */
382 27 : head = next; /* move prune queue head forward */
383 27 : }
384 6 : newr->parent = null; /* unlink old root */
385 6 : ghost->root = blk_pool_idx( blk_pool( ghost ), newr ); /* replace with new root */
386 6 : }
387 :
388 : int
389 15 : fd_ghost_verify( fd_ghost_t * ghost ) {
390 15 : if( FD_UNLIKELY( !ghost ) ) {
391 0 : FD_LOG_WARNING(( "NULL ghost" ));
392 0 : return -1;
393 0 : }
394 :
395 15 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)ghost, fd_ghost_align() ) ) ) {
396 0 : FD_LOG_WARNING(( "misaligned ghost" ));
397 0 : return -1;
398 0 : }
399 :
400 15 : fd_wksp_t * wksp = fd_wksp_containing( ghost );
401 15 : if( FD_UNLIKELY( !wksp ) ) {
402 0 : FD_LOG_WARNING(( "ghost must be part of a workspace" ));
403 0 : return -1;
404 0 : }
405 :
406 15 : fd_ghost_blk_t const * pool = blk_pool( ghost );
407 15 : ulong null = blk_pool_idx_null( pool );
408 :
409 : /* Check every ele that exists in pool exists in map. */
410 :
411 15 : if( blk_map_verify( blk_map( ghost ), blk_pool_max( pool ), pool ) ) return -1;
412 :
413 : /* Check every ele's stake is >= sum of children's stakes. */
414 :
415 15 : fd_ghost_blk_t const * parent = fd_ghost_root( ghost );
416 30 : while( FD_LIKELY( parent ) ) {
417 15 : ulong weight = 0;
418 15 : fd_ghost_blk_t const * child = blk_pool_ele( blk_pool( ghost ), parent->child );
419 15 : while( FD_LIKELY( child && child->sibling != null ) ) {
420 0 : weight += child->stake;
421 0 : child = blk_pool_ele( blk_pool( ghost ), child->sibling );
422 0 : }
423 15 : # if FD_GHOST_USE_HANDHOLDING
424 15 : FD_TEST( parent->stake >= weight );
425 15 : # endif
426 15 : parent = blk_pool_ele_const( pool, parent->next );
427 15 : }
428 :
429 15 : return 0;
430 15 : }
431 :
432 : #include <stdio.h>
433 :
434 0 : #define PRINT( fmt, ... ) do { if( FD_LIKELY( ostream_opt ) ) { snprintf( buf, sizeof(buf), fmt, ##__VA_ARGS__ ); fd_io_buffered_ostream_write( ostream_opt, buf, strlen(buf) ); } else { printf( fmt, ##__VA_ARGS__ ); } } while(0)
435 0 : #define PRINT_STR( str ) do { if( FD_LIKELY( ostream_opt ) ) { fd_io_buffered_ostream_write( ostream_opt, str, strlen(str) ); } else { printf( str ); } } while(0)
436 : static void
437 0 : print( fd_ghost_t const * ghost, fd_ghost_blk_t const * ele, ulong total_stake, int space, const char * prefix, fd_io_buffered_ostream_t * ostream_opt ) {
438 0 : fd_ghost_blk_t const * pool = blk_pool_const( ghost );
439 0 : char buf[1024];
440 :
441 0 : if( FD_UNLIKELY( ele == NULL ) ) return;
442 :
443 0 : if( FD_LIKELY( space > 0 ) ) PRINT_STR( "\n" );
444 0 : for( int i = 0; i < space; i++ )
445 0 : PRINT_STR( " " );
446 0 : if( FD_UNLIKELY( ele->stake > 100 ) ) {
447 0 : }
448 0 : if( FD_UNLIKELY( total_stake == 0 ) ) {
449 0 : PRINT( "%s%lu (%lu)", prefix, ele->slot, ele->stake );
450 0 : } else {
451 0 : double pct = ( (double)ele->stake / (double)total_stake ) * 100;
452 0 : if( FD_UNLIKELY( pct < 0.99 )) {
453 0 : PRINT( "%s%lu (%.0lf%%, %lu)", prefix, ele->slot, pct, ele->stake );
454 0 : } else {
455 0 : PRINT( "%s%lu (%.0lf%%)", prefix, ele->slot, pct );
456 0 : }
457 0 : }
458 :
459 0 : fd_ghost_blk_t const * curr = blk_pool_ele_const( pool, ele->child );
460 0 : char new_prefix[1024]; /* FIXME size this correctly */
461 0 : while( curr ) {
462 0 : if( FD_UNLIKELY( blk_pool_ele_const( pool, curr->sibling ) ) ) {
463 0 : sprintf( new_prefix, "├── " ); /* branch indicating more siblings follow */
464 0 : print( ghost, curr, total_stake, space + 4, new_prefix, ostream_opt );
465 0 : } else {
466 0 : sprintf( new_prefix, "└── " ); /* end branch */
467 0 : print( ghost, curr, total_stake, space + 4, new_prefix, ostream_opt );
468 0 : }
469 0 : curr = blk_pool_ele_const( pool, curr->sibling );
470 0 : }
471 0 : }
472 :
473 : void
474 : fd_ghost_print( fd_ghost_t const * ghost,
475 : fd_ghost_blk_t const * root,
476 0 : fd_io_buffered_ostream_t * ostream_opt ) {
477 0 : if( FD_LIKELY( ostream_opt ) ) PRINT_STR( "\n\n[Ghost]\n" );
478 0 : else FD_LOG_NOTICE( ( "\n\n[Ghost]" ) );
479 0 : print( ghost, root, root->total_stake, 0, "", ostream_opt );
480 : PRINT_STR( "\n\n" );
481 0 : }
482 : #undef PRINT
|