LCOV - code coverage report
Current view: top level - choreo/ghost - fd_ghost.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 327 0.0 %
Date: 2026-03-19 18:19:27 Functions: 0 20 0.0 %

          Line data    Source code
       1             : #include "fd_ghost.h"
       2             : #include "fd_ghost_private.h"
       3             : 
       4             : ulong
       5           0 : fd_ghost_align( void ) {
       6           0 :   return alignof(fd_ghost_t);
       7           0 : }
       8             : 
       9             : ulong
      10             : fd_ghost_footprint( ulong blk_max,
      11           0 :                     ulong vtr_max ) {
      12           0 :   return FD_LAYOUT_FINI(
      13           0 :     FD_LAYOUT_APPEND(
      14           0 :     FD_LAYOUT_APPEND(
      15           0 :     FD_LAYOUT_APPEND(
      16           0 :     FD_LAYOUT_APPEND(
      17           0 :     FD_LAYOUT_APPEND(
      18           0 :     FD_LAYOUT_INIT,
      19           0 :       alignof(fd_ghost_t), sizeof(fd_ghost_t)            ),
      20           0 :       blk_pool_align(),    blk_pool_footprint( blk_max ) ),
      21           0 :       blk_map_align(),     blk_map_footprint ( blk_max ) ),
      22           0 :       vtr_pool_align(),    vtr_pool_footprint( vtr_max ) ),
      23           0 :       vtr_map_align(),     vtr_map_footprint ( vtr_max ) ),
      24           0 :     fd_ghost_align() );
      25           0 : }
      26             : 
      27             : void *
      28             : fd_ghost_new( void * shmem,
      29             :               ulong  blk_max,
      30             :               ulong  vtr_max,
      31           0 :               ulong  seed ) {
      32             : 
      33           0 :   if( FD_UNLIKELY( !shmem ) ) {
      34           0 :     FD_LOG_WARNING(( "NULL mem" ));
      35           0 :     return NULL;
      36           0 :   }
      37             : 
      38           0 :   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           0 :   ulong footprint = fd_ghost_footprint( blk_max, vtr_max );
      44           0 :   if( FD_UNLIKELY( !footprint ) ) {
      45           0 :     FD_LOG_WARNING(( "bad blk_max (%lu)", blk_max ));
      46           0 :     return NULL;
      47           0 :   }
      48             : 
      49           0 :   fd_wksp_t * wksp = fd_wksp_containing( shmem );
      50           0 :   if( FD_UNLIKELY( !wksp ) ) {
      51           0 :     FD_LOG_WARNING(( "shmem must be part of a workspace" ));
      52           0 :     return NULL;
      53           0 :   }
      54             : 
      55           0 :   fd_memset( shmem, 0, footprint );
      56             : 
      57           0 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
      58           0 :   fd_ghost_t * ghost    = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_ghost_t), sizeof(fd_ghost_t)            );
      59           0 :   void *       blk_pool = FD_SCRATCH_ALLOC_APPEND( l, blk_pool_align(),    blk_pool_footprint( blk_max ) );
      60           0 :   void *       blk_map  = FD_SCRATCH_ALLOC_APPEND( l, blk_map_align(),     blk_map_footprint ( blk_max ) );
      61           0 :   void *       vtr_pool = FD_SCRATCH_ALLOC_APPEND( l, vtr_pool_align(),    vtr_pool_footprint( vtr_max ) );
      62           0 :   void *       vtr_map  = FD_SCRATCH_ALLOC_APPEND( l, vtr_map_align(),     vtr_map_footprint ( vtr_max ) );
      63           0 :   FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_ghost_align() ) == (ulong)shmem + footprint );
      64             : 
      65           0 :   ghost->root           = ULONG_MAX;
      66           0 :   ghost->blk_pool_gaddr = fd_wksp_gaddr_fast( wksp, blk_pool_join( blk_pool_new ( blk_pool, blk_max       ) ) );
      67           0 :   ghost->blk_map_gaddr  = fd_wksp_gaddr_fast( wksp, blk_map_join ( blk_map_new  ( blk_map,  blk_max, seed ) ) );
      68           0 :   ghost->vtr_pool_gaddr = fd_wksp_gaddr_fast( wksp, vtr_pool_join( vtr_pool_new ( vtr_pool, vtr_max       ) ) );
      69           0 :   ghost->vtr_map_gaddr  = fd_wksp_gaddr_fast( wksp, vtr_map_join ( vtr_map_new  ( vtr_map,  vtr_max, seed ) ) );
      70             : 
      71           0 :   return shmem;
      72           0 : }
      73             : 
      74             : fd_ghost_t *
      75           0 : fd_ghost_join( void * shghost ) {
      76           0 :   fd_ghost_t * ghost = (fd_ghost_t *)shghost;
      77             : 
      78           0 :   if( FD_UNLIKELY( !ghost ) ) {
      79           0 :     FD_LOG_WARNING(( "NULL ghost" ));
      80           0 :     return NULL;
      81           0 :   }
      82             : 
      83           0 :   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           0 :   return ghost;
      89           0 : }
      90             : 
      91             : void *
      92           0 : fd_ghost_leave( fd_ghost_t const * ghost ) {
      93             : 
      94           0 :   if( FD_UNLIKELY( !ghost ) ) {
      95           0 :     FD_LOG_WARNING(( "NULL ghost" ));
      96           0 :     return NULL;
      97           0 :   }
      98             : 
      99           0 :   return (void *)ghost;
     100           0 : }
     101             : 
     102             : void *
     103           0 : fd_ghost_delete( void * ghost ) {
     104             : 
     105           0 :   if( FD_UNLIKELY( !ghost ) ) {
     106           0 :     FD_LOG_WARNING(( "NULL ghost" ));
     107           0 :     return NULL;
     108           0 :   }
     109             : 
     110           0 :   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           0 :   return ghost;
     116           0 : }
     117             : 
     118             : fd_ghost_blk_t *
     119           0 : fd_ghost_root( fd_ghost_t * ghost ) {
     120           0 :   return blk_pool_ele( blk_pool( ghost ), ghost->root );
     121           0 : }
     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           0 :                 fd_hash_t  const * block_id ) {
     131           0 :   return blk_map_ele_query( blk_map( ghost ), block_id, NULL, blk_pool( ghost ) );
     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 :   blk_pool_t *     pool = blk_pool( ghost );
     138           0 :   ulong            null = blk_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 = blk_pool_ele( 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             : 
     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           0 :         best = fd_ptr_if(
     158           0 :           fd_int_if(
     159           0 :             child->stake == best->stake,   /* if the weights are equal */
     160           0 :             child->slot  <  best->slot,    /* then tie-break by lower slot number */
     161           0 :             child->stake >  best->stake ), /* else return heavier */
     162           0 :           child, best );
     163           0 :       }
     164           0 :       child = blk_pool_ele( pool, child->sibling );
     165           0 :     }
     166           0 :     if( FD_UNLIKELY( !valid ) ) break; /* no children are valid, so short-circuit traversal */
     167           0 :   }
     168           0 :   return best;
     169           0 : }
     170             : 
     171             : fd_ghost_blk_t *
     172             : fd_ghost_deepest( fd_ghost_t     * ghost,
     173           0 :                   fd_ghost_blk_t * root ) {
     174           0 :   blk_pool_t *     pool = blk_pool( ghost );
     175           0 :   ulong            null = blk_pool_idx_null( pool );
     176           0 :   fd_ghost_blk_t * head = blk_map_ele_remove( blk_map( ghost ), &root->id, NULL, pool ); /* remove ele from map to reuse `.next` */
     177           0 :   fd_ghost_blk_t * tail = head;
     178           0 :   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           0 :   head->next = null;
     188           0 :   while( FD_LIKELY( head ) ) {
     189           0 :     fd_ghost_blk_t const * child = blk_pool_ele( pool, head->child );
     190           0 :     while( FD_LIKELY( child ) ) {
     191           0 :       FD_TEST( blk_map_ele_remove( blk_map( ghost ), &child->id, NULL, pool ) ); /* in the tree so must be in the map */
     192           0 :       tail->next = blk_pool_idx( pool, child );
     193           0 :       tail       = blk_pool_ele( pool, tail->next );
     194           0 :       tail->next = blk_pool_idx_null( pool );
     195           0 :       child      = blk_pool_ele( pool, child->sibling ); /* next sibling */
     196           0 :     }
     197           0 :     fd_ghost_blk_t * next = blk_pool_ele( pool, head->next ); /* pop prune queue head */
     198           0 :     blk_map_ele_insert( blk_map( ghost ), head, pool );       /* re-insert head into map */
     199           0 :     prev = head;
     200           0 :     head = next;
     201           0 :   }
     202           0 :   return prev;
     203           0 : }
     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           0 :                  ulong             slot ) {
     239             : 
     240           0 :   fd_ghost_blk_t * pool = blk_pool( ghost );
     241           0 :   ulong            null = blk_pool_idx_null( pool );
     242           0 :   fd_ghost_blk_t * blk  = blk_map_ele_query( blk_map( ghost ), block_id, NULL, pool );
     243             : 
     244           0 :   FD_TEST( !blk ); /* duplicate insert */
     245           0 :   FD_TEST( blk_pool_free( pool ) ); /* ghost full */
     246             : 
     247           0 :   blk              = blk_pool_ele_acquire( pool );
     248           0 :   blk->id          = *block_id;
     249           0 :   blk->slot        = slot;
     250           0 :   blk->next        = null;
     251           0 :   blk->parent      = null;
     252           0 :   blk->child       = null;
     253           0 :   blk->sibling     = null;
     254           0 :   blk->stake       = 0;
     255           0 :   blk->total_stake = 0;
     256           0 :   blk->eqvoc       = 0;
     257           0 :   blk->conf        = 0;
     258           0 :   blk->valid       = 1;
     259           0 :   blk_map_ele_insert( blk_map( ghost ), blk, pool );
     260             : 
     261           0 :   if( FD_UNLIKELY( !parent_block_id ) ) {
     262           0 :     ghost->root = blk_pool_idx( pool, blk );
     263           0 :     return blk;
     264           0 :   }
     265             : 
     266           0 :   fd_ghost_blk_t * parent = blk_map_ele_query( blk_map( ghost ), parent_block_id, NULL, pool );
     267           0 :   FD_TEST( parent ); /* parent must exist if this is not the first insertion */
     268           0 :   blk->parent  = blk_pool_idx( pool, parent );
     269           0 :   if( FD_LIKELY( parent->child == null ) ) {
     270           0 :     parent->child = blk_pool_idx( pool, blk );    /* left-child */
     271           0 :   } else {
     272           0 :     fd_ghost_blk_t * sibling = blk_pool_ele( pool, parent->child );
     273           0 :     while( sibling->sibling != null ) sibling = blk_pool_ele( pool, sibling->sibling );
     274           0 :     sibling->sibling = blk_pool_idx( pool, blk ); /* right-sibling */
     275           0 :   }
     276             : 
     277           0 :   return blk;
     278           0 : }
     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           0 :                      ulong               slot ) {
     286             : 
     287           0 :   fd_ghost_blk_t const * root = fd_ghost_root( ghost );
     288           0 :   fd_ghost_vtr_t *       vtr  = vtr_map_ele_query( vtr_map( ghost ), vote_acc, NULL, vtr_pool( ghost ) );
     289             : 
     290           0 :   if( FD_UNLIKELY( slot == ULONG_MAX  ) ) return; /* hasn't voted */
     291           0 :   if( FD_UNLIKELY( slot <  root->slot ) ) return; /* vote older than root */
     292             : 
     293           0 :   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           0 :     vtr       = vtr_pool_ele_acquire( vtr_pool( ghost ) );
     299           0 :     vtr->addr = *vote_acc;
     300           0 :     vtr_map_ele_insert( vtr_map( ghost ), vtr, vtr_pool( ghost ) );
     301             : 
     302           0 :   } 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           0 :     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           0 :     fd_ghost_blk_t * ancestor = blk_map_ele_query( blk_map( ghost ), &vtr->prev_block_id, NULL, blk_pool( ghost ) );
     320           0 :     while( FD_LIKELY( ancestor ) ) {
     321           0 :       int cf = __builtin_usubl_overflow( ancestor->stake, vtr->prev_stake, &ancestor->stake );
     322           0 :       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           0 :       ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent );
     327           0 :     }
     328           0 :   }
     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           0 :   fd_ghost_blk_t * ancestor = blk;
     337           0 :   while( FD_LIKELY( ancestor ) ) {
     338           0 :     int cf = __builtin_uaddl_overflow( ancestor->stake, stake, &ancestor->stake );
     339           0 :     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           0 :     ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent );
     344           0 :   }
     345           0 :   vtr->prev_block_id = blk->id;
     346           0 :   vtr->prev_stake    = stake;
     347           0 :   vtr->prev_slot     = slot;
     348           0 : }
     349             : 
     350             : void
     351             : fd_ghost_publish( fd_ghost_t     * ghost,
     352           0 :                   fd_ghost_blk_t * newr ) {
     353             : 
     354           0 :   fd_ghost_blk_t * pool = blk_pool( ghost );
     355           0 :   ulong            null = blk_pool_idx_null( pool );
     356           0 :   fd_ghost_blk_t * oldr = fd_ghost_root( ghost );
     357             : 
     358           0 :   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           0 :   fd_ghost_blk_t * head = blk_map_ele_remove( blk_map( ghost ), &oldr->id, NULL, pool ); /* remove ele from map to reuse `.next` */
     365           0 :   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           0 :   head->next = null;
     407           0 :   while( FD_LIKELY( head ) ) {
     408           0 :     fd_ghost_blk_t * child = blk_pool_ele( blk_pool( ghost ), head->child );
     409           0 :     while( FD_LIKELY( child ) ) {                                                    /* iterate over children */
     410           0 :       if( FD_LIKELY( child != newr ) ) {                                             /* stop at new root */
     411           0 :         tail->next = blk_map_idx_remove( blk_map( ghost ), &child->id, null, pool ); /* remove ele from map to reuse `.next` */
     412           0 :         FD_BASE58_ENCODE_32_BYTES( child->id.key, block_id_cstr );
     413           0 :         tail       = blk_pool_ele( blk_pool( ghost ), tail->next );                  /* push onto prune queue (so descendants can be pruned) */
     414           0 :         tail->next = blk_pool_idx_null( blk_pool( ghost ) );
     415           0 :       }
     416           0 :       child = blk_pool_ele( blk_pool( ghost ), child->sibling ); /* next sibling */
     417           0 :     }
     418           0 :     fd_ghost_blk_t * next = blk_pool_ele( blk_pool( ghost ), head->next ); /* pop prune queue head */
     419           0 :     blk_pool_ele_release( blk_pool( ghost ), head );                       /* free prune queue head */
     420           0 :     head = next;                                                           /* move prune queue head forward */
     421           0 :   }
     422           0 :   newr->parent = null;                                    /* unlink old root */
     423           0 :   ghost->root  = blk_pool_idx( blk_pool( ghost ), newr ); /* replace with new root */
     424           0 : }
     425             : 
     426             : int
     427           0 : fd_ghost_verify( fd_ghost_t * ghost ) {
     428           0 :   if( FD_UNLIKELY( !ghost ) ) {
     429           0 :     FD_LOG_WARNING(( "NULL ghost" ));
     430           0 :     return -1;
     431           0 :   }
     432             : 
     433           0 :   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           0 :   fd_wksp_t * wksp = fd_wksp_containing( ghost );
     439           0 :   if( FD_UNLIKELY( !wksp ) ) {
     440           0 :     FD_LOG_WARNING(( "ghost must be part of a workspace" ));
     441           0 :     return -1;
     442           0 :   }
     443             : 
     444           0 :   fd_ghost_blk_t const * pool = blk_pool( ghost );
     445             : 
     446             :   /* Check every ele that exists in pool exists in map. */
     447             : 
     448           0 :   if( blk_map_verify( blk_map( ghost ), blk_pool_max( pool ), pool ) ) return -1;
     449             : 
     450           0 :   return 0;
     451           0 : }
     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 : }

Generated by: LCOV version 1.14