Line data Source code
1 : #include "fd_ghost.h"
2 : #include "fd_ghost_private.h"
3 :
4 : ulong
5 168 : fd_ghost_align( void ) {
6 168 : return alignof(fd_ghost_t);
7 168 : }
8 :
9 : ulong
10 : fd_ghost_footprint( ulong blk_max,
11 30 : ulong vtr_max ) {
12 30 : return FD_LAYOUT_FINI(
13 30 : FD_LAYOUT_APPEND(
14 30 : FD_LAYOUT_APPEND(
15 30 : FD_LAYOUT_APPEND(
16 30 : FD_LAYOUT_APPEND(
17 30 : FD_LAYOUT_APPEND(
18 30 : FD_LAYOUT_INIT,
19 30 : alignof(fd_ghost_t), sizeof(fd_ghost_t) ),
20 30 : blk_pool_align(), blk_pool_footprint( blk_max ) ),
21 30 : blk_map_align(), blk_map_footprint ( blk_max ) ),
22 30 : vtr_pool_align(), vtr_pool_footprint( vtr_max ) ),
23 30 : vtr_map_align(), vtr_map_footprint ( vtr_max ) ),
24 30 : fd_ghost_align() );
25 30 : }
26 :
27 : void *
28 : fd_ghost_new( void * shmem,
29 : ulong blk_max,
30 : ulong vtr_max,
31 15 : ulong seed ) {
32 :
33 15 : if( FD_UNLIKELY( !shmem ) ) {
34 0 : FD_LOG_WARNING(( "NULL mem" ));
35 0 : return NULL;
36 0 : }
37 :
38 15 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_ghost_align() ) ) ) {
39 0 : FD_LOG_WARNING(( "misaligned mem" ));
40 0 : return NULL;
41 0 : }
42 :
43 15 : ulong footprint = fd_ghost_footprint( blk_max, vtr_max );
44 15 : if( FD_UNLIKELY( !footprint ) ) {
45 0 : FD_LOG_WARNING(( "bad blk_max (%lu)", blk_max ));
46 0 : return NULL;
47 0 : }
48 :
49 15 : fd_wksp_t * wksp = fd_wksp_containing( shmem );
50 15 : if( FD_UNLIKELY( !wksp ) ) {
51 0 : FD_LOG_WARNING(( "shmem must be part of a workspace" ));
52 0 : return NULL;
53 0 : }
54 :
55 15 : fd_memset( shmem, 0, footprint );
56 :
57 15 : FD_SCRATCH_ALLOC_INIT( l, shmem );
58 15 : fd_ghost_t * ghost = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_ghost_t), sizeof(fd_ghost_t) );
59 15 : void * blk_pool = FD_SCRATCH_ALLOC_APPEND( l, blk_pool_align(), blk_pool_footprint( blk_max ) );
60 15 : void * blk_map = FD_SCRATCH_ALLOC_APPEND( l, blk_map_align(), blk_map_footprint ( blk_max ) );
61 15 : void * vtr_pool = FD_SCRATCH_ALLOC_APPEND( l, vtr_pool_align(), vtr_pool_footprint( vtr_max ) );
62 15 : void * vtr_map = FD_SCRATCH_ALLOC_APPEND( l, vtr_map_align(), vtr_map_footprint ( vtr_max ) );
63 15 : FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_ghost_align() ) == (ulong)shmem + footprint );
64 :
65 15 : ghost->root = ULONG_MAX;
66 15 : ghost->blk_pool_gaddr = fd_wksp_gaddr_fast( wksp, blk_pool_join( blk_pool_new ( blk_pool, blk_max ) ) );
67 15 : ghost->blk_map_gaddr = fd_wksp_gaddr_fast( wksp, blk_map_join ( blk_map_new ( blk_map, blk_max, seed ) ) );
68 15 : ghost->vtr_pool_gaddr = fd_wksp_gaddr_fast( wksp, vtr_pool_join( vtr_pool_new ( vtr_pool, vtr_max ) ) );
69 15 : ghost->vtr_map_gaddr = fd_wksp_gaddr_fast( wksp, vtr_map_join ( vtr_map_new ( vtr_map, vtr_max, seed ) ) );
70 :
71 15 : return shmem;
72 15 : }
73 :
74 : fd_ghost_t *
75 15 : fd_ghost_join( void * shghost ) {
76 15 : fd_ghost_t * ghost = (fd_ghost_t *)shghost;
77 :
78 15 : if( FD_UNLIKELY( !ghost ) ) {
79 0 : FD_LOG_WARNING(( "NULL ghost" ));
80 0 : return NULL;
81 0 : }
82 :
83 15 : if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)ghost, fd_ghost_align() ) ) ) {
84 0 : FD_LOG_WARNING(( "misaligned ghost" ));
85 0 : return NULL;
86 0 : }
87 :
88 15 : return ghost;
89 15 : }
90 :
91 : void *
92 15 : fd_ghost_leave( fd_ghost_t const * ghost ) {
93 :
94 15 : if( FD_UNLIKELY( !ghost ) ) {
95 0 : FD_LOG_WARNING(( "NULL ghost" ));
96 0 : return NULL;
97 0 : }
98 :
99 15 : return (void *)ghost;
100 15 : }
101 :
102 : void *
103 15 : fd_ghost_delete( void * ghost ) {
104 :
105 15 : if( FD_UNLIKELY( !ghost ) ) {
106 0 : FD_LOG_WARNING(( "NULL ghost" ));
107 0 : return NULL;
108 0 : }
109 :
110 15 : if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)ghost, fd_ghost_align() ) ) ) {
111 0 : FD_LOG_WARNING(( "misaligned ghost" ));
112 0 : return NULL;
113 0 : }
114 :
115 15 : return ghost;
116 15 : }
117 :
118 : fd_ghost_blk_t *
119 51 : fd_ghost_root( fd_ghost_t * ghost ) {
120 51 : return blk_pool_ele( blk_pool( ghost ), ghost->root );
121 51 : }
122 :
123 : fd_ghost_blk_t *
124 0 : fd_ghost_parent( fd_ghost_t * ghost, fd_ghost_blk_t * blk ) {
125 0 : return blk_pool_ele( blk_pool( ghost ), blk->parent );
126 0 : }
127 :
128 : fd_ghost_blk_t *
129 : fd_ghost_query( fd_ghost_t * ghost,
130 117 : fd_hash_t const * block_id ) {
131 117 : return blk_map_ele_query( blk_map( ghost ), block_id, NULL, blk_pool( ghost ) );
132 117 : }
133 :
134 : fd_ghost_blk_t *
135 : fd_ghost_best( fd_ghost_t * ghost,
136 21 : fd_ghost_blk_t * root ) {
137 21 : blk_pool_t * pool = blk_pool( ghost );
138 21 : ulong null = blk_pool_idx_null( pool );
139 21 : fd_ghost_blk_t * best = root;
140 84 : while( FD_LIKELY( best->child != null ) ) {
141 63 : int valid = 0; /* at least one child is valid */
142 63 : fd_ghost_blk_t * child = blk_pool_ele( pool, best->child );
143 141 : while( FD_LIKELY( child ) ) { /* greedily pick the heaviest valid child */
144 78 : if( FD_LIKELY( child->valid ) ) {
145 78 : if( FD_LIKELY( !valid ) ) { /* this is the first valid child, so progress the head */
146 63 : best = child;
147 63 : valid = 1;
148 63 : }
149 :
150 : /* When stake is equal, tie-break by lower slot. Two valid
151 : children with equal stake and equal slot (ie. equivocating
152 : blocks) cannot occur: equivocating blocks are marked eqvoc=1
153 : and valid=0, so at most one of them would be valid unless
154 : multiple blocks for that slot are duplicate confirmed, which
155 : is a consensus invariant violation. */
156 :
157 78 : best = fd_ptr_if(
158 78 : fd_int_if(
159 78 : child->stake == best->stake, /* if the weights are equal */
160 78 : child->slot < best->slot, /* then tie-break by lower slot number */
161 78 : child->stake > best->stake ), /* else return heavier */
162 78 : child, best );
163 78 : }
164 78 : child = blk_pool_ele( pool, child->sibling );
165 78 : }
166 63 : if( FD_UNLIKELY( !valid ) ) break; /* no children are valid, so short-circuit traversal */
167 63 : }
168 21 : return best;
169 21 : }
170 :
171 : fd_ghost_blk_t *
172 : fd_ghost_deepest( fd_ghost_t * ghost,
173 15 : fd_ghost_blk_t * root ) {
174 15 : blk_pool_t * pool = blk_pool( ghost );
175 15 : ulong null = blk_pool_idx_null( pool );
176 15 : fd_ghost_blk_t * head = blk_map_ele_remove( blk_map( ghost ), &root->id, NULL, pool ); /* remove ele from map to reuse `.next` */
177 15 : fd_ghost_blk_t * tail = head;
178 15 : fd_ghost_blk_t * prev = NULL;
179 :
180 : /* Below is a level-order traversal (BFS), returning the last leaf
181 : which is guaranteed to return an element of the max depth.
182 :
183 : It temporarily removes elements of the map when pushing onto the
184 : BFS queue to reuse the .next pointer and then inserts back into
185 : the map on queue pop. */
186 :
187 15 : head->next = null;
188 93 : while( FD_LIKELY( head ) ) {
189 78 : fd_ghost_blk_t const * child = blk_pool_ele( pool, head->child );
190 141 : while( FD_LIKELY( child ) ) {
191 63 : FD_TEST( blk_map_ele_remove( blk_map( ghost ), &child->id, NULL, pool ) ); /* in the tree so must be in the map */
192 63 : tail->next = blk_pool_idx( pool, child );
193 63 : tail = blk_pool_ele( pool, tail->next );
194 63 : tail->next = blk_pool_idx_null( pool );
195 63 : child = blk_pool_ele( pool, child->sibling ); /* next sibling */
196 63 : }
197 78 : fd_ghost_blk_t * next = blk_pool_ele( pool, head->next ); /* pop prune queue head */
198 78 : blk_map_ele_insert( blk_map( ghost ), head, pool ); /* re-insert head into map */
199 78 : prev = head;
200 78 : head = next;
201 78 : }
202 15 : return prev;
203 15 : }
204 :
205 0 : #define PREDICATE_ANCESTOR( predicate ) do { \
206 0 : fd_ghost_blk_t * ancestor = descendant; \
207 0 : while( FD_LIKELY( ancestor ) ) { \
208 0 : if( FD_LIKELY( predicate ) ) return ancestor; \
209 0 : ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent ); \
210 0 : } \
211 0 : return NULL; \
212 0 : } while(0)
213 :
214 : fd_ghost_blk_t *
215 : fd_ghost_ancestor( fd_ghost_t * ghost,
216 : fd_ghost_blk_t * descendant,
217 0 : fd_hash_t const * ancestor_id ) {
218 0 : PREDICATE_ANCESTOR( 0==memcmp( &ancestor->id, ancestor_id, sizeof(fd_hash_t) ) );
219 0 : }
220 :
221 : fd_ghost_blk_t *
222 : fd_ghost_slot_ancestor( fd_ghost_t * ghost,
223 : fd_ghost_blk_t * descendant,
224 0 : ulong slot ) {
225 0 : PREDICATE_ANCESTOR( ancestor->slot == slot );
226 0 : }
227 :
228 : fd_ghost_blk_t *
229 : fd_ghost_invalid_ancestor( fd_ghost_t * ghost,
230 0 : fd_ghost_blk_t * descendant ) {
231 0 : PREDICATE_ANCESTOR( !ancestor->valid );
232 0 : }
233 :
234 : fd_ghost_blk_t *
235 : fd_ghost_insert( fd_ghost_t * ghost,
236 : fd_hash_t const * block_id,
237 : fd_hash_t const * parent_block_id,
238 105 : ulong slot ) {
239 :
240 105 : fd_ghost_blk_t * pool = blk_pool( ghost );
241 105 : ulong null = blk_pool_idx_null( pool );
242 105 : fd_ghost_blk_t * blk = blk_map_ele_query( blk_map( ghost ), block_id, NULL, pool );
243 :
244 105 : FD_TEST( !blk ); /* duplicate insert */
245 105 : FD_TEST( blk_pool_free( pool ) ); /* ghost full */
246 :
247 105 : blk = blk_pool_ele_acquire( pool );
248 105 : blk->id = *block_id;
249 105 : blk->slot = slot;
250 105 : blk->next = null;
251 105 : blk->parent = null;
252 105 : blk->child = null;
253 105 : blk->sibling = null;
254 105 : blk->stake = 0;
255 105 : blk->total_stake = 0;
256 105 : blk->eqvoc = 0;
257 105 : blk->conf = 0;
258 105 : blk->valid = 1;
259 105 : blk_map_ele_insert( blk_map( ghost ), blk, pool );
260 :
261 105 : if( FD_UNLIKELY( !parent_block_id ) ) {
262 15 : ghost->root = blk_pool_idx( pool, blk );
263 15 : return blk;
264 15 : }
265 :
266 90 : fd_ghost_blk_t * parent = blk_map_ele_query( blk_map( ghost ), parent_block_id, NULL, pool );
267 90 : FD_TEST( parent ); /* parent must exist if this is not the first insertion */
268 90 : blk->parent = blk_pool_idx( pool, parent );
269 90 : if( FD_LIKELY( parent->child == null ) ) {
270 75 : parent->child = blk_pool_idx( pool, blk ); /* left-child */
271 75 : } else {
272 15 : fd_ghost_blk_t * sibling = blk_pool_ele( pool, parent->child );
273 15 : while( sibling->sibling != null ) sibling = blk_pool_ele( pool, sibling->sibling );
274 15 : sibling->sibling = blk_pool_idx( pool, blk ); /* right-sibling */
275 15 : }
276 :
277 90 : return blk;
278 90 : }
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 18 : ulong slot ) {
286 :
287 18 : fd_ghost_blk_t const * root = fd_ghost_root( ghost );
288 18 : fd_ghost_vtr_t * vtr = vtr_map_ele_query( vtr_map( ghost ), vote_acc, NULL, vtr_pool( ghost ) );
289 :
290 18 : if( FD_UNLIKELY( slot == ULONG_MAX ) ) return; /* hasn't voted */
291 18 : if( FD_UNLIKELY( slot < root->slot ) ) return; /* vote older than root */
292 :
293 18 : 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 9 : vtr = vtr_pool_ele_acquire( vtr_pool( ghost ) );
299 9 : vtr->addr = *vote_acc;
300 9 : vtr_map_ele_insert( vtr_map( ghost ), vtr, vtr_pool( ghost ) );
301 :
302 9 : } 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 9 : 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 (after): %lu. subtracted: %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 15 : fd_ghost_blk_t * ancestor = blk;
337 69 : while( FD_LIKELY( ancestor ) ) {
338 54 : int cf = __builtin_uaddl_overflow( ancestor->stake, stake, &ancestor->stake );
339 54 : if( FD_UNLIKELY( cf ) ) {
340 0 : FD_BASE58_ENCODE_32_BYTES( ancestor->id.key, ancestor_id_b58 );
341 0 : FD_LOG_CRIT(( "[%s] overflow (after): %lu. added: %lu. (slot %lu, block_id: %s)", __func__, ancestor->stake, stake, ancestor->slot, ancestor_id_b58 ));
342 0 : }
343 54 : ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent );
344 54 : }
345 15 : vtr->prev_block_id = blk->id;
346 15 : vtr->prev_stake = stake;
347 15 : vtr->prev_slot = slot;
348 15 : }
349 :
350 : void
351 : fd_ghost_publish( fd_ghost_t * ghost,
352 6 : fd_ghost_blk_t * newr ) {
353 :
354 6 : fd_ghost_blk_t * pool = blk_pool( ghost );
355 6 : ulong null = blk_pool_idx_null( pool );
356 6 : fd_ghost_blk_t * oldr = fd_ghost_root( ghost );
357 :
358 6 : if( FD_UNLIKELY( oldr==newr ) ) return;
359 :
360 : /* First, remove the previous root, and add it to the prune list. In
361 : this context, head is the list head (not to be confused with the
362 : ghost head.) */
363 :
364 6 : fd_ghost_blk_t * head = blk_map_ele_remove( blk_map( ghost ), &oldr->id, NULL, pool ); /* remove ele from map to reuse `.next` */
365 6 : fd_ghost_blk_t * tail = head;
366 :
367 : /* Second, BFS down the tree, pruning all of root's ancestors and also
368 : any descendants of those ancestors.
369 :
370 : oldr
371 : |
372 : X
373 : / \
374 : newr Y
375 : |
376 : Z
377 :
378 : ...
379 :
380 : newr
381 :
382 : BFS starts with oldr. Its child is X. X != newr, so X gets
383 : enqueued. oldr is released. Next head = X. X's children are newr
384 : and Y. newr is skipped. Y gets enqueued. X is released. Next
385 : head = Y. Y's child Z gets enqueued. Y released. Z released.
386 : Queue is empty, loop ends.
387 :
388 : oldr
389 : / \
390 : A newr
391 : / \
392 : B C
393 :
394 : ...
395 :
396 : newr
397 : / \
398 : B C
399 :
400 :
401 : The BFS starts with oldr. Its children are A and newr. A gets
402 : enqueued for pruning. newr is skipped (line 374). Then oldr is
403 : released. Next, head = A. A has no children. A is released.
404 : Queue is empty, loop ends. */
405 :
406 6 : head->next = null;
407 33 : while( FD_LIKELY( head ) ) {
408 27 : fd_ghost_blk_t * child = blk_pool_ele( blk_pool( ghost ), head->child );
409 54 : while( FD_LIKELY( child ) ) { /* iterate over children */
410 27 : if( FD_LIKELY( child != newr ) ) { /* stop at new root */
411 21 : tail->next = blk_map_idx_remove( blk_map( ghost ), &child->id, null, pool ); /* remove ele from map to reuse `.next` */
412 21 : FD_BASE58_ENCODE_32_BYTES( child->id.key, block_id_cstr );
413 21 : tail = blk_pool_ele( blk_pool( ghost ), tail->next ); /* push onto prune queue (so descendants can be pruned) */
414 21 : tail->next = blk_pool_idx_null( blk_pool( ghost ) );
415 21 : }
416 27 : child = blk_pool_ele( blk_pool( ghost ), child->sibling ); /* next sibling */
417 27 : }
418 27 : fd_ghost_blk_t * next = blk_pool_ele( blk_pool( ghost ), head->next ); /* pop prune queue head */
419 27 : blk_pool_ele_release( blk_pool( ghost ), head ); /* free prune queue head */
420 27 : head = next; /* move prune queue head forward */
421 27 : }
422 6 : newr->parent = null; /* unlink old root */
423 6 : ghost->root = blk_pool_idx( blk_pool( ghost ), newr ); /* replace with new root */
424 6 : }
425 :
426 : int
427 18 : fd_ghost_verify( fd_ghost_t * ghost ) {
428 18 : if( FD_UNLIKELY( !ghost ) ) {
429 0 : FD_LOG_WARNING(( "NULL ghost" ));
430 0 : return -1;
431 0 : }
432 :
433 18 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)ghost, fd_ghost_align() ) ) ) {
434 0 : FD_LOG_WARNING(( "misaligned ghost" ));
435 0 : return -1;
436 0 : }
437 :
438 18 : fd_wksp_t * wksp = fd_wksp_containing( ghost );
439 18 : if( FD_UNLIKELY( !wksp ) ) {
440 0 : FD_LOG_WARNING(( "ghost must be part of a workspace" ));
441 0 : return -1;
442 0 : }
443 :
444 18 : fd_ghost_blk_t const * pool = blk_pool( ghost );
445 :
446 : /* Check every ele that exists in pool exists in map. */
447 :
448 18 : if( blk_map_verify( blk_map( ghost ), blk_pool_max( pool ), pool ) ) return -1;
449 :
450 18 : return 0;
451 18 : }
452 :
453 : #include <stdio.h>
454 : #include <string.h>
455 :
456 : #define BUF_MAX 4096
457 : #define DEPTH_MAX 512
458 :
459 : static void
460 : to_cstr( fd_ghost_t const * ghost,
461 : fd_ghost_blk_t const * ele,
462 : ulong total_stake,
463 : int space,
464 : const char * prefix,
465 : char * cstr,
466 : ulong len,
467 : ulong * off,
468 0 : ulong depth ) {
469 0 : if( FD_UNLIKELY( depth>DEPTH_MAX ) ) return;
470 :
471 0 : fd_ghost_blk_t const * pool = blk_pool_const( ghost );
472 0 : int n;
473 :
474 0 : if( FD_UNLIKELY( ele == NULL ) ) return;
475 :
476 0 : if( FD_LIKELY( space > 0 ) && *off < len ) {
477 0 : cstr[(*off)++] = '\n';
478 0 : }
479 :
480 0 : for( int i = 0; i < space && *off < len; i++ ) {
481 0 : cstr[(*off)++] = ' ';
482 0 : }
483 :
484 0 : if( FD_UNLIKELY( ele->stake > 100 ) ) {
485 0 : }
486 :
487 0 : if( FD_UNLIKELY( total_stake == 0 ) ) {
488 0 : if( *off < len ) {
489 0 : n = snprintf( cstr + *off, len - *off, "%s%lu (%lu)", prefix, ele->slot, ele->stake );
490 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
491 0 : *off += (ulong)n;
492 0 : }
493 0 : } else {
494 0 : double pct = ( (double)ele->stake / (double)total_stake ) * 100;
495 0 : if( FD_UNLIKELY( pct < 0.99 ) ) {
496 0 : if( *off < len ) {
497 0 : n = snprintf( cstr + *off, len - *off, "%s%lu (%.0lf%%, %lu)", prefix, ele->slot, pct, ele->stake );
498 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
499 0 : *off += (ulong)n;
500 0 : }
501 0 : } else {
502 0 : if( *off < len ) {
503 0 : n = snprintf( cstr + *off, len - *off, "%s%lu (%.0lf%%)", prefix, ele->slot, pct );
504 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
505 0 : *off += (ulong)n;
506 0 : }
507 0 : }
508 0 : }
509 :
510 0 : fd_ghost_blk_t const * curr = blk_pool_ele_const( pool, ele->child );
511 :
512 0 : while( curr ) {
513 0 : char const * next_prefix = blk_pool_ele_const( pool, curr->sibling ) ? "├── " : "└── ";
514 0 : to_cstr( ghost, curr, total_stake, space + 4, next_prefix, cstr, len, off, depth + 1 ); /* TODO remove recursion */
515 0 : curr = blk_pool_ele_const( pool, curr->sibling );
516 0 : }
517 0 : }
518 :
519 : char *
520 : fd_ghost_to_cstr( fd_ghost_t const * ghost,
521 : fd_ghost_blk_t const * root,
522 : char * cstr,
523 : ulong cstr_max,
524 0 : ulong * cstr_len ) {
525 :
526 0 : ulong off = 0;
527 :
528 0 : int n = snprintf( cstr + off, cstr_max - off, "[Ghost]\n\n" );
529 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
530 0 : off += (ulong)n;
531 :
532 0 : to_cstr( ghost, root, root->total_stake, 0, "", cstr, cstr_max, &off, 0 );
533 :
534 0 : if( off < cstr_max ) {
535 0 : n = snprintf( cstr + off, cstr_max - off, "\n\n" );
536 0 : if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
537 0 : off += (ulong)n;
538 0 : }
539 :
540 0 : cstr[fd_ulong_min( off++, cstr_max - 1 )] = '\0';
541 0 : *cstr_len = fd_ulong_min( off, cstr_max );
542 0 : return cstr;
543 0 : }
|