LCOV - code coverage report
Current view: top level - choreo/ghost - fd_ghost.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 331 453 73.1 %
Date: 2026-06-08 09:27:03 Functions: 37 50 74.0 %

          Line data    Source code
       1             : #include "fd_ghost.h"
       2             : 
       3             : #define POOL_NAME blk_pool
       4         120 : #define POOL_T    fd_ghost_blk_t
       5             : #include "../../util/tmpl/fd_pool.c"
       6             : 
       7             : #define MAP_NAME               blk_map
       8             : #define MAP_ELE_T              fd_ghost_blk_t
       9        1083 : #define MAP_KEY                id
      10             : #define MAP_KEY_T              fd_hash_t
      11        2136 : #define MAP_KEY_EQ(k0,k1)      (!memcmp((k0),(k1), sizeof(fd_hash_t)))
      12        3231 : #define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t)))
      13        3015 : #define MAP_NEXT               next
      14             : #include "../../util/tmpl/fd_map_chain.c"
      15             : 
      16             : #define POOL_NAME vtr_pool
      17         120 : #define POOL_T    fd_ghost_vtr_t
      18             : #include "../../util/tmpl/fd_pool.c"
      19             : 
      20             : #define MAP_NAME                           vtr_map
      21           6 : #define MAP_ELE_T                          fd_ghost_vtr_t
      22          30 : #define MAP_KEY                            addr
      23             : #define MAP_KEY_T                          fd_pubkey_t
      24          12 : #define MAP_KEY_EQ(k0,k1)                  (!memcmp((k0),(k1), sizeof(fd_pubkey_t)))
      25          72 : #define MAP_KEY_HASH(key,seed)             (fd_hash((seed),(key),sizeof(fd_pubkey_t)))
      26          24 : #define MAP_PREV                           map.prev
      27          30 : #define MAP_NEXT                           map.next
      28             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
      29             : #include "../../util/tmpl/fd_map_chain.c"
      30             : 
      31             : #define DLIST_NAME  vtr_dlist
      32             : #define DLIST_ELE_T fd_ghost_vtr_t
      33          36 : #define DLIST_PREV  dlist.prev
      34          42 : #define DLIST_NEXT  dlist.next
      35             : #include "../../util/tmpl/fd_dlist.c"
      36             : 
      37             : /* fd_ghost_t is the top-level structure that holds the root of the
      38             :    tree, as well as the memory pools and map structures for tracking
      39             :    ghost eles and votes.
      40             : 
      41             :    These structures are bump-allocated and laid out contiguously in
      42             :    memory from the fd_ghost_t * pointer which points to the beginning of
      43             :    the memory region.
      44             : 
      45             :    --------------------------- <- fd_ghost_t *
      46             :    | fd_ghost_t              |
      47             :    ---------------------------
      48             :    | blk_pool                |
      49             :    ---------------------------
      50             :    | blk_map                 |
      51             :    ---------------------------
      52             :    | vtr_pool                |
      53             :    ---------------------------
      54             :    | vtr_map                 |
      55             :    ---------------------------
      56             :    | vtr_dlist for blk[0]    |
      57             :    | vtr_dlist for blk[1]    |
      58             :    | ...                     |
      59             :    | vtr_dlist for blk[N-1]  |
      60             :    --------------------------- */
      61             : 
      62             : struct __attribute__((aligned(128UL))) fd_ghost {
      63             :   ulong root;           /* pool idx of the root tree element */
      64             :   ulong wksp_gaddr;     /* wksp gaddr of fd_ghost in the backing wksp */
      65             :   ulong blk_pool_gaddr; /* memory offset of the blk_pool */
      66             :   ulong blk_map_gaddr;  /* memory offset of the blk_map */
      67             :   ulong vtr_pool_gaddr; /* memory offset of the vtr_pool */
      68             :   ulong vtr_map_gaddr;  /* memory offset of the vtr_map */
      69             :   ulong width;          /* incrementally updated width of the fork tree */
      70             : };
      71             : 
      72             : typedef fd_ghost_blk_t blk_pool_t;
      73             : typedef fd_ghost_vtr_t vtr_pool_t;
      74             : 
      75             : /* wksp returns the local join to the wksp backing the
      76             :    ghost.  The lifetime of the returned pointer is at least as
      77             :    long as the lifetime of the local join.  Assumes ghost is a
      78             :    current local join. */
      79             : 
      80             : FD_FN_PURE static inline fd_wksp_t *
      81        8382 : wksp( fd_ghost_t const * ghost ) {
      82        8382 :   return (fd_wksp_t *)( ((ulong)ghost) - ghost->wksp_gaddr );
      83        8382 : }
      84             : 
      85             : static inline blk_pool_t *
      86        5178 : blk_pool( fd_ghost_t * ghost ) {
      87        5178 :   return (blk_pool_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->blk_pool_gaddr );
      88        5178 : }
      89             : 
      90             : static inline blk_pool_t const *
      91           0 : blk_pool_const( fd_ghost_t const * ghost ) {
      92           0 :   return (blk_pool_t const *)fd_wksp_laddr_fast( wksp( ghost ), ghost->blk_pool_gaddr );
      93           0 : }
      94             : 
      95             : static inline blk_map_t *
      96        2874 : blk_map( fd_ghost_t * ghost ) {
      97        2874 :   return (blk_map_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->blk_map_gaddr );
      98        2874 : }
      99             : 
     100             : static inline vtr_pool_t *
     101         183 : vtr_pool( fd_ghost_t * ghost ) {
     102         183 :   return (vtr_pool_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->vtr_pool_gaddr );
     103         183 : }
     104             : 
     105             : static inline vtr_map_t *
     106          72 : vtr_map( fd_ghost_t * ghost ) {
     107          72 :   return (vtr_map_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->vtr_map_gaddr );
     108          72 : }
     109             : 
     110             : /* blk_vtr_dlist returns the local join to blk's vtr_dlist — the list of
     111             :    vtrs whose prev_block_id == blk->id.  Each block slot is bound (by
     112             :    gaddr) to a dlist once in fd_ghost_new, so this is gaddr-safe across
     113             :    relocation / separate local joins. */
     114             : 
     115             : static inline vtr_dlist_t *
     116          75 : blk_vtr_dlist( fd_ghost_t * ghost, fd_ghost_blk_t const * blk ) {
     117          75 :   return (vtr_dlist_t *)fd_wksp_laddr_fast( wksp( ghost ), blk->vtr_dlist_gaddr );
     118          75 : }
     119             : 
     120             : ulong
     121         624 : fd_ghost_align( void ) {
     122         624 :   return alignof(fd_ghost_t);
     123         624 : }
     124             : 
     125             : ulong
     126             : fd_ghost_footprint( ulong blk_max,
     127         120 :                     ulong vtr_max ) {
     128         120 :   blk_max = fd_ulong_pow2_up( blk_max );
     129         120 :   vtr_max = fd_ulong_pow2_up( vtr_max ) * 2; /* epoch boundary overlap — old epoch vtrs parked on live blocks while new epoch voters acquire */
     130         120 :   ulong blk_chain_cnt = blk_map_chain_cnt_est( blk_max );
     131         120 :   ulong vtr_chain_cnt = vtr_map_chain_cnt_est( vtr_max );
     132         120 :   return FD_LAYOUT_FINI(
     133         120 :     FD_LAYOUT_APPEND(
     134         120 :     FD_LAYOUT_APPEND(
     135         120 :     FD_LAYOUT_APPEND(
     136         120 :     FD_LAYOUT_APPEND(
     137         120 :     FD_LAYOUT_APPEND(
     138         120 :     FD_LAYOUT_APPEND(
     139         120 :     FD_LAYOUT_INIT,
     140         120 :       alignof(fd_ghost_t), sizeof(fd_ghost_t)                  ),
     141         120 :       blk_pool_align(),    blk_pool_footprint( blk_max )       ),
     142         120 :       blk_map_align(),     blk_map_footprint ( blk_chain_cnt ) ),
     143         120 :       vtr_pool_align(),    vtr_pool_footprint( vtr_max )       ),
     144         120 :       vtr_map_align(),     vtr_map_footprint ( vtr_chain_cnt ) ),
     145         120 :       vtr_dlist_align(),   vtr_dlist_footprint() * blk_max     ),
     146         120 :     fd_ghost_align() );
     147         120 : }
     148             : 
     149             : void *
     150             : fd_ghost_new( void * shmem,
     151             :               ulong  blk_max,
     152             :               ulong  vtr_max,
     153          60 :               ulong  seed ) {
     154             : 
     155          60 :   if( FD_UNLIKELY( !shmem ) ) {
     156           0 :     FD_LOG_WARNING(( "NULL mem" ));
     157           0 :     return NULL;
     158           0 :   }
     159             : 
     160          60 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_ghost_align() ) ) ) {
     161           0 :     FD_LOG_WARNING(( "misaligned mem" ));
     162           0 :     return NULL;
     163           0 :   }
     164             : 
     165          60 :   ulong footprint = fd_ghost_footprint( blk_max, vtr_max );
     166             : 
     167          60 :   blk_max = fd_ulong_pow2_up( blk_max );
     168          60 :   vtr_max = fd_ulong_pow2_up( vtr_max ) * 2; /* epoch boundary overlap */
     169          60 :   if( FD_UNLIKELY( !footprint ) ) {
     170           0 :     FD_LOG_WARNING(( "bad blk_max (%lu)", blk_max ));
     171           0 :     return NULL;
     172           0 :   }
     173             : 
     174          60 :   fd_wksp_t * wksp = fd_wksp_containing( shmem );
     175          60 :   if( FD_UNLIKELY( !wksp ) ) {
     176           0 :     FD_LOG_WARNING(( "shmem must be part of a workspace" ));
     177           0 :     return NULL;
     178           0 :   }
     179             : 
     180          60 :   fd_memset( shmem, 0, footprint );
     181             : 
     182          60 :   ulong blk_chain_cnt = blk_map_chain_cnt_est( blk_max );
     183          60 :   ulong vtr_chain_cnt = vtr_map_chain_cnt_est( vtr_max );
     184             : 
     185          60 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     186          60 :   fd_ghost_t * ghost    = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_ghost_t), sizeof(fd_ghost_t)                  );
     187          60 :   void *       blk_pool = FD_SCRATCH_ALLOC_APPEND( l, blk_pool_align(),    blk_pool_footprint( blk_max )       );
     188          60 :   void *       blk_map  = FD_SCRATCH_ALLOC_APPEND( l, blk_map_align(),     blk_map_footprint ( blk_chain_cnt ) );
     189          60 :   void *       vtr_pool  = FD_SCRATCH_ALLOC_APPEND( l, vtr_pool_align(),    vtr_pool_footprint( vtr_max )       );
     190          60 :   void *       vtr_map   = FD_SCRATCH_ALLOC_APPEND( l, vtr_map_align(),     vtr_map_footprint ( vtr_chain_cnt ) );
     191          60 :   void *       vtr_dlist = FD_SCRATCH_ALLOC_APPEND( l, vtr_dlist_align(),   vtr_dlist_footprint() * blk_max     );
     192          60 :   FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_ghost_align() ) == (ulong)shmem + footprint );
     193             : 
     194          60 :   ghost->root           = ULONG_MAX;
     195          60 :   ghost->wksp_gaddr     = fd_wksp_gaddr_fast( wksp, ghost );
     196          60 :   ghost->blk_pool_gaddr = fd_wksp_gaddr_fast( wksp, blk_pool_join( blk_pool_new ( blk_pool, blk_max             ) ) );
     197          60 :   ghost->blk_map_gaddr  = fd_wksp_gaddr_fast( wksp, blk_map_join ( blk_map_new  ( blk_map,  blk_chain_cnt, seed ) ) );
     198          60 :   ghost->vtr_pool_gaddr = fd_wksp_gaddr_fast( wksp, vtr_pool_join( vtr_pool_new ( vtr_pool, vtr_max             ) ) );
     199          60 :   ghost->vtr_map_gaddr  = fd_wksp_gaddr_fast( wksp, vtr_map_join ( vtr_map_new  ( vtr_map,  vtr_chain_cnt, seed ) ) );
     200             : 
     201             :   /* Format one vtr_dlist per block slot and bind it (by gaddr) to its
     202             :      pool element.  The binding is permanent: acquire/release in
     203             :      insert()/fd_ghost_publish never changes blk->vtr_dlist_gaddr, and
     204             :      fd_ghost_publish drains a pruned block's dlist (leaving it empty)
     205             :      before releasing the slot, so a re-acquired block always starts with
     206             :      a clean list. */
     207             : 
     208          60 :   blk_pool_t * bp = (blk_pool_t *)fd_wksp_laddr_fast( wksp, ghost->blk_pool_gaddr ); /* blk_pool() accessor is shadowed by the local here */
     209        2724 :   for( ulong i=0UL; i<blk_max; i++ ) {
     210        2664 :     void * dl = vtr_dlist_join( vtr_dlist_new( (uchar *)vtr_dlist + i*vtr_dlist_footprint() ) );
     211        2664 :     blk_pool_ele( bp, i )->vtr_dlist_gaddr = fd_wksp_gaddr_fast( wksp, dl );
     212        2664 :   }
     213             : 
     214          60 :   return shmem;
     215          60 : }
     216             : 
     217             : fd_ghost_t *
     218          60 : fd_ghost_join( void * shghost ) {
     219          60 :   fd_ghost_t * ghost = (fd_ghost_t *)shghost;
     220             : 
     221          60 :   if( FD_UNLIKELY( !ghost ) ) {
     222           0 :     FD_LOG_WARNING(( "NULL ghost" ));
     223           0 :     return NULL;
     224           0 :   }
     225             : 
     226          60 :   if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)ghost, fd_ghost_align() ) ) ) {
     227           0 :     FD_LOG_WARNING(( "misaligned ghost" ));
     228           0 :     return NULL;
     229           0 :   }
     230             : 
     231          60 :   return ghost;
     232          60 : }
     233             : 
     234             : void *
     235          39 : fd_ghost_leave( fd_ghost_t const * ghost ) {
     236             : 
     237          39 :   if( FD_UNLIKELY( !ghost ) ) {
     238           0 :     FD_LOG_WARNING(( "NULL ghost" ));
     239           0 :     return NULL;
     240           0 :   }
     241             : 
     242          39 :   return (void *)ghost;
     243          39 : }
     244             : 
     245             : void *
     246          39 : fd_ghost_delete( void * ghost ) {
     247             : 
     248          39 :   if( FD_UNLIKELY( !ghost ) ) {
     249           0 :     FD_LOG_WARNING(( "NULL ghost" ));
     250           0 :     return NULL;
     251           0 :   }
     252             : 
     253          39 :   if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)ghost, fd_ghost_align() ) ) ) {
     254           0 :     FD_LOG_WARNING(( "misaligned ghost" ));
     255           0 :     return NULL;
     256           0 :   }
     257             : 
     258          39 :   return ghost;
     259          39 : }
     260             : 
     261             : fd_ghost_blk_t *
     262         126 : fd_ghost_root( fd_ghost_t * ghost ) {
     263         126 :   return blk_pool_ele( blk_pool( ghost ), ghost->root );
     264         126 : }
     265             : 
     266             : fd_ghost_blk_t *
     267           0 : fd_ghost_parent( fd_ghost_t * ghost, fd_ghost_blk_t * blk ) {
     268           0 :   return blk_pool_ele( blk_pool( ghost ), blk->parent );
     269           0 : }
     270             : 
     271             : fd_ghost_blk_t *
     272             : fd_ghost_query( fd_ghost_t       * ghost,
     273         258 :                 fd_hash_t  const * block_id ) {
     274         258 :   return blk_map_ele_query( blk_map( ghost ), block_id, NULL, blk_pool( ghost ) );
     275         258 : }
     276             : 
     277             : fd_ghost_blk_t *
     278             : fd_ghost_best( fd_ghost_t     * ghost,
     279          27 :                fd_ghost_blk_t * root ) {
     280          27 :   blk_pool_t *     pool = blk_pool( ghost );
     281          27 :   ulong            null = blk_pool_idx_null( pool );
     282          27 :   fd_ghost_blk_t * best = root;
     283         102 :   while( FD_LIKELY( best->child != null ) ) {
     284          75 :     int              valid = 0; /* at least one child is valid */
     285          75 :     fd_ghost_blk_t * child = blk_pool_ele( pool, best->child );
     286         171 :     while( FD_LIKELY( child ) ) { /* greedily pick the heaviest valid child */
     287          96 :       if( FD_LIKELY( child->valid ) ) {
     288          90 :         if( FD_LIKELY( !valid ) ) { /* this is the first valid child, so progress the head */
     289          75 :           best  = child;
     290          75 :           valid = 1;
     291          75 :         }
     292             : 
     293             :         /* When stake is equal, tie-break by lower slot.  Two valid
     294             :            children with equal stake and equal slot (ie. equivocating
     295             :            blocks) cannot occur: equivocating blocks are marked valid=0,
     296             :            so at most one of them would be valid unless multiple blocks
     297             :            for that slot are duplicate confirmed, which is a consensus
     298             :            invariant violation. */
     299             : 
     300          90 :         best = fd_ptr_if(
     301          90 :           fd_int_if(
     302          90 :             child->stake == best->stake,   /* if the weights are equal */
     303          90 :             child->slot  <  best->slot,    /* then tie-break by lower slot number */
     304          90 :             child->stake >  best->stake ), /* else return heavier */
     305          90 :           child, best );
     306          90 :       }
     307          96 :       child = blk_pool_ele( pool, child->sibling );
     308          96 :     }
     309          75 :     if( FD_UNLIKELY( !valid ) ) break; /* no children are valid, so short-circuit traversal */
     310          75 :   }
     311          27 :   return best;
     312          27 : }
     313             : 
     314             : fd_ghost_blk_t *
     315             : fd_ghost_deepest( fd_ghost_t     * ghost,
     316          18 :                   fd_ghost_blk_t * root ) {
     317          18 :   blk_pool_t *     pool = blk_pool( ghost );
     318          18 :   ulong            null = blk_pool_idx_null( pool );
     319          18 :   fd_ghost_blk_t * head = blk_map_ele_remove( blk_map( ghost ), &root->id, NULL, pool ); /* remove ele from map to reuse `.next` */
     320          18 :   fd_ghost_blk_t * tail = head;
     321          18 :   fd_ghost_blk_t * prev = NULL;
     322             : 
     323             :   /* Below is a level-order traversal (BFS), returning the last leaf
     324             :      which is guaranteed to return an element of the max depth.
     325             : 
     326             :      It temporarily removes elements of the map when pushing onto the
     327             :      BFS queue to reuse the .next pointer and then inserts back into
     328             :      the map on queue pop. */
     329             : 
     330          18 :   head->next = null;
     331          99 :   while( FD_LIKELY( head ) ) {
     332          81 :     fd_ghost_blk_t const * child = blk_pool_ele( pool, head->child );
     333         144 :     while( FD_LIKELY( child ) ) {
     334          63 :       FD_TEST( blk_map_ele_remove( blk_map( ghost ), &child->id, NULL, pool ) ); /* in the tree so must be in the map */
     335          63 :       tail->next = blk_pool_idx( pool, child );
     336          63 :       tail       = blk_pool_ele( pool, tail->next );
     337          63 :       tail->next = blk_pool_idx_null( pool );
     338          63 :       child      = blk_pool_ele( pool, child->sibling ); /* next sibling */
     339          63 :     }
     340          81 :     fd_ghost_blk_t * next = blk_pool_ele( pool, head->next ); /* pop prune queue head */
     341          81 :     blk_map_ele_insert( blk_map( ghost ), head, pool );       /* re-insert head into map */
     342          81 :     prev = head;
     343          81 :     head = next;
     344          81 :   }
     345          18 :   return prev;
     346          18 : }
     347             : 
     348          12 : #define PREDICATE_ANCESTOR( predicate ) do {                          \
     349          12 :     fd_ghost_blk_t * ancestor = descendant;                           \
     350          36 :     while( FD_LIKELY( ancestor ) ) {                                  \
     351          30 :       if( FD_LIKELY( predicate ) ) return ancestor;                   \
     352          30 :       ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent ); \
     353          24 :     }                                                                 \
     354          12 :     return NULL;                                                      \
     355          12 :   } while(0)
     356             : 
     357             : fd_ghost_blk_t *
     358             : fd_ghost_ancestor( fd_ghost_t      * ghost,
     359             :                    fd_ghost_blk_t  * descendant,
     360           6 :                    fd_hash_t const * ancestor_id ) {
     361           6 :   PREDICATE_ANCESTOR( 0==memcmp( &ancestor->id, ancestor_id, sizeof(fd_hash_t) ) );
     362           6 : }
     363             : 
     364             : fd_ghost_blk_t *
     365             : fd_ghost_slot_ancestor( fd_ghost_t     * ghost,
     366             :                         fd_ghost_blk_t * descendant,
     367           0 :                         ulong            slot ) {
     368           0 :   PREDICATE_ANCESTOR( ancestor->slot == slot );
     369           0 : }
     370             : 
     371             : fd_ghost_blk_t *
     372             : fd_ghost_invalid_ancestor( fd_ghost_t     * ghost,
     373           6 :                            fd_ghost_blk_t * descendant ) {
     374           6 :   PREDICATE_ANCESTOR( !ancestor->valid );
     375           6 : }
     376             : 
     377             : static fd_ghost_blk_t *
     378             : insert( fd_ghost_t      * ghost,
     379             :         ulong             slot,
     380         414 :         fd_hash_t const * block_id ) {
     381         414 :   fd_ghost_blk_t * pool = blk_pool( ghost );
     382         414 :   ulong            null = blk_pool_idx_null( pool );
     383         414 :   fd_ghost_blk_t * blk  = blk_map_ele_query( blk_map( ghost ), block_id, NULL, pool );
     384             : 
     385         414 :   FD_TEST( !blk ); /* duplicate insert */
     386         414 :   FD_TEST( blk_pool_free( pool ) ); /* ghost full */
     387             : 
     388         414 :   blk              = blk_pool_ele_acquire( pool );
     389         414 :   blk->id          = *block_id;
     390         414 :   blk->slot        = slot;
     391         414 :   blk->next        = null;
     392         414 :   blk->parent      = null;
     393         414 :   blk->child       = null;
     394         414 :   blk->sibling     = null;
     395         414 :   blk->stake       = 0;
     396         414 :   blk->total_stake = 0;
     397         414 :   blk->valid       = 1;
     398         414 :   blk_map_ele_insert( blk_map( ghost ), blk, pool );
     399         414 :   return blk;
     400         414 : }
     401             : 
     402             : fd_ghost_blk_t *
     403             : fd_ghost_init( fd_ghost_t      * ghost,
     404             :                ulong             slot,
     405          60 :                fd_hash_t const * block_id ) {
     406          60 :   fd_ghost_blk_t * blk = insert( ghost, slot, block_id );
     407          60 :   ghost->root          = blk_pool_idx( blk_pool( ghost ), blk );
     408          60 :   ghost->width         = 1;
     409          60 :   return blk;
     410          60 : }
     411             : 
     412             : fd_ghost_blk_t *
     413             : fd_ghost_insert( fd_ghost_t      * ghost,
     414             :                  ulong             slot,
     415             :                  fd_hash_t const * block_id,
     416         354 :                  fd_hash_t const * parent_block_id ) {
     417         354 :   fd_ghost_blk_t * blk    = insert( ghost, slot, block_id );
     418         354 :   fd_ghost_blk_t * pool   = blk_pool( ghost );
     419         354 :   ulong            null   = blk_pool_idx_null( pool );
     420         354 :   fd_ghost_blk_t * parent = blk_map_ele_query( blk_map( ghost ), parent_block_id, NULL, pool );
     421         354 :   FD_TEST( parent ); /* parent must exist be in ghost */
     422         354 :   blk->parent  = blk_pool_idx( pool, parent );
     423         354 :   if( FD_LIKELY( parent->child == null ) ) {
     424         270 :     parent->child = blk_pool_idx( pool, blk );    /* left-child */
     425         270 :   } else {
     426          84 :     fd_ghost_blk_t * sibling = blk_pool_ele( pool, parent->child );
     427          93 :     while( sibling->sibling != null ) sibling = blk_pool_ele( pool, sibling->sibling );
     428          84 :     sibling->sibling = blk_pool_idx( pool, blk ); /* right-sibling */
     429          84 :     ghost->width++;
     430          84 :   }
     431             : 
     432         354 :   return blk;
     433         354 : }
     434             : 
     435             : int
     436             : fd_ghost_count_vote( fd_ghost_t *        ghost,
     437             :                      fd_ghost_blk_t *    blk,
     438             :                      fd_pubkey_t const * vote_acc,
     439             :                      ulong               stake,
     440          33 :                      ulong               slot ) {
     441             : 
     442          33 :   fd_ghost_blk_t const * root = fd_ghost_root( ghost );
     443          33 :   fd_ghost_vtr_t *       vtr  = vtr_map_ele_query( vtr_map( ghost ), vote_acc, NULL, vtr_pool( ghost ) );
     444             : 
     445          33 :   if( FD_UNLIKELY( slot==ULONG_MAX  ) ) return FD_GHOST_ERR_NOT_VOTED;
     446          33 :   if( FD_UNLIKELY( slot< root->slot ) ) return FD_GHOST_ERR_VOTE_TOO_OLD;
     447             : 
     448          33 :   if( FD_UNLIKELY( !vtr ) ) {
     449             : 
     450             :     /* This vote account address has not previously voted, so add it to
     451             :        the map of voters. */
     452             : 
     453          24 :     vtr       = vtr_pool_ele_acquire( vtr_pool( ghost ) );
     454          24 :     vtr->addr = *vote_acc;
     455          24 :     vtr_map_ele_insert( vtr_map( ghost ), vtr, vtr_pool( ghost ) );
     456             : 
     457          24 :   } else {
     458             : 
     459             :     /* Only process the vote if it is not the same as the previous vote
     460             :        and also that the vote slot is most recent.  It's possible for
     461             :        ghost to process votes out of order because votes happen in
     462             :        replay order which is concurrent across different forks.
     463             : 
     464             :        For example, if a voter votes for 3 then switches to 5, we might
     465             :        observe the vote for 5 before the vote for 3. */
     466             : 
     467           9 :     if( FD_UNLIKELY( !( slot > vtr->prev_slot ) ) ) return FD_GHOST_ERR_ALREADY_VOTED;
     468             : 
     469             :     /* The voter is switching off their previous vote.  Remove the vtr
     470             :        from the previous block's dlist; it is re-pushed onto the new
     471             :        block's dlist below.  By the dlist invariant, a vtr that is still
     472             :        in the vtr_map has not had its previous block pruned, so prev is
     473             :        non-NULL. */
     474             : 
     475           6 :     fd_ghost_blk_t * prev = blk_map_ele_query( blk_map( ghost ), &vtr->prev_block_id, NULL, blk_pool( ghost ) );
     476           6 :     if( FD_LIKELY( prev ) ) vtr_dlist_ele_remove( blk_vtr_dlist( ghost, prev ), vtr, vtr_pool( ghost ) );
     477             : 
     478             :     /* LMD-rule: subtract the voter's stake from the entire fork they
     479             :       previously voted for. */
     480             : 
     481             :     /* TODO can optimize this if they're voting for the same fork */
     482             : 
     483           6 :     fd_ghost_blk_t * ancestor = prev;
     484          24 :     while( FD_LIKELY( ancestor ) ) {
     485          18 :       int cf = __builtin_usubl_overflow( ancestor->stake, vtr->prev_stake, &ancestor->stake );
     486          18 :       if( FD_UNLIKELY( cf ) ) {
     487           0 :         FD_BASE58_ENCODE_32_BYTES( ancestor->id.key, ancestor_id_b58 );
     488           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 ));
     489           0 :       }
     490          18 :       ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent );
     491          18 :     }
     492           6 :   }
     493             : 
     494             :   /* Park the vtr on the dlist of the block it is now voting for, so that
     495             :      it is released back to the vtr_pool when that block is pruned. */
     496             : 
     497          30 :   vtr_dlist_ele_push_tail( blk_vtr_dlist( ghost, blk ), vtr, vtr_pool( ghost ) );
     498             : 
     499             :   /* Add voter's stake to the entire fork they are voting for. Propagate
     500             :      the vote stake up the ancestry. We do this for all cases we exited
     501             :      above: this vote is the first vote we've seen from a pubkey, this
     502             :      vote is switched from a previous vote that was on a missing ele
     503             :      (pruned), or the regular case. */
     504             : 
     505          30 :   fd_ghost_blk_t * ancestor = blk;
     506         129 :   while( FD_LIKELY( ancestor ) ) {
     507          99 :     int cf = __builtin_uaddl_overflow( ancestor->stake, stake, &ancestor->stake );
     508          99 :     if( FD_UNLIKELY( cf ) ) {
     509           0 :       FD_BASE58_ENCODE_32_BYTES( ancestor->id.key, ancestor_id_b58 );
     510           0 :       FD_LOG_CRIT(( "[%s] overflow (after): %lu. added: %lu. (slot %lu, block_id: %s)", __func__, ancestor->stake, stake, ancestor->slot, ancestor_id_b58 ));
     511           0 :     }
     512          99 :     ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent );
     513          99 :   }
     514          30 :   vtr->prev_stake    = stake;
     515          30 :   vtr->prev_slot     = slot;
     516          30 :   vtr->prev_block_id = blk->id;
     517          30 :   return FD_GHOST_SUCCESS;
     518          30 : }
     519             : 
     520             : void
     521             : fd_ghost_publish( fd_ghost_t     * ghost,
     522           9 :                   fd_ghost_blk_t * newr ) {
     523             : 
     524           9 :   fd_ghost_blk_t * pool = blk_pool( ghost );
     525           9 :   ulong            null = blk_pool_idx_null( pool );
     526           9 :   fd_ghost_blk_t * oldr = fd_ghost_root( ghost );
     527             : 
     528           9 :   if( FD_UNLIKELY( oldr==newr ) ) return;
     529             : 
     530             :   /* First, remove the previous root, and add it to the prune list. In
     531             :      this context, head is the list head (not to be confused with the
     532             :      ghost head.) */
     533             : 
     534           9 :   fd_ghost_blk_t * head = blk_map_ele_remove( blk_map( ghost ), &oldr->id, NULL, pool ); /* remove ele from map to reuse `.next` */
     535           9 :   fd_ghost_blk_t * tail = head;
     536             : 
     537             :   /* Second, BFS down the tree, pruning all of root's ancestors and also
     538             :      any descendants of those ancestors.
     539             : 
     540             :          oldr
     541             :           |
     542             :           X
     543             :          / \
     544             :       newr   Y
     545             :               |
     546             :               Z
     547             : 
     548             :          ...
     549             : 
     550             :         newr
     551             : 
     552             :     BFS starts with oldr.  Its child is X.  X != newr, so X gets
     553             :     enqueued. oldr is released.  Next head = X. X's children are newr
     554             :     and Y.  newr is skipped.  Y gets enqueued.  X is released.  Next
     555             :     head = Y.  Y's child Z gets enqueued.  Y released.  Z released.
     556             :     Queue is empty, loop ends.
     557             : 
     558             :        oldr
     559             :      /    \
     560             :     A     newr
     561             :           /   \
     562             :          B     C
     563             : 
     564             :       ...
     565             : 
     566             :      newr
     567             :      /   \
     568             :     B     C
     569             : 
     570             : 
     571             :     The BFS starts with oldr.  Its children are A and newr.  A gets
     572             :     enqueued for pruning.  newr is skipped (line 374).  Then oldr is
     573             :     released.  Next, head = A.  A has no children.  A is released.
     574             :     Queue is empty, loop ends. */
     575             : 
     576           9 :   head->next = null;
     577          48 :   while( FD_LIKELY( head ) ) {
     578          39 :     fd_ghost_blk_t * child = blk_pool_ele( blk_pool( ghost ), head->child );
     579          78 :     while( FD_LIKELY( child ) ) {                                                    /* iterate over children */
     580          39 :       if( FD_LIKELY( child != newr ) ) {                                             /* stop at new root */
     581          30 :         tail->next = blk_map_idx_remove( blk_map( ghost ), &child->id, null, pool ); /* remove ele from map to reuse `.next` */
     582          30 :         FD_BASE58_ENCODE_32_BYTES( child->id.key, block_id_cstr );
     583          30 :         tail       = blk_pool_ele( blk_pool( ghost ), tail->next );                  /* push onto prune queue (so descendants can be pruned) */
     584          30 :         tail->next = blk_pool_idx_null( blk_pool( ghost ) );
     585          30 :       }
     586          39 :       child = blk_pool_ele( blk_pool( ghost ), child->sibling ); /* next sibling */
     587          39 :       ghost->width -= !!child; /* has a sibling == a fork to be pruned */
     588          39 :     }
     589          39 :     fd_ghost_blk_t * next = blk_pool_ele( blk_pool( ghost ), head->next ); /* pop prune queue head */
     590             : 
     591             :     /* Release every vtr parked on this pruned block's dlist back to the
     592             :        vtr_pool.  Their previous stake lived on this pruned subtree (which
     593             :        is being discarded along with the block), so there is nothing to
     594             :        unwind; if those voters vote again on a surviving block,
     595             :        fd_ghost_count_vote acquires a fresh vtr.  This is what bounds the
     596             :        vtr_pool to the set of voters on the live tree. */
     597             : 
     598          39 :     vtr_dlist_t * dlist = blk_vtr_dlist( ghost, head );
     599          45 :     while( FD_LIKELY( !vtr_dlist_is_empty( dlist, vtr_pool( ghost ) ) ) ) {
     600           6 :       fd_ghost_vtr_t * vtr = vtr_dlist_ele_pop_head( dlist, vtr_pool( ghost ) );
     601           6 :       vtr_map_ele_remove_fast( vtr_map( ghost ), vtr, vtr_pool( ghost ) );
     602           6 :       vtr_pool_ele_release( vtr_pool( ghost ), vtr );
     603           6 :     }
     604             : 
     605          39 :     blk_pool_ele_release( blk_pool( ghost ), head );                       /* free prune queue head */
     606          39 :     head = next;                                                           /* move prune queue head forward */
     607          39 :   }
     608           9 :   newr->parent = null;                                    /* unlink old root */
     609           9 :   ghost->root  = blk_pool_idx( blk_pool( ghost ), newr ); /* replace with new root */
     610           9 : }
     611             : 
     612             : /* mark_invalid marks the entire subtree beginning from root as invalid.
     613             :    Implementation is iterative pre-order traversal using O(1) space. */
     614             : 
     615             : static void
     616             : mark_invalid( fd_ghost_t     * ghost,
     617           6 :               fd_ghost_blk_t * root ) {
     618           6 :   fd_ghost_blk_t * pool = blk_pool( ghost );
     619           6 :   fd_ghost_blk_t * curr = root;
     620             : 
     621             :   /* Loop invariant: curr has not been visited.
     622             : 
     623             :      Before: curr = root, which has not been visited.  Trivially true.
     624             : 
     625             :      After: curr is set to either a child (step 2) or a right sibling of
     626             :      an ancestor found during backtracking (step 3).  Preorder visits
     627             :      parents before children and left before right, so neither has been
     628             :      visited yet.  If backtracking reaches root (step 4), loop exits. */
     629             : 
     630          18 :   for(;;) {
     631             : 
     632             :     /* 1. Visit: mark the current curr invalid. */
     633             : 
     634          18 :     curr->valid = 0;
     635             : 
     636             :     /* 2. Descend: if the curr has a child, pivot to it. */
     637             : 
     638          18 :     fd_ghost_blk_t * child = blk_pool_ele( pool, curr->child );
     639          18 :     if( FD_LIKELY( child ) ) { curr = child; continue; }
     640             : 
     641             :     /* 3. Backtrack: if the curr is a leaf, traverse up until we find an
     642             :           ancestor with a right sibling, then pivot to that sibling. */
     643             : 
     644          18 :     while( FD_LIKELY( curr!=root ) ) {
     645          12 :       fd_ghost_blk_t * sibling = blk_pool_ele( pool, curr->sibling );
     646          12 :       if( FD_LIKELY( sibling ) ) { curr = sibling; break; }
     647          12 :       curr = blk_pool_ele( pool, curr->parent );
     648          12 :     }
     649             : 
     650             :     /* 4. Terminate: if we backtrack all the way to root, the traversal
     651             :           is complete. */
     652             : 
     653           6 :     if( FD_UNLIKELY( curr==root ) ) break;
     654           6 :   }
     655           6 : }
     656             : 
     657             : void
     658             : fd_ghost_confirm( fd_ghost_t      * ghost,
     659           0 :                   fd_hash_t const * confirmed_block_id ) {
     660           0 :   fd_ghost_blk_t * pool = blk_pool( ghost );
     661           0 :   fd_ghost_blk_t * blk  = blk_map_ele_query( blk_map( ghost ), confirmed_block_id, NULL, pool );
     662           0 :   if( FD_UNLIKELY( !blk ) ) return;
     663             : 
     664             :   /* Mark the confirmed block and its ancestors as valid, short-
     665             :      circuiting at the first ancestor that is already valid. */
     666             : 
     667           0 :   fd_ghost_blk_t * anc = blk;
     668           0 :   while( FD_LIKELY( anc ) ) {
     669           0 :     if( FD_LIKELY( anc->valid ) ) break;
     670           0 :     anc->valid = 1;
     671           0 :     anc = blk_pool_ele( pool, anc->parent );
     672           0 :   }
     673           0 : }
     674             : 
     675             : void
     676             : fd_ghost_eqvoc( fd_ghost_t      * ghost,
     677           6 :                 fd_hash_t const * block_id ) {
     678           6 :   fd_ghost_blk_t * pool = blk_pool( ghost );
     679           6 :   fd_ghost_blk_t * blk  = blk_map_ele_query( blk_map( ghost ), block_id, NULL, pool );
     680           6 :   if( FD_UNLIKELY( !blk ) ) return;
     681           6 :   mark_invalid( ghost, blk );
     682           6 : }
     683             : 
     684             : ulong
     685           0 : fd_ghost_width( fd_ghost_t * ghost ) {
     686           0 :   return ghost->width;
     687           0 : }
     688             : 
     689             : fd_ghost_blk_t *
     690             : fd_ghost_blk_map_remove( fd_ghost_t     * ghost,
     691         588 :                          fd_ghost_blk_t * blk ) {
     692         588 :   return blk_map_ele_remove( blk_map( ghost ), &blk->id, NULL, blk_pool( ghost ) );
     693         588 : }
     694             : 
     695             : void
     696             : fd_ghost_blk_map_insert( fd_ghost_t     * ghost,
     697         588 :                          fd_ghost_blk_t * blk ) {
     698         588 :   blk_map_ele_insert( blk_map( ghost ), blk, blk_pool( ghost ) );
     699         588 : }
     700             : 
     701             : fd_ghost_blk_t *
     702             : fd_ghost_blk_child( fd_ghost_t     * ghost,
     703         564 :                     fd_ghost_blk_t * blk ) {
     704         564 :   return blk_pool_ele( blk_pool( ghost ), blk->child );
     705         564 : }
     706             : 
     707             : fd_ghost_blk_t *
     708             : fd_ghost_blk_sibling( fd_ghost_t     * ghost,
     709         543 :                       fd_ghost_blk_t * blk ) {
     710         543 :   return blk_pool_ele( blk_pool( ghost ), blk->sibling );
     711         543 : }
     712             : 
     713             : fd_ghost_blk_t *
     714             : fd_ghost_blk_next( fd_ghost_t     * ghost,
     715         588 :                    fd_ghost_blk_t * blk ) {
     716         588 :   return blk_pool_ele( blk_pool( ghost ), blk->next );
     717         588 : }
     718             : 
     719             : ulong
     720             : fd_ghost_blk_idx( fd_ghost_t     * ghost,
     721         537 :                   fd_ghost_blk_t * blk ) {
     722         537 :   return blk_pool_idx( blk_pool( ghost ), blk );
     723         537 : }
     724             : 
     725             : ulong
     726          51 : fd_ghost_blk_idx_null( fd_ghost_t * ghost ) {
     727          51 :   return blk_pool_idx_null( blk_pool( ghost ) );
     728          51 : }
     729             : 
     730             : int
     731          45 : fd_ghost_verify( fd_ghost_t * ghost ) {
     732          45 :   if( FD_UNLIKELY( !ghost ) ) {
     733           0 :     FD_LOG_WARNING(( "NULL ghost" ));
     734           0 :     return -1;
     735           0 :   }
     736             : 
     737          45 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)ghost, fd_ghost_align() ) ) ) {
     738           0 :     FD_LOG_WARNING(( "misaligned ghost" ));
     739           0 :     return -1;
     740           0 :   }
     741             : 
     742          45 :   fd_wksp_t * wksp = fd_wksp_containing( ghost );
     743          45 :   if( FD_UNLIKELY( !wksp ) ) {
     744           0 :     FD_LOG_WARNING(( "ghost must be part of a workspace" ));
     745           0 :     return -1;
     746           0 :   }
     747             : 
     748          45 :   fd_ghost_blk_t const * pool = blk_pool( ghost );
     749             : 
     750             :   /* Check every ele that exists in pool exists in map. */
     751             : 
     752          45 :   if( blk_map_verify( blk_map( ghost ), blk_pool_max( pool ), pool ) ) return -1;
     753             : 
     754          45 :   return 0;
     755          45 : }
     756             : 
     757             : #include <stdio.h>
     758             : #include <string.h>
     759             : 
     760             : #define BUF_MAX 4096
     761             : #define DEPTH_MAX 512
     762             : 
     763             : static void
     764             : to_cstr( fd_ghost_t const *     ghost,
     765             :          fd_ghost_blk_t const * ele,
     766             :          ulong                  total_stake,
     767             :          int                    space,
     768             :          const char *           prefix,
     769             :          char *                 cstr,
     770             :          ulong                  len,
     771             :          ulong *                off,
     772           0 :          ulong                  depth ) {
     773           0 :   if( FD_UNLIKELY( depth>DEPTH_MAX ) ) return;
     774             : 
     775           0 :   fd_ghost_blk_t const * pool = blk_pool_const( ghost );
     776           0 :   int n;
     777             : 
     778           0 :   if( FD_UNLIKELY( ele == NULL ) ) return;
     779             : 
     780           0 :   if( FD_LIKELY( space > 0 ) && *off < len ) {
     781           0 :     cstr[(*off)++] = '\n';
     782           0 :   }
     783             : 
     784           0 :   for( int i = 0; i < space && *off < len; i++ ) {
     785           0 :     cstr[(*off)++] = ' ';
     786           0 :   }
     787             : 
     788           0 :   if( FD_UNLIKELY( ele->stake > 100 ) ) {
     789           0 :   }
     790             : 
     791           0 :   if( FD_UNLIKELY( total_stake == 0 ) ) {
     792           0 :     if( *off < len ) {
     793           0 :       n = snprintf( cstr + *off, len - *off, "%s%lu (%lu)", prefix, ele->slot, ele->stake );
     794           0 :       if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     795           0 :       *off += (ulong)n;
     796           0 :     }
     797           0 :   } else {
     798           0 :     double pct = ( (double)ele->stake / (double)total_stake ) * 100;
     799           0 :     if( FD_UNLIKELY( pct < 0.99 ) ) {
     800           0 :       if( *off < len ) {
     801           0 :         n = snprintf( cstr + *off, len - *off, "%s%lu (%.0lf%%, %lu)", prefix, ele->slot, pct, ele->stake );
     802           0 :         if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     803           0 :         *off += (ulong)n;
     804           0 :       }
     805           0 :     } else {
     806           0 :       if( *off < len ) {
     807           0 :         n = snprintf( cstr + *off, len - *off, "%s%lu (%.0lf%%)", prefix, ele->slot, pct );
     808           0 :         if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     809           0 :         *off += (ulong)n;
     810           0 :       }
     811           0 :     }
     812           0 :   }
     813             : 
     814           0 :   fd_ghost_blk_t const * curr = blk_pool_ele_const( pool, ele->child );
     815             : 
     816           0 :   while( curr ) {
     817           0 :     char const * next_prefix = blk_pool_ele_const( pool, curr->sibling ) ? "├── " : "└── ";
     818           0 :     to_cstr( ghost, curr, total_stake, space + 4, next_prefix, cstr, len, off, depth + 1 ); /* TODO remove recursion */
     819           0 :     curr = blk_pool_ele_const( pool, curr->sibling );
     820           0 :   }
     821           0 : }
     822             : 
     823             : char *
     824             : fd_ghost_to_cstr( fd_ghost_t const *     ghost,
     825             :                   fd_ghost_blk_t const * root,
     826             :                   char *                 cstr,
     827             :                   ulong                  cstr_max,
     828           0 :                   ulong *                cstr_len ) {
     829             : 
     830           0 :   ulong off = 0;
     831             : 
     832           0 :   int n = snprintf( cstr + off, cstr_max - off, "[Ghost]\n\n" );
     833           0 :   if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     834           0 :   off += (ulong)n;
     835             : 
     836           0 :   to_cstr( ghost, root, root->total_stake, 0, "", cstr, cstr_max, &off, 0 );
     837             : 
     838           0 :   if( off < cstr_max ) {
     839           0 :     n = snprintf( cstr + off, cstr_max - off, "\n\n" );
     840           0 :     if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     841           0 :     off += (ulong)n;
     842           0 :   }
     843             : 
     844           0 :   cstr[fd_ulong_min( off++, cstr_max - 1 )] = '\0';
     845           0 :   *cstr_len = fd_ulong_min( off, cstr_max );
     846           0 :   return cstr;
     847           0 : }

Generated by: LCOV version 1.14