LCOV - code coverage report
Current view: top level - choreo/ghost - fd_ghost.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 236 358 65.9 %
Date: 2026-04-04 05:47:49 Functions: 21 28 75.0 %

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

Generated by: LCOV version 1.14