LCOV - code coverage report
Current view: top level - choreo/ghost - fd_ghost.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 316 0.0 %
Date: 2025-12-27 05:04:59 Functions: 0 19 0.0 %

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

Generated by: LCOV version 1.14