LCOV - code coverage report
Current view: top level - choreo/ghost - fd_ghost.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 406 453 89.6 %
Date: 2026-06-04 08:36:56 Functions: 47 50 94.0 %

          Line data    Source code
       1             : #include "fd_ghost.h"
       2             : 
       3             : #define POOL_NAME blk_pool
       4         162 : #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        1377 : #define MAP_KEY                id
      10             : #define MAP_KEY_T              fd_hash_t
      11       23025 : #define MAP_KEY_EQ(k0,k1)      (!memcmp((k0),(k1), sizeof(fd_hash_t)))
      12       24546 : #define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t)))
      13        3726 : #define MAP_NEXT               next
      14             : #include "../../util/tmpl/fd_map_chain.c"
      15             : 
      16             : #define POOL_NAME vtr_pool
      17         162 : #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        1689 : #define MAP_KEY                            addr
      23             : #define MAP_KEY_T                          fd_pubkey_t
      24       20928 : #define MAP_KEY_EQ(k0,k1)                  (!memcmp((k0),(k1), sizeof(fd_pubkey_t)))
      25       23256 : #define MAP_KEY_HASH(key,seed)             (fd_hash((seed),(key),sizeof(fd_pubkey_t)))
      26        3741 : #define MAP_PREV                           map.prev
      27        4755 : #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       41211 : #define DLIST_PREV  dlist.prev
      34       41217 : #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      629622 : wksp( fd_ghost_t const * ghost ) {
      82      629622 :   return (fd_wksp_t *)( ((ulong)ghost) - ghost->wksp_gaddr );
      83      629622 : }
      84             : 
      85             : static inline blk_pool_t *
      86      471954 : blk_pool( fd_ghost_t * ghost ) {
      87      471954 :   return (blk_pool_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->blk_pool_gaddr );
      88      471954 : }
      89             : 
      90             : static inline blk_pool_t const *
      91        2772 : blk_pool_const( fd_ghost_t const * ghost ) {
      92        2772 :   return (blk_pool_t const *)fd_wksp_laddr_fast( wksp( ghost ), ghost->blk_pool_gaddr );
      93        2772 : }
      94             : 
      95             : static inline blk_map_t *
      96       24189 : blk_map( fd_ghost_t * ghost ) {
      97       24189 :   return (blk_map_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->blk_map_gaddr );
      98       24189 : }
      99             : 
     100             : static inline vtr_pool_t *
     101       66201 : vtr_pool( fd_ghost_t * ghost ) {
     102       66201 :   return (vtr_pool_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->vtr_pool_gaddr );
     103       66201 : }
     104             : 
     105             : static inline vtr_map_t *
     106       23256 : vtr_map( fd_ghost_t * ghost ) {
     107       23256 :   return (vtr_map_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->vtr_map_gaddr );
     108       23256 : }
     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       41250 : blk_vtr_dlist( fd_ghost_t * ghost, fd_ghost_blk_t const * blk ) {
     117       41250 :   return (vtr_dlist_t *)fd_wksp_laddr_fast( wksp( ghost ), blk->vtr_dlist_gaddr );
     118       41250 : }
     119             : 
     120             : ulong
     121         981 : fd_ghost_align( void ) {
     122         981 :   return alignof(fd_ghost_t);
     123         981 : }
     124             : 
     125             : ulong
     126             : fd_ghost_footprint( ulong blk_max,
     127         204 :                     ulong vtr_max ) {
     128         204 :   blk_max = fd_ulong_pow2_up( blk_max );
     129         204 :   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         204 :   ulong blk_chain_cnt = blk_map_chain_cnt_est( blk_max );
     131         204 :   ulong vtr_chain_cnt = vtr_map_chain_cnt_est( vtr_max );
     132         204 :   return FD_LAYOUT_FINI(
     133         204 :     FD_LAYOUT_APPEND(
     134         204 :     FD_LAYOUT_APPEND(
     135         204 :     FD_LAYOUT_APPEND(
     136         204 :     FD_LAYOUT_APPEND(
     137         204 :     FD_LAYOUT_APPEND(
     138         204 :     FD_LAYOUT_APPEND(
     139         204 :     FD_LAYOUT_INIT,
     140         204 :       alignof(fd_ghost_t), sizeof(fd_ghost_t)                  ),
     141         204 :       blk_pool_align(),    blk_pool_footprint( blk_max )       ),
     142         204 :       blk_map_align(),     blk_map_footprint ( blk_chain_cnt ) ),
     143         204 :       vtr_pool_align(),    vtr_pool_footprint( vtr_max )       ),
     144         204 :       vtr_map_align(),     vtr_map_footprint ( vtr_chain_cnt ) ),
     145         204 :       vtr_dlist_align(),   vtr_dlist_footprint() * blk_max     ),
     146         204 :     fd_ghost_align() );
     147         204 : }
     148             : 
     149             : void *
     150             : fd_ghost_new( void * shmem,
     151             :               ulong  blk_max,
     152             :               ulong  vtr_max,
     153          81 :               ulong  seed ) {
     154             : 
     155          81 :   if( FD_UNLIKELY( !shmem ) ) {
     156           0 :     FD_LOG_WARNING(( "NULL mem" ));
     157           0 :     return NULL;
     158           0 :   }
     159             : 
     160          81 :   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          81 :   ulong footprint = fd_ghost_footprint( blk_max, vtr_max );
     166             : 
     167          81 :   blk_max = fd_ulong_pow2_up( blk_max );
     168          81 :   vtr_max = fd_ulong_pow2_up( vtr_max ) * 2; /* epoch boundary overlap */
     169          81 :   if( FD_UNLIKELY( !footprint ) ) {
     170           0 :     FD_LOG_WARNING(( "bad blk_max (%lu)", blk_max ));
     171           0 :     return NULL;
     172           0 :   }
     173             : 
     174          81 :   fd_wksp_t * wksp = fd_wksp_containing( shmem );
     175          81 :   if( FD_UNLIKELY( !wksp ) ) {
     176           0 :     FD_LOG_WARNING(( "shmem must be part of a workspace" ));
     177           0 :     return NULL;
     178           0 :   }
     179             : 
     180          81 :   fd_memset( shmem, 0, footprint );
     181             : 
     182          81 :   ulong blk_chain_cnt = blk_map_chain_cnt_est( blk_max );
     183          81 :   ulong vtr_chain_cnt = vtr_map_chain_cnt_est( vtr_max );
     184             : 
     185          81 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     186          81 :   fd_ghost_t * ghost    = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_ghost_t), sizeof(fd_ghost_t)                  );
     187          81 :   void *       blk_pool = FD_SCRATCH_ALLOC_APPEND( l, blk_pool_align(),    blk_pool_footprint( blk_max )       );
     188          81 :   void *       blk_map  = FD_SCRATCH_ALLOC_APPEND( l, blk_map_align(),     blk_map_footprint ( blk_chain_cnt ) );
     189          81 :   void *       vtr_pool  = FD_SCRATCH_ALLOC_APPEND( l, vtr_pool_align(),    vtr_pool_footprint( vtr_max )       );
     190          81 :   void *       vtr_map   = FD_SCRATCH_ALLOC_APPEND( l, vtr_map_align(),     vtr_map_footprint ( vtr_chain_cnt ) );
     191          81 :   void *       vtr_dlist = FD_SCRATCH_ALLOC_APPEND( l, vtr_dlist_align(),   vtr_dlist_footprint() * blk_max     );
     192          81 :   FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_ghost_align() ) == (ulong)shmem + footprint );
     193             : 
     194          81 :   ghost->root           = ULONG_MAX;
     195          81 :   ghost->wksp_gaddr     = fd_wksp_gaddr_fast( wksp, ghost );
     196          81 :   ghost->blk_pool_gaddr = fd_wksp_gaddr_fast( wksp, blk_pool_join( blk_pool_new ( blk_pool, blk_max             ) ) );
     197          81 :   ghost->blk_map_gaddr  = fd_wksp_gaddr_fast( wksp, blk_map_join ( blk_map_new  ( blk_map,  blk_chain_cnt, seed ) ) );
     198          81 :   ghost->vtr_pool_gaddr = fd_wksp_gaddr_fast( wksp, vtr_pool_join( vtr_pool_new ( vtr_pool, vtr_max             ) ) );
     199          81 :   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          81 :   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        5433 :   for( ulong i=0UL; i<blk_max; i++ ) {
     210        5352 :     void * dl = vtr_dlist_join( vtr_dlist_new( (uchar *)vtr_dlist + i*vtr_dlist_footprint() ) );
     211        5352 :     blk_pool_ele( bp, i )->vtr_dlist_gaddr = fd_wksp_gaddr_fast( wksp, dl );
     212        5352 :   }
     213             : 
     214          81 :   return shmem;
     215          81 : }
     216             : 
     217             : fd_ghost_t *
     218          81 : fd_ghost_join( void * shghost ) {
     219          81 :   fd_ghost_t * ghost = (fd_ghost_t *)shghost;
     220             : 
     221          81 :   if( FD_UNLIKELY( !ghost ) ) {
     222           0 :     FD_LOG_WARNING(( "NULL ghost" ));
     223           0 :     return NULL;
     224           0 :   }
     225             : 
     226          81 :   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          81 :   return ghost;
     232          81 : }
     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       51642 : fd_ghost_root( fd_ghost_t * ghost ) {
     263       51642 :   return blk_pool_ele( blk_pool( ghost ), ghost->root );
     264       51642 : }
     265             : 
     266             : fd_ghost_blk_t *
     267          12 : fd_ghost_parent( fd_ghost_t * ghost, fd_ghost_blk_t * blk ) {
     268          12 :   return blk_pool_ele( blk_pool( ghost ), blk->parent );
     269          12 : }
     270             : 
     271             : fd_ghost_blk_t *
     272             : fd_ghost_query( fd_ghost_t       * ghost,
     273         924 :                 fd_hash_t  const * block_id ) {
     274         924 :   return blk_map_ele_query( blk_map( ghost ), block_id, NULL, blk_pool( ghost ) );
     275         924 : }
     276             : 
     277             : fd_ghost_blk_t *
     278             : fd_ghost_best( fd_ghost_t     * ghost,
     279         321 :                fd_ghost_blk_t * root ) {
     280         321 :   blk_pool_t *     pool = blk_pool( ghost );
     281         321 :   ulong            null = blk_pool_idx_null( pool );
     282         321 :   fd_ghost_blk_t * best = root;
     283        2868 :   while( FD_LIKELY( best->child != null ) ) {
     284        2553 :     int              valid = 0; /* at least one child is valid */
     285        2553 :     fd_ghost_blk_t * child = blk_pool_ele( pool, best->child );
     286        5127 :     while( FD_LIKELY( child ) ) { /* greedily pick the heaviest valid child */
     287        2574 :       if( FD_LIKELY( child->valid ) ) {
     288        2562 :         if( FD_LIKELY( !valid ) ) { /* this is the first valid child, so progress the head */
     289        2547 :           best  = child;
     290        2547 :           valid = 1;
     291        2547 :         }
     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        2562 :         best = fd_ptr_if(
     301        2562 :           fd_int_if(
     302        2562 :             child->stake == best->stake,   /* if the weights are equal */
     303        2562 :             child->slot  <  best->slot,    /* then tie-break by lower slot number */
     304        2562 :             child->stake >  best->stake ), /* else return heavier */
     305        2562 :           child, best );
     306        2562 :       }
     307        2574 :       child = blk_pool_ele( pool, child->sibling );
     308        2574 :     }
     309        2553 :     if( FD_UNLIKELY( !valid ) ) break; /* no children are valid, so short-circuit traversal */
     310        2553 :   }
     311         321 :   return best;
     312         321 : }
     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       22035 : #define PREDICATE_ANCESTOR( predicate ) do {                          \
     349       22035 :     fd_ghost_blk_t * ancestor = descendant;                           \
     350       48969 :     while( FD_LIKELY( ancestor ) ) {                                  \
     351       48465 :       if( FD_LIKELY( predicate ) ) return ancestor;                   \
     352       48465 :       ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent ); \
     353       26934 :     }                                                                 \
     354       22035 :     return NULL;                                                      \
     355       22035 :   } while(0)
     356             : 
     357             : fd_ghost_blk_t *
     358             : fd_ghost_ancestor( fd_ghost_t      * ghost,
     359             :                    fd_ghost_blk_t  * descendant,
     360         252 :                    fd_hash_t const * ancestor_id ) {
     361         252 :   PREDICATE_ANCESTOR( 0==memcmp( &ancestor->id, ancestor_id, sizeof(fd_hash_t) ) );
     362         252 : }
     363             : 
     364             : fd_ghost_blk_t *
     365             : fd_ghost_slot_ancestor( fd_ghost_t     * ghost,
     366             :                         fd_ghost_blk_t * descendant,
     367       21525 :                         ulong            slot ) {
     368       21525 :   PREDICATE_ANCESTOR( ancestor->slot == slot );
     369       21525 : }
     370             : 
     371             : fd_ghost_blk_t *
     372             : fd_ghost_invalid_ancestor( fd_ghost_t     * ghost,
     373         258 :                            fd_ghost_blk_t * descendant ) {
     374         258 :   PREDICATE_ANCESTOR( !ancestor->valid );
     375         258 : }
     376             : 
     377             : static fd_ghost_blk_t *
     378             : insert( fd_ghost_t      * ghost,
     379             :         ulong             slot,
     380         708 :         fd_hash_t const * block_id ) {
     381         708 :   fd_ghost_blk_t * pool = blk_pool( ghost );
     382         708 :   ulong            null = blk_pool_idx_null( pool );
     383         708 :   fd_ghost_blk_t * blk  = blk_map_ele_query( blk_map( ghost ), block_id, NULL, pool );
     384             : 
     385         708 :   FD_TEST( !blk ); /* duplicate insert */
     386         708 :   FD_TEST( blk_pool_free( pool ) ); /* ghost full */
     387             : 
     388         708 :   blk              = blk_pool_ele_acquire( pool );
     389         708 :   blk->id          = *block_id;
     390         708 :   blk->slot        = slot;
     391         708 :   blk->next        = null;
     392         708 :   blk->parent      = null;
     393         708 :   blk->child       = null;
     394         708 :   blk->sibling     = null;
     395         708 :   blk->stake       = 0;
     396         708 :   blk->total_stake = 0;
     397         708 :   blk->valid       = 1;
     398         708 :   blk_map_ele_insert( blk_map( ghost ), blk, pool );
     399         708 :   return blk;
     400         708 : }
     401             : 
     402             : fd_ghost_blk_t *
     403             : fd_ghost_init( fd_ghost_t      * ghost,
     404             :                ulong             slot,
     405          81 :                fd_hash_t const * block_id ) {
     406          81 :   fd_ghost_blk_t * blk = insert( ghost, slot, block_id );
     407          81 :   ghost->root          = blk_pool_idx( blk_pool( ghost ), blk );
     408          81 :   ghost->width         = 1;
     409          81 :   return blk;
     410          81 : }
     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         627 :                  fd_hash_t const * parent_block_id ) {
     417         627 :   fd_ghost_blk_t * blk    = insert( ghost, slot, block_id );
     418         627 :   fd_ghost_blk_t * pool   = blk_pool( ghost );
     419         627 :   ulong            null   = blk_pool_idx_null( pool );
     420         627 :   fd_ghost_blk_t * parent = blk_map_ele_query( blk_map( ghost ), parent_block_id, NULL, pool );
     421         627 :   FD_TEST( parent ); /* parent must exist be in ghost */
     422         627 :   blk->parent  = blk_pool_idx( pool, parent );
     423         627 :   if( FD_LIKELY( parent->child == null ) ) {
     424         543 :     parent->child = blk_pool_idx( pool, blk );    /* left-child */
     425         543 :   } 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         627 :   return blk;
     433         627 : }
     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       21558 :                      ulong               slot ) {
     441             : 
     442       21558 :   fd_ghost_blk_t const * root = fd_ghost_root( ghost );
     443       21558 :   fd_ghost_vtr_t *       vtr  = vtr_map_ele_query( vtr_map( ghost ), vote_acc, NULL, vtr_pool( ghost ) );
     444             : 
     445       21558 :   if( FD_UNLIKELY( slot==ULONG_MAX  ) ) return FD_GHOST_ERR_NOT_VOTED;
     446       21558 :   if( FD_UNLIKELY( slot< root->slot ) ) return FD_GHOST_ERR_VOTE_TOO_OLD;
     447             : 
     448       21558 :   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        1683 :     vtr       = vtr_pool_ele_acquire( vtr_pool( ghost ) );
     454        1683 :     vtr->addr = *vote_acc;
     455        1683 :     vtr_map_ele_insert( vtr_map( ghost ), vtr, vtr_pool( ghost ) );
     456             : 
     457       19875 :   } 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       19875 :     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       19764 :     fd_ghost_blk_t * prev = blk_map_ele_query( blk_map( ghost ), &vtr->prev_block_id, NULL, blk_pool( ghost ) );
     476       19764 :     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       19764 :     fd_ghost_blk_t * ancestor = prev;
     484      192510 :     while( FD_LIKELY( ancestor ) ) {
     485      172746 :       int cf = __builtin_usubl_overflow( ancestor->stake, vtr->prev_stake, &ancestor->stake );
     486      172746 :       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      172746 :       ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent );
     491      172746 :     }
     492       19764 :   }
     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       21447 :   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       21447 :   fd_ghost_blk_t * ancestor = blk;
     506      215799 :   while( FD_LIKELY( ancestor ) ) {
     507      194352 :     int cf = __builtin_uaddl_overflow( ancestor->stake, stake, &ancestor->stake );
     508      194352 :     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      194352 :     ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent );
     513      194352 :   }
     514       21447 :   vtr->prev_stake    = stake;
     515       21447 :   vtr->prev_slot     = slot;
     516       21447 :   vtr->prev_block_id = blk->id;
     517       21447 :   return FD_GHOST_SUCCESS;
     518       21447 : }
     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          27 :               fd_ghost_blk_t * root ) {
     618          27 :   fd_ghost_blk_t * pool = blk_pool( ghost );
     619          27 :   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          39 :   for(;;) {
     631             : 
     632             :     /* 1. Visit: mark the current curr invalid. */
     633             : 
     634          39 :     curr->valid = 0;
     635             : 
     636             :     /* 2. Descend: if the curr has a child, pivot to it. */
     637             : 
     638          39 :     fd_ghost_blk_t * child = blk_pool_ele( pool, curr->child );
     639          39 :     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          39 :     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          27 :     if( FD_UNLIKELY( curr==root ) ) break;
     654          27 :   }
     655          27 : }
     656             : 
     657             : void
     658             : fd_ghost_confirm( fd_ghost_t      * ghost,
     659           9 :                   fd_hash_t const * confirmed_block_id ) {
     660           9 :   fd_ghost_blk_t * pool = blk_pool( ghost );
     661           9 :   fd_ghost_blk_t * blk  = blk_map_ele_query( blk_map( ghost ), confirmed_block_id, NULL, pool );
     662           9 :   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           9 :   fd_ghost_blk_t * anc = blk;
     668          12 :   while( FD_LIKELY( anc ) ) {
     669          12 :     if( FD_LIKELY( anc->valid ) ) break;
     670           3 :     anc->valid = 1;
     671           3 :     anc = blk_pool_ele( pool, anc->parent );
     672           3 :   }
     673           9 : }
     674             : 
     675             : void
     676             : fd_ghost_eqvoc( fd_ghost_t      * ghost,
     677          27 :                 fd_hash_t const * block_id ) {
     678          27 :   fd_ghost_blk_t * pool = blk_pool( ghost );
     679          27 :   fd_ghost_blk_t * blk  = blk_map_ele_query( blk_map( ghost ), block_id, NULL, pool );
     680          27 :   if( FD_UNLIKELY( !blk ) ) return;
     681          27 :   mark_invalid( ghost, blk );
     682          27 : }
     683             : 
     684             : ulong
     685         294 : fd_ghost_width( fd_ghost_t * ghost ) {
     686         294 :   return ghost->width;
     687         294 : }
     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        2772 :          ulong                  depth ) {
     773        2772 :   if( FD_UNLIKELY( depth>DEPTH_MAX ) ) return;
     774             : 
     775        2772 :   fd_ghost_blk_t const * pool = blk_pool_const( ghost );
     776        2772 :   int n;
     777             : 
     778        2772 :   if( FD_UNLIKELY( ele == NULL ) ) return;
     779             : 
     780        2772 :   if( FD_LIKELY( space > 0 ) && *off < len ) {
     781        2478 :     cstr[(*off)++] = '\n';
     782        2478 :   }
     783             : 
     784       84084 :   for( int i = 0; i < space && *off < len; i++ ) {
     785       81312 :     cstr[(*off)++] = ' ';
     786       81312 :   }
     787             : 
     788        2772 :   if( FD_UNLIKELY( ele->stake > 100 ) ) {
     789        2478 :   }
     790             : 
     791        2772 :   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        2772 :   } else {
     798        2772 :     double pct = ( (double)ele->stake / (double)total_stake ) * 100;
     799        2772 :     if( FD_UNLIKELY( pct < 0.99 ) ) {
     800         294 :       if( *off < len ) {
     801         294 :         n = snprintf( cstr + *off, len - *off, "%s%lu (%.0lf%%, %lu)", prefix, ele->slot, pct, ele->stake );
     802         294 :         if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     803         294 :         *off += (ulong)n;
     804         294 :       }
     805        2478 :     } else {
     806        2478 :       if( *off < len ) {
     807        2478 :         n = snprintf( cstr + *off, len - *off, "%s%lu (%.0lf%%)", prefix, ele->slot, pct );
     808        2478 :         if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     809        2478 :         *off += (ulong)n;
     810        2478 :       }
     811        2478 :     }
     812        2772 :   }
     813             : 
     814        2772 :   fd_ghost_blk_t const * curr = blk_pool_ele_const( pool, ele->child );
     815             : 
     816        5250 :   while( curr ) {
     817        2478 :     char const * next_prefix = blk_pool_ele_const( pool, curr->sibling ) ? "├── " : "└── ";
     818        2478 :     to_cstr( ghost, curr, total_stake, space + 4, next_prefix, cstr, len, off, depth + 1 ); /* TODO remove recursion */
     819        2478 :     curr = blk_pool_ele_const( pool, curr->sibling );
     820        2478 :   }
     821        2772 : }
     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         294 :                   ulong *                cstr_len ) {
     829             : 
     830         294 :   ulong off = 0;
     831             : 
     832         294 :   int n = snprintf( cstr + off, cstr_max - off, "[Ghost]\n\n" );
     833         294 :   if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     834         294 :   off += (ulong)n;
     835             : 
     836         294 :   to_cstr( ghost, root, root->total_stake, 0, "", cstr, cstr_max, &off, 0 );
     837             : 
     838         294 :   if( off < cstr_max ) {
     839         294 :     n = snprintf( cstr + off, cstr_max - off, "\n\n" );
     840         294 :     if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n ));
     841         294 :     off += (ulong)n;
     842         294 :   }
     843             : 
     844         294 :   cstr[fd_ulong_min( off++, cstr_max - 1 )] = '\0';
     845         294 :   *cstr_len = fd_ulong_min( off, cstr_max );
     846         294 :   return cstr;
     847         294 : }

Generated by: LCOV version 1.14