LCOV - code coverage report
Current view: top level - choreo/ghost - fd_ghost.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 440 0.0 %
Date: 2025-01-08 12:08:44 Functions: 0 17 0.0 %

          Line data    Source code
       1             : #include "fd_ghost.h"
       2             : 
       3           0 : static void ver_inc( ulong ** ver ) {
       4           0 :   fd_fseq_update( *ver, fd_fseq_query( *ver ) + 1 );
       5           0 : }
       6             : 
       7           0 : #define VER_INC ulong * ver __attribute__((cleanup(ver_inc))) = fd_ghost_ver( ghost ); ver_inc( &ver )
       8             : 
       9             : void *
      10           0 : fd_ghost_new( void * shmem, ulong seed, ulong node_max ) {
      11             : 
      12           0 :   if( FD_UNLIKELY( !shmem ) ) {
      13           0 :     FD_LOG_WARNING(( "NULL mem" ));
      14           0 :     return NULL;
      15           0 :   }
      16             : 
      17           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_ghost_align() ) ) ) {
      18           0 :     FD_LOG_WARNING(( "misaligned mem" ));
      19           0 :     return NULL;
      20           0 :   }
      21             : 
      22           0 :   ulong footprint = fd_ghost_footprint( node_max );
      23           0 :   if( FD_UNLIKELY( !footprint ) ) {
      24           0 :     FD_LOG_WARNING(( "bad node_max (%lu)", node_max ));
      25           0 :     return NULL;
      26           0 :   }
      27             : 
      28           0 :   fd_wksp_t * wksp = fd_wksp_containing( shmem );
      29           0 :   if( FD_UNLIKELY( !wksp ) ) {
      30           0 :     FD_LOG_WARNING(( "shmem must be part of a workspace" ));
      31           0 :     return NULL;
      32           0 :   }
      33             : 
      34           0 :   fd_memset( shmem, 0, footprint );
      35             : 
      36           0 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
      37           0 :   fd_ghost_t * ghost = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_align(), sizeof(fd_ghost_t) );
      38           0 :   void * ver         = FD_SCRATCH_ALLOC_APPEND( l, fd_fseq_align(),  fd_fseq_footprint() );
      39           0 :   void * node_pool   = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_node_pool_align(), fd_ghost_node_pool_footprint( node_max ) );
      40           0 :   void * node_map    = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_node_map_align(),  fd_ghost_node_map_footprint( node_max ) );
      41           0 :   FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_ghost_align() ) == (ulong)shmem + footprint );
      42             : 
      43           0 :   ghost->ver_gaddr       = fd_wksp_gaddr_fast( wksp, fd_fseq_join( fd_fseq_new( ver, ULONG_MAX ) ) );
      44           0 :   ghost->node_pool_gaddr = fd_wksp_gaddr_fast( wksp, fd_ghost_node_pool_join(fd_ghost_node_pool_new( node_pool, node_max ) ));
      45           0 :   ghost->node_map_gaddr  = fd_wksp_gaddr_fast( wksp, fd_ghost_node_map_join(fd_ghost_node_map_new( node_map, node_max, seed ) ));
      46             : 
      47           0 :   ghost->ghost_gaddr = fd_wksp_gaddr_fast( wksp, ghost );
      48           0 :   ghost->seed        = seed;
      49           0 :   ghost->root_idx    = fd_ghost_node_pool_idx_null( fd_ghost_node_pool( ghost ) );
      50             : 
      51           0 :   FD_COMPILER_MFENCE();
      52           0 :   FD_VOLATILE( ghost->magic ) = FD_GHOST_MAGIC;
      53           0 :   FD_COMPILER_MFENCE();
      54             : 
      55           0 :   return shmem;
      56           0 : }
      57             : 
      58             : fd_ghost_t *
      59           0 : fd_ghost_join( void * shghost ) {
      60           0 :   fd_ghost_t * ghost = (fd_ghost_t *)shghost;
      61             : 
      62           0 :   if( FD_UNLIKELY( !ghost ) ) {
      63           0 :     FD_LOG_WARNING(( "NULL ghost" ));
      64           0 :     return NULL;
      65           0 :   }
      66             : 
      67           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)ghost, fd_ghost_align() ) ) ) {
      68           0 :     FD_LOG_WARNING(( "misaligned ghost" ));
      69           0 :     return NULL;
      70           0 :   }
      71             : 
      72           0 :   fd_wksp_t * wksp = fd_wksp_containing( ghost );
      73           0 :   if( FD_UNLIKELY( !wksp ) ) {
      74           0 :     FD_LOG_WARNING(( "ghost must be part of a workspace" ));
      75           0 :     return NULL;
      76           0 :   }
      77             : 
      78           0 :   if( FD_UNLIKELY( ghost->magic!=FD_GHOST_MAGIC ) ) {
      79           0 :     FD_LOG_WARNING(( "bad magic" ));
      80           0 :     return NULL;
      81           0 :   }
      82             : 
      83           0 :   return ghost;
      84           0 : }
      85             : 
      86             : void *
      87           0 : fd_ghost_leave( fd_ghost_t const * ghost ) {
      88             : 
      89           0 :   if( FD_UNLIKELY( !ghost ) ) {
      90           0 :     FD_LOG_WARNING(( "NULL ghost" ));
      91           0 :     return NULL;
      92           0 :   }
      93             : 
      94           0 :   return (void *)ghost;
      95           0 : }
      96             : 
      97             : void *
      98           0 : fd_ghost_delete( void * ghost ) {
      99             : 
     100           0 :   if( FD_UNLIKELY( !ghost ) ) {
     101           0 :     FD_LOG_WARNING(( "NULL ghost" ));
     102           0 :     return NULL;
     103           0 :   }
     104             : 
     105           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)ghost, fd_ghost_align() ) ) ) {
     106           0 :     FD_LOG_WARNING(( "misaligned ghost" ));
     107           0 :     return NULL;
     108           0 :   }
     109             :   
     110             :   // TODO: zero out mem?
     111             : 
     112           0 :   return ghost;
     113           0 : }
     114             : 
     115             : void
     116           0 : fd_ghost_init( fd_ghost_t * ghost, ulong root ) {
     117             : 
     118           0 :   if( FD_UNLIKELY( !ghost ) ) {
     119           0 :     FD_LOG_WARNING(( "NULL ghost" ));
     120           0 :     return;
     121           0 :   }
     122             : 
     123           0 :   if( FD_UNLIKELY( root == FD_SLOT_NULL ) ) {
     124           0 :     FD_LOG_WARNING(( "NULL root" ));
     125           0 :     return;
     126           0 :   }
     127             : 
     128           0 :   if( FD_UNLIKELY( fd_fseq_query( fd_ghost_ver( ghost ) ) != ULONG_MAX ) ) {
     129           0 :     FD_LOG_WARNING(( "ghost already initalized" ));
     130           0 :     return;
     131           0 :   }
     132             : 
     133           0 :   fd_ghost_node_t * node_pool    = fd_ghost_node_pool( ghost );
     134           0 :   fd_ghost_node_map_t * node_map = fd_ghost_node_map( ghost );
     135           0 :   ulong null_idx                 = fd_ghost_node_pool_idx_null( node_pool );
     136             : 
     137           0 :   if( FD_UNLIKELY( ghost->root_idx != null_idx ) ) {
     138           0 :     FD_LOG_WARNING(( "ghost already initialized" ));
     139           0 :     return;
     140           0 :   }
     141             : 
     142             :   /* Initialize the root node from a pool element. */
     143             : 
     144           0 :   fd_ghost_node_t * root_ele = fd_ghost_node_pool_ele_acquire( node_pool );
     145           0 :   memset( root_ele, 0, sizeof( fd_ghost_node_t ) );
     146           0 :   root_ele->slot        = root;
     147           0 :   root_ele->next        = null_idx;
     148           0 :   root_ele->valid       = 1;
     149           0 :   root_ele->parent_idx  = null_idx;
     150           0 :   root_ele->child_idx   = null_idx;
     151           0 :   root_ele->sibling_idx = null_idx;
     152             : 
     153             :   /* Insert the root and record the root ele's pool idx. */
     154             : 
     155           0 :   fd_ghost_node_map_ele_insert( node_map, root_ele, node_pool ); /* cannot fail */
     156           0 :   ghost->root_idx = fd_ghost_node_map_idx_query( node_map, &root, null_idx, node_pool );
     157             : 
     158             :   /* Sanity checks. */
     159             : 
     160           0 :   FD_TEST( fd_ghost_root( ghost ) );
     161           0 :   FD_TEST( fd_ghost_root( ghost ) == fd_ghost_query( ghost, root ));
     162           0 :   FD_TEST( fd_ghost_root( ghost )->slot == root );
     163             : 
     164           0 :   fd_fseq_update( fd_ghost_ver( ghost ), 0 );
     165           0 :   return;
     166           0 : }
     167             : 
     168             : int
     169           0 : fd_ghost_verify( fd_ghost_t const * ghost ) {
     170           0 :   if( FD_UNLIKELY( !ghost ) ) {
     171           0 :     FD_LOG_WARNING(( "NULL ghost" ));
     172           0 :     return -1;
     173           0 :   }
     174             : 
     175           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)ghost, fd_ghost_align() ) ) ) {
     176           0 :     FD_LOG_WARNING(( "misaligned ghost" ));
     177           0 :     return -1;
     178           0 :   }
     179             : 
     180           0 :   fd_wksp_t * wksp = fd_wksp_containing( ghost );
     181           0 :   if( FD_UNLIKELY( !wksp ) ) {
     182           0 :     FD_LOG_WARNING(( "ghost must be part of a workspace" ));
     183           0 :     return -1;
     184           0 :   }
     185             : 
     186           0 :   if( FD_UNLIKELY( ghost->magic!=FD_GHOST_MAGIC ) ) {
     187           0 :     FD_LOG_WARNING(( "bad magic" ));
     188           0 :     return -1;
     189           0 :   }
     190             : 
     191           0 :   if( FD_UNLIKELY( fd_fseq_query( fd_ghost_ver( ghost ) ) == ULONG_MAX ) ) {
     192           0 :     FD_LOG_WARNING(( "ghost uninitalized or invalid" ));
     193           0 :     return -1;
     194           0 :   }
     195             : 
     196           0 :   fd_ghost_node_t const *     node_pool = fd_ghost_node_pool_const( ghost );
     197           0 :   fd_ghost_node_map_t const * node_map  = fd_ghost_node_map_const( ghost );
     198             : 
     199             :   /* every element that exists in pool exists in map  */
     200             : 
     201           0 :   if( fd_ghost_node_map_verify( node_map, fd_ghost_node_pool_max( node_pool ), node_pool ) ) return -1;
     202             : 
     203             :   /* every node's weight is >= sum of children's weights */
     204             : 
     205           0 :   fd_ghost_node_t const * parent = fd_ghost_root( ghost );
     206           0 :   while( parent ) {
     207           0 :     ulong child_idx    = parent->child_idx;
     208           0 :     ulong children_weight = 0;
     209           0 :     while( child_idx != fd_ghost_node_pool_idx_null( node_pool ) ) {
     210           0 :       fd_ghost_node_t const * child = fd_ghost_node_pool_ele_const( node_pool, child_idx );
     211           0 :       children_weight += child->weight;
     212           0 :       child_idx = child->sibling_idx;
     213           0 :     }
     214           0 :     if( FD_UNLIKELY( parent->weight < children_weight ) ) {
     215           0 :       FD_LOG_WARNING(( "[%s] invariant violation. %lu's weight: %lu < children's weight: %lu", __func__, parent->slot, parent->weight, children_weight ));
     216           0 :       return -1;
     217           0 :     }
     218           0 :     parent = fd_ghost_node_pool_ele_const( node_pool, parent->next );
     219           0 :   }
     220             : 
     221           0 :   return 0;
     222           0 : }
     223             : 
     224             : fd_ghost_node_t *
     225           0 : fd_ghost_insert( fd_ghost_t * ghost, ulong parent_slot, ulong slot ) {
     226           0 :   VER_INC;
     227             : 
     228           0 :   FD_LOG_DEBUG(( "[%s] slot: %lu. parent: %lu.", __func__, slot, parent_slot ));
     229             : 
     230           0 :   #if FD_GHOST_USE_HANDHOLDING
     231           0 :   FD_TEST( ghost->magic == FD_GHOST_MAGIC );
     232           0 :   #endif
     233             : 
     234           0 :   fd_ghost_node_map_t * node_map = fd_ghost_node_map( ghost );
     235           0 :   fd_ghost_node_t * node_pool    = fd_ghost_node_pool( ghost );
     236             : 
     237           0 :   ulong null_idx               = fd_ghost_node_pool_idx_null( node_pool );
     238           0 :   fd_ghost_node_t * parent_ele = fd_ghost_node_map_ele_query( node_map, &parent_slot, NULL, node_pool );
     239           0 :   ulong             parent_idx = fd_ghost_node_pool_idx( node_pool, parent_ele );
     240           0 :   fd_ghost_node_t const * root = fd_ghost_root( ghost );
     241             : 
     242           0 :   #if FD_GHOST_USE_HANDHOLDING
     243           0 :   if( FD_UNLIKELY( fd_ghost_query( ghost, slot ) ) ) {  /* slot already in ghost */
     244           0 :     FD_LOG_WARNING(( "[%s] slot %lu already in ghost.", __func__, slot ));
     245           0 :     return NULL;
     246           0 :   }
     247             : 
     248           0 :   if( FD_UNLIKELY( !parent_ele ) ) { /* parent_slot not in ghost */
     249           0 :     FD_LOG_WARNING(( "[%s] missing `parent_slot` %lu.", __func__, parent_slot ));
     250           0 :     return NULL;
     251           0 :   } 
     252             : 
     253           0 :   if( FD_UNLIKELY( !fd_ghost_node_pool_free( node_pool ) ) ) { /* ghost full */
     254           0 :     FD_LOG_WARNING(( "[%s] ghost full.", __func__ ));
     255           0 :     return NULL;
     256           0 :   }
     257             : 
     258           0 :   if( FD_UNLIKELY( slot <= root->slot ) ) { /* slot must > root */
     259           0 :     FD_LOG_WARNING(( "[%s] slot %lu <= root %lu", __func__, slot, root->slot )); 
     260           0 :     return NULL;
     261           0 :   }
     262           0 :   #endif
     263             : 
     264           0 :   fd_ghost_node_t * node_ele = fd_ghost_node_pool_ele_acquire( node_pool );
     265           0 :   ulong             node_idx = fd_ghost_node_pool_idx( node_pool, node_ele );
     266             : 
     267           0 :   memset( node_ele, 0, sizeof(fd_ghost_node_t) );
     268           0 :   node_ele->slot        = slot;
     269           0 :   node_ele->next        = null_idx;
     270           0 :   node_ele->valid       = 1;
     271           0 :   node_ele->parent_idx  = null_idx;
     272           0 :   node_ele->child_idx   = null_idx;
     273           0 :   node_ele->sibling_idx = null_idx;
     274             : 
     275             :   /* Insert into the map for O(1) random access. */
     276             : 
     277           0 :   fd_ghost_node_map_ele_insert( node_map, node_ele, node_pool ); /* cannot fail */
     278             : 
     279             :   /* Link node->parent. */
     280             : 
     281           0 :   node_ele->parent_idx = parent_idx;
     282             : 
     283             :   /* Link parent->node and sibling->node. */
     284             : 
     285           0 :   if( FD_LIKELY( parent_ele->child_idx == null_idx ) ) {
     286             :   
     287             :     /* No children yet so set as left-most child. */
     288             : 
     289           0 :     parent_ele->child_idx = node_idx;
     290             : 
     291           0 :   } else {
     292             : 
     293             :     /* Already have children so iterate to right-most sibling. */
     294             : 
     295           0 :     fd_ghost_node_t * curr = fd_ghost_node_pool_ele( node_pool, parent_ele->child_idx );
     296           0 :     while( curr->sibling_idx != null_idx ) {
     297           0 :       curr = fd_ghost_node_pool_ele( node_pool, curr->sibling_idx );
     298           0 :     }
     299             : 
     300             :     /* Link to right-most sibling. */
     301             : 
     302           0 :     curr->sibling_idx = node_idx;
     303           0 :   }
     304             : 
     305             :   /* Return newly-created node. */
     306             : 
     307           0 :   return node_ele;
     308           0 : }
     309             : 
     310             : fd_ghost_node_t const *
     311           0 : fd_ghost_head( fd_ghost_t const * ghost, fd_ghost_node_t const * node ) {
     312           0 :   #if FD_GHOST_USE_HANDHOLDING
     313           0 :   FD_TEST( ghost->magic == FD_GHOST_MAGIC );
     314           0 :   FD_TEST( node );
     315           0 :   #endif
     316             : 
     317           0 :   fd_ghost_node_t const * node_pool  = fd_ghost_node_pool_const( ghost );
     318           0 :   fd_ghost_node_t const * head = node;
     319           0 :   ulong null_idx               = fd_ghost_node_pool_idx_null( node_pool );
     320             : 
     321           0 :   while( head->child_idx != null_idx ) {
     322           0 :     head                         = fd_ghost_node_pool_ele_const( node_pool, head->child_idx );
     323           0 :     fd_ghost_node_t const * curr = head;
     324           0 :     while( curr ) {
     325             : 
     326           0 :       head = fd_ptr_if(
     327           0 :         fd_int_if(
     328             :           /* if the weights are equal... */
     329             : 
     330           0 :           curr->weight == head->weight,
     331             : 
     332             :           /* ...tie-break by slot number */
     333             : 
     334           0 :           curr->slot < head->slot,
     335             : 
     336             :           /* otherwise return curr if curr > head */
     337             : 
     338           0 :           curr->weight > head->weight ),
     339           0 :         curr, head );
     340             : 
     341           0 :       curr = fd_ghost_node_pool_ele_const( node_pool, curr->sibling_idx );
     342           0 :     }
     343           0 :   }
     344           0 :   return head;
     345           0 : }
     346             : 
     347             : void
     348           0 : fd_ghost_replay_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong slot ) {
     349           0 :   VER_INC;
     350             : 
     351           0 :   FD_LOG_DEBUG(( "[%s] slot %lu, pubkey %s, stake %lu", __func__, slot, FD_BASE58_ENC_32_ALLOCA( &voter->key ), voter->stake ));
     352             : 
     353           0 :   fd_ghost_node_map_t *   node_map  = fd_ghost_node_map( ghost );
     354           0 :   fd_ghost_node_t *       node_pool = fd_ghost_node_pool( ghost );
     355           0 :   ulong                   vote      = voter->replay_vote;
     356           0 :   fd_ghost_node_t const * root      = fd_ghost_root( ghost );
     357             : 
     358           0 :   #if FD_GHOST_USE_HANDHOLDING
     359           0 :   if( FD_UNLIKELY( slot < root->slot ) ) FD_LOG_ERR(( "[%s] illegal argument. vote slot: %lu, root: %lu", __func__, slot, root->slot ));
     360           0 :   #endif
     361             : 
     362             :   /* Return early if the vote slot <= the voter's last tower vote. It is
     363             :      important that the vote slots are monotonically increasing, because
     364             :      the order we receive blocks is non-deterministic (due to network
     365             :      propagation variance), so we may process forks in a different order
     366             :      from the sender of this vote.
     367             : 
     368             :      For example, if a validator votes on A then switches to B, we might
     369             :      instead process B then A. In this case, the validator's tower on B
     370             :      would contain a strictly higher vote slot than A (due to lockout),
     371             :      so we would observe while processing A, that the vote slot < the
     372             :      last vote slot we have saved for that validator. */
     373             :   
     374           0 :   if( FD_UNLIKELY( vote != FD_SLOT_NULL && slot <= vote ) ) return;
     375             : 
     376             :   /* LMD-rule: subtract the voter's stake from previous vote. */
     377             : 
     378           0 :   if( FD_LIKELY( vote != FD_SLOT_NULL && vote >= root->slot ) ) {
     379           0 :     FD_LOG_DEBUG(( "[%s] removing (%s, %lu, %lu)", __func__, FD_BASE58_ENC_32_ALLOCA( &voter->key ), voter->stake, vote ));
     380             : 
     381           0 :     fd_ghost_node_t * node = fd_ghost_node_map_ele_query( node_map, &vote, NULL, node_pool );
     382           0 :     #if FD_GHOST_USE_HANDHOLDING
     383           0 :     if( FD_UNLIKELY( !node ) ) FD_LOG_ERR(( "missing ghost node" )); /* slot must be in ghost. */
     384           0 :     #endif
     385             : 
     386           0 :     #if FD_GHOST_USE_HANDHOLDING
     387           0 :     int cf = __builtin_usubl_overflow( node->replay_stake, voter->stake, &node->replay_stake );
     388           0 :     if( FD_UNLIKELY( cf ) ) FD_LOG_ERR(( "[%s] sub overflow. node stake %lu voter stake %lu", __func__, node->replay_stake, voter->stake ));
     389             :     #else
     390             :     node->replay_stake -= voter->stake;
     391             :     #endif
     392             : 
     393           0 :     fd_ghost_node_t * ancestor = node;
     394           0 :     while( ancestor ) {
     395           0 :       cf = __builtin_usubl_overflow( ancestor->weight, voter->stake, &ancestor->weight );
     396           0 :       #if FD_GHOST_USE_HANDHOLDING
     397           0 :       if( FD_UNLIKELY( cf ) ) FD_LOG_ERR(( "[%s] sub overflow. ancestor->weight %lu latest_vote->stake %lu", __func__, ancestor->weight, voter->stake ));
     398             :       #else
     399             :       ancestor_weight -= voter->stake;
     400             :       #endif
     401           0 :       ancestor = fd_ghost_node_pool_ele( node_pool, ancestor->parent_idx );
     402           0 :     }
     403           0 :   }
     404             : 
     405             :   /* Add voter's stake to the ghost node keyed by `slot`.  Propagate the
     406             :      vote stake up the ancestry. */
     407             : 
     408           0 :   FD_LOG_DEBUG(( "[%s] adding (%s, %lu, %lu)", __func__, FD_BASE58_ENC_32_ALLOCA( &voter->key ), voter->stake, slot ));
     409             : 
     410           0 :   fd_ghost_node_t * node = fd_ghost_node_map_ele_query( node_map, &slot, NULL, node_pool );
     411           0 :   #if FD_GHOST_USE_HANDHOLDING
     412           0 :   if( FD_UNLIKELY( !node ) ) FD_LOG_ERR(( "missing ghost node" )); /* slot must be in ghost. */
     413           0 :   #endif
     414             : 
     415           0 :   #if FD_GHOST_USE_HANDHOLDING
     416           0 :   int cf = __builtin_uaddl_overflow( node->replay_stake, voter->stake, &node->replay_stake );
     417           0 :   if( FD_UNLIKELY( cf ) ) FD_LOG_ERR(( "[%s] add overflow. node->stake %lu latest_vote->stake %lu", __func__, node->replay_stake, voter->stake ));
     418             :   #else
     419             :   node->replay_stake += voter->stake;
     420             :   #endif
     421             : 
     422           0 :   fd_ghost_node_t * ancestor = node;
     423           0 :   while( ancestor ) {
     424           0 :     #if FD_GHOST_USE_HANDHOLDING
     425           0 :     int cf = __builtin_uaddl_overflow( ancestor->weight, voter->stake, &ancestor->weight );
     426           0 :     if( FD_UNLIKELY( cf ) ) FD_LOG_ERR(( "[%s] add overflow. ancestor->weight %lu latest_vote->stake %lu", __func__, ancestor->weight, voter->stake ));
     427             :     #else
     428             :     ancestor_weight += voter->stake;
     429             :     #endif
     430           0 :     ancestor = fd_ghost_node_pool_ele( node_pool, ancestor->parent_idx );
     431           0 :   }
     432             : 
     433           0 :   voter->replay_vote = slot; /* update the cached replay vote slot on voter */
     434           0 : }
     435             : 
     436             : void
     437             : fd_ghost_gossip_vote( FD_PARAM_UNUSED fd_ghost_t * ghost,
     438             :                       FD_PARAM_UNUSED fd_voter_t * voter,
     439           0 :                       FD_PARAM_UNUSED ulong        slot ) {
     440           0 :   FD_LOG_ERR(( "unimplemented" ));
     441           0 : }
     442             : 
     443             : void
     444           0 : fd_ghost_rooted_vote( fd_ghost_t * ghost, fd_voter_t * voter, ulong root ) {
     445           0 :   VER_INC;
     446             : 
     447           0 :   FD_LOG_DEBUG(( "[%s] root %lu, pubkey %s, stake %lu", __func__, root, FD_BASE58_ENC_32_ALLOCA(&voter->key), voter->stake ));
     448             : 
     449           0 :   fd_ghost_node_map_t * node_map    = fd_ghost_node_map( ghost );
     450           0 :   fd_ghost_node_t * node_pool       = fd_ghost_node_pool( ghost );
     451           0 :   fd_ghost_node_t const * root_node = fd_ghost_root( ghost );
     452             : 
     453           0 :   #if FD_GHOST_USE_HANDHOLDING
     454           0 :   if( FD_UNLIKELY( root < root_node->slot ) ) {
     455           0 :     FD_LOG_ERR(( "caller must only insert vote slots >= ghost root. vote: %lu, root: %lu", root, root_node->slot ));
     456           0 :   }
     457           0 :   #endif
     458             : 
     459             :   /* It is invariant that the voter's root is found in ghost (as long as
     460             :      voter's root >= our root ). This is because voter's root is sourced
     461             :      from their vote state, so it must be on the fork we're replaying
     462             :      and we must have already inserted their root slot into ghost. */
     463             : 
     464           0 :   fd_ghost_node_t * node = fd_ghost_node_map_ele_query( node_map, &root, NULL, node_pool );
     465           0 :   #if FD_GHOST_USE_HANDHOLDING
     466           0 :   if( FD_UNLIKELY( !node ) ) FD_LOG_ERR(( "[%s] invariant violation. missing voter %s's root %lu.", __func__, FD_BASE58_ENC_32_ALLOCA(&voter->key), root ));
     467           0 :   #endif
     468             : 
     469             :   /* Add to the rooted stake. */
     470             : 
     471           0 :   node->rooted_stake += voter->stake;
     472           0 : }
     473             : 
     474             : fd_ghost_node_t const *
     475           0 : fd_ghost_publish( fd_ghost_t * ghost, ulong slot ) {
     476           0 :   FD_LOG_NOTICE(( "[%s] slot %lu", __func__, slot ));
     477           0 :   VER_INC;
     478             : 
     479           0 :   fd_ghost_node_map_t * node_map    = fd_ghost_node_map( ghost );
     480           0 :   fd_ghost_node_t * node_pool       = fd_ghost_node_pool( ghost );
     481           0 :   fd_ghost_node_t const * root_node = fd_ghost_root( ghost );
     482           0 :   ulong null_idx                    = fd_ghost_node_pool_idx_null( node_pool );
     483             : 
     484           0 : #if FD_GHOST_USE_HANDHOLDING
     485           0 :   if( FD_UNLIKELY( slot < root_node->slot ) ) {
     486           0 :     FD_LOG_WARNING(( "[fd_ghost_publish] trying to publish slot %lu older than ghost->root %lu.",
     487           0 :                       slot,
     488           0 :                       root_node->slot ));
     489           0 :     return NULL;
     490           0 :   }
     491           0 :   if( FD_UNLIKELY( slot == root_node->slot ) ) {
     492           0 :     FD_LOG_WARNING(( "[fd_ghost_publish] publishing same slot %lu as ghost->root %lu.",
     493           0 :                       slot,
     494           0 :                       root_node->slot ));
     495           0 :     return NULL;
     496           0 :   }
     497           0 : #endif
     498             : 
     499             :   // new root
     500           0 :   fd_ghost_node_t * root = fd_ghost_node_map_ele_query( node_map,
     501           0 :                                                         &slot,
     502           0 :                                                         NULL,
     503           0 :                                                         node_pool );
     504             : 
     505           0 : #if FD_GHOST_USE_HANDHOLDING
     506           0 :   if( FD_UNLIKELY( !root ) ) {
     507           0 :     FD_LOG_ERR(( "[fd_ghost_publish] publish slot %lu not found in ghost", slot ));
     508           0 :   }
     509             : 
     510           0 : #endif
     511             : 
     512             :   /* First, remove the previous root, and add it to the prune list.
     513             : 
     514             :      In this context, head is the list head (not to be confused with the
     515             :      ghost head.) */
     516             : 
     517           0 :   fd_ghost_node_t * head = fd_ghost_node_map_ele_remove( node_map,
     518           0 :                                                          &root_node->slot,
     519           0 :                                                          NULL,
     520           0 :                                                          node_pool );
     521           0 :   head->next             = fd_ghost_node_pool_idx_null( node_pool );
     522           0 :   fd_ghost_node_t * tail = head;
     523             : 
     524             :   /* Second, BFS down the tree, adding nodes to the prune queue except
     525             :      for the new root.
     526             : 
     527             :      Loop invariant: the old root must be in new root's ancestry. */
     528             : 
     529           0 :   while( head ) {
     530           0 :     fd_ghost_node_t * child = fd_ghost_node_pool_ele( node_pool, head->child_idx );
     531             : 
     532           0 :     while( FD_LIKELY( child ) ) {
     533             : 
     534             :       /* Do not prune the new root. */
     535             : 
     536           0 :       if( FD_LIKELY( child != root ) ) {
     537             : 
     538             :         /* Remove the child from the map and push the child onto the list. */
     539             : 
     540           0 :         tail->next = fd_ghost_node_map_idx_remove( node_map,
     541           0 :                                                    &child->slot,
     542           0 :                                                    fd_ghost_node_pool_idx_null( node_pool ),
     543           0 :                                                    node_pool );
     544           0 : #if FD_GHOST_USE_HANDHOLDING
     545           0 :         if( FD_UNLIKELY( tail->next == fd_ghost_node_pool_idx_null( node_pool ) ) ) {
     546           0 :           FD_LOG_ERR(( "Failed to remove child. Child must exist given the while condition. "
     547           0 :                        "Possible memory corruption or data race." ));
     548           0 :         }
     549           0 : #endif
     550           0 :         tail       = fd_ghost_node_pool_ele( node_pool, tail->next );
     551           0 :         tail->next = fd_ghost_node_pool_idx_null( node_pool );
     552           0 :       }
     553             : 
     554           0 :       child = fd_ghost_node_pool_ele( node_pool, child->sibling_idx );
     555           0 :     }
     556             : 
     557             :     /* Free the head, and move the head pointer forward. */
     558             : 
     559           0 :     fd_ghost_node_t * next = fd_ghost_node_pool_ele( node_pool, head->next );
     560           0 :     fd_ghost_node_pool_ele_release( node_pool, head );
     561           0 :     head = next;
     562           0 :   }
     563             : 
     564             :   /* Unlink the root and set the new root. */
     565             : 
     566           0 :   root->parent_idx = null_idx;
     567           0 :   ghost->root_idx  = fd_ghost_node_map_idx_query( node_map, &slot, null_idx, node_pool );
     568             : 
     569           0 :   return root;
     570           0 : }
     571             : 
     572             : fd_ghost_node_t const *
     573           0 : fd_ghost_gca( fd_ghost_t const * ghost, ulong slot1, ulong slot2 ) {
     574           0 :   fd_ghost_node_t const * node_pool = fd_ghost_node_pool_const( ghost );
     575           0 :   fd_ghost_node_t const * node1     = fd_ghost_query( ghost, slot1 );
     576           0 :   fd_ghost_node_t const * node2     = fd_ghost_query( ghost, slot2 );
     577             : 
     578           0 : #if FD_GHOST_USE_HANDHOLDING
     579           0 :   if( FD_UNLIKELY( !node1 ) ) {
     580           0 :     FD_LOG_WARNING(( "slot1 %lu is missing from ghost", slot1 ));
     581           0 :     return NULL;
     582           0 :   }
     583             : 
     584           0 :   if( FD_UNLIKELY( !node2 ) ) {
     585           0 :     FD_LOG_WARNING(( "slot2 %lu is missing from ghost", slot2 ));
     586           0 :     return NULL;
     587           0 :   }
     588           0 : #endif
     589             : 
     590             :   /* Find the greatest common ancestor. */
     591             : 
     592           0 :   while( node1 && node2 ) {
     593           0 :     if( FD_UNLIKELY( node1->slot == node2->slot ) ) return node1;
     594           0 :     if( node1->slot > node2->slot ) {
     595           0 :       node1 = fd_ghost_node_pool_ele_const( node_pool, node1->parent_idx );
     596           0 :     } else {
     597           0 :       node2 = fd_ghost_node_pool_ele_const( node_pool, node2->parent_idx );
     598           0 :     }
     599           0 :   }
     600             : 
     601           0 :   FD_LOG_ERR(( "Unable to find GCA. Is this a valid ghost?" ));
     602           0 : }
     603             : 
     604             : int
     605           0 : fd_ghost_is_ancestor( fd_ghost_t const * ghost, ulong ancestor, ulong slot ) {
     606           0 :   fd_ghost_node_t const * root = fd_ghost_root( ghost );
     607           0 :   fd_ghost_node_t const * curr = fd_ghost_query( ghost, slot );
     608             : 
     609           0 :   #if FD_GHOST_USE_HANDHOLDING
     610           0 :   if( FD_UNLIKELY( ancestor < root->slot ) ) {
     611           0 :     FD_LOG_WARNING(( "[%s] ancestor %lu too old. root %lu.", __func__, ancestor, root->slot ));
     612           0 :     return 0;
     613           0 :   }
     614             : 
     615           0 :   if( FD_UNLIKELY( !curr ) ) {
     616           0 :     FD_LOG_WARNING(( "[%s] slot %lu not in ghost.", __func__, slot ));
     617           0 :     return 0;
     618           0 :   }
     619           0 :   #endif
     620             : 
     621             :   /* Look for `ancestor` in the fork ancestry.
     622             : 
     623             :      Stop looking when there is either no ancestry remaining or there is
     624             :      no reason to look further because we've searched past the
     625             :      `ancestor`. */
     626             : 
     627           0 :   while( FD_LIKELY( curr && curr->slot >= ancestor ) ) {
     628           0 :     if( FD_UNLIKELY( curr->slot == ancestor ) ) return 1; /* optimize for depth > 1 */
     629           0 :     curr = fd_ghost_node_pool_ele_const( fd_ghost_node_pool_const( ghost ), curr->parent_idx );
     630           0 :   }
     631           0 :   return 0; /* not found */
     632           0 : }
     633             : 
     634             : #include <stdio.h>
     635             : 
     636             : static void
     637           0 : print( fd_ghost_t const * ghost, fd_ghost_node_t const * node, int space, const char * prefix, ulong total ) {
     638           0 :   fd_ghost_node_t const * node_pool = fd_ghost_node_pool_const( ghost );
     639             : 
     640           0 :   if( node == NULL ) return;
     641             : 
     642           0 :   if( space > 0 ) printf( "\n" );
     643           0 :   for( int i = 0; i < space; i++ )
     644           0 :     printf( " " );
     645           0 :   if( FD_UNLIKELY( node->weight > 100 ) ) {
     646           0 :   }
     647           0 :   if( FD_UNLIKELY( total == 0 ) ) {
     648           0 :     printf( "%s%lu (%lu)", prefix, node->slot, node->weight );
     649           0 :   } else {
     650           0 :     double pct = ( (double)node->weight / (double)total ) * 100;
     651           0 :     if( FD_UNLIKELY( pct < 0.99 )) {
     652           0 :       printf( "%s%lu (%.0lf%%, %lu)", prefix, node->slot, pct, node->weight );
     653           0 :     } else {
     654           0 :       printf( "%s%lu (%.0lf%%)", prefix, node->slot, pct );
     655           0 :     }
     656           0 :   }
     657             : 
     658           0 :   fd_ghost_node_t const * curr = fd_ghost_node_pool_ele_const( node_pool, node->child_idx );
     659           0 :   char                    new_prefix[1024]; /* FIXME size this correctly */
     660           0 :   while( curr ) {
     661           0 :     if( fd_ghost_node_pool_ele_const( node_pool, curr->sibling_idx ) ) {
     662           0 :       sprintf( new_prefix, "├── " ); /* branch indicating more siblings follow */
     663           0 :       print( ghost, curr, space + 4, new_prefix, total );
     664           0 :     } else {
     665           0 :       sprintf( new_prefix, "└── " ); /* end branch */
     666           0 :       print( ghost, curr, space + 4, new_prefix, total );
     667           0 :     }
     668           0 :     curr = fd_ghost_node_pool_ele_const( node_pool, curr->sibling_idx );
     669           0 :   }
     670           0 : }
     671             : 
     672             : void
     673           0 : fd_ghost_print( fd_ghost_t const * ghost, fd_epoch_t const * epoch, fd_ghost_node_t const * node ) {
     674           0 :   FD_LOG_NOTICE( ( "\n\n[Ghost]" ) );
     675           0 :   print( ghost, node, 0, "", epoch->total_stake );
     676           0 :   printf( "\n\n" );
     677           0 : }

Generated by: LCOV version 1.14