LCOV - code coverage report
Current view: top level - choreo/ghost - fd_ghost.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 219 327 67.0 %
Date: 2026-01-23 05:02:40 Functions: 14 20 70.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         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

Generated by: LCOV version 1.14