LCOV - code coverage report
Current view: top level - discof/reasm - fd_reasm.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 618 752 82.2 %
Date: 2026-06-29 05:51:35 Functions: 33 40 82.5 %

          Line data    Source code
       1             : #include "fd_reasm.h"
       2             : #include "fd_reasm_private.h"
       3             : #include "../../disco/shred/fd_fec_set.h"
       4             : 
       5             : #define LOGGING 0
       6             : 
       7             : FD_FN_CONST ulong
       8         555 : fd_reasm_align( void ) {
       9         555 :   return alignof(fd_reasm_t);
      10         555 : }
      11             : 
      12             : FD_FN_CONST ulong
      13         126 : fd_reasm_footprint( ulong fec_max ) {
      14         126 :   ulong max_slots = fd_ulong_max(fec_max / FD_FEC_BLK_MAX, 1UL);                  /* add capacity for a block id per slot */
      15         126 :   ulong chain_cnt = ancestry_chain_cnt_est( fec_max );                            /* estimated buckets for ancestry map */
      16         126 :   int  lgf_max    = fd_ulong_find_msb( fd_ulong_pow2_up( fec_max + max_slots ) ); /* capacity for fec_max fecs + (fec_max / 1024) more block ids */
      17         126 :   return FD_LAYOUT_FINI(
      18         126 :     FD_LAYOUT_APPEND(
      19         126 :     FD_LAYOUT_APPEND(
      20         126 :     FD_LAYOUT_APPEND(
      21         126 :     FD_LAYOUT_APPEND(
      22         126 :     FD_LAYOUT_APPEND(
      23         126 :     FD_LAYOUT_APPEND(
      24         126 :     FD_LAYOUT_APPEND(
      25         126 :     FD_LAYOUT_APPEND(
      26         126 :     FD_LAYOUT_INIT,
      27         126 :       alignof(fd_reasm_t), sizeof(fd_reasm_t)              ),
      28         126 :       pool_align(),        pool_footprint    ( fec_max )   ),
      29         126 :       ancestry_align(),    ancestry_footprint( chain_cnt ) ),
      30         126 :       frontier_align(),    frontier_footprint( chain_cnt ) ),
      31         126 :       orphaned_align(),    orphaned_footprint( chain_cnt ) ),
      32         126 :       subtrees_align(),    subtrees_footprint( chain_cnt ) ),
      33         126 :       bfs_align(),         bfs_footprint     ( fec_max )   ),
      34         126 :       xid_align(),         xid_footprint     ( lgf_max )   ),
      35         126 :     fd_reasm_align() );
      36         126 : }
      37             : 
      38             : void *
      39             : fd_reasm_new( void * shmem,
      40             :               ulong  fec_max,
      41          63 :               ulong  seed ) {
      42             : 
      43          63 :   if( FD_UNLIKELY( !shmem ) ) {
      44           0 :     FD_LOG_WARNING(( "NULL mem" ));
      45           0 :     return NULL;
      46           0 :   }
      47             : 
      48          63 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_reasm_align() ) ) ) {
      49           0 :     FD_LOG_WARNING(( "misaligned mem" ));
      50           0 :     return NULL;
      51           0 :   }
      52             : 
      53          63 :   ulong footprint = fd_reasm_footprint( fec_max );
      54          63 :   if( FD_UNLIKELY( !footprint ) ) {
      55           0 :     FD_LOG_WARNING(( "bad fec_max (%lu)", fec_max ));
      56           0 :     return NULL;
      57           0 :   }
      58             : 
      59          63 :   fd_wksp_t * wksp = fd_wksp_containing( shmem );
      60          63 :   if( FD_UNLIKELY( !wksp ) ) {
      61           0 :     FD_LOG_WARNING(( "shmem must be part of a workspace" ));
      62           0 :     return NULL;
      63           0 :   }
      64             : 
      65          63 :   fd_memset( shmem, 0, footprint );
      66             : 
      67          63 :   ulong max_slots = fd_ulong_max(fec_max / FD_FEC_BLK_MAX, 1UL);                  /* add capacity for a block id per slot */
      68          63 :   int   lgf_max   = fd_ulong_find_msb( fd_ulong_pow2_up( fec_max + max_slots ) ); /* capacity for fec_max fecs + (fec_max / 1024) more block ids */
      69          63 :   ulong chain_cnt = ancestry_chain_cnt_est( fec_max );                            /* estimated buckets for ancestry map */
      70             : 
      71          63 :   fd_reasm_t * reasm;
      72          63 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
      73          63 :   reasm           = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_reasm_t), sizeof(fd_reasm_t)              );
      74          63 :   void * pool     = FD_SCRATCH_ALLOC_APPEND( l, pool_align(),        pool_footprint    ( fec_max )   );
      75          63 :   void * ancestry = FD_SCRATCH_ALLOC_APPEND( l, ancestry_align(),    ancestry_footprint( chain_cnt ) );
      76          63 :   void * frontier = FD_SCRATCH_ALLOC_APPEND( l, frontier_align(),    frontier_footprint( chain_cnt ) );
      77          63 :   void * orphaned = FD_SCRATCH_ALLOC_APPEND( l, orphaned_align(),    orphaned_footprint( chain_cnt ) );
      78          63 :   void * subtrees = FD_SCRATCH_ALLOC_APPEND( l, subtrees_align(),    subtrees_footprint( chain_cnt ) );
      79          63 :   void * bfs      = FD_SCRATCH_ALLOC_APPEND( l, bfs_align(),         bfs_footprint     ( fec_max )   );
      80          63 :   void * xid      = FD_SCRATCH_ALLOC_APPEND( l, xid_align(),         xid_footprint     ( lgf_max )   );
      81          63 :   FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_reasm_align() ) == (ulong)shmem + footprint );
      82             : 
      83          63 :   reasm->slot0      = ULONG_MAX;
      84          63 :   reasm->root       = pool_idx_null( pool );
      85          63 :   reasm->pool_gaddr = fd_wksp_gaddr_fast( wksp, pool_join( pool_new( pool, fec_max ) ) );
      86          63 :   reasm->wksp_gaddr = fd_wksp_gaddr_fast( wksp, reasm );
      87          63 :   reasm->ancestry   = ancestry_new( ancestry, chain_cnt, seed );
      88          63 :   reasm->frontier   = frontier_new( frontier, chain_cnt, seed );
      89          63 :   reasm->orphaned   = orphaned_new( orphaned, chain_cnt, seed );
      90          63 :   reasm->subtrees   = subtrees_new( subtrees, chain_cnt, seed );
      91          63 :   reasm->subtreel   = subtreel_new( reasm->_subtrlf           );
      92          63 :   reasm->out        = out_new     ( reasm->_out               );
      93          63 :   reasm->bfs        = bfs_new     ( bfs,      fec_max         );
      94          63 :   reasm->xid        = xid_new     ( xid,      lgf_max, seed   );
      95             : 
      96          63 :   return shmem;
      97          63 : }
      98             : 
      99             : fd_reasm_t *
     100          63 : fd_reasm_join( void * shreasm ) {
     101          63 :   fd_reasm_t * reasm = (fd_reasm_t *)shreasm;
     102             : 
     103          63 :   if( FD_UNLIKELY( !reasm ) ) {
     104           0 :     FD_LOG_WARNING(( "NULL reasm" ));
     105           0 :     return NULL;
     106           0 :   }
     107             :   /* pool join handled in fd_reasm_new */
     108          63 :   reasm->ancestry = ancestry_join( reasm->ancestry );
     109          63 :   reasm->frontier = frontier_join( reasm->frontier );
     110          63 :   reasm->orphaned = orphaned_join( reasm->orphaned );
     111          63 :   reasm->subtrees = subtrees_join( reasm->subtrees );
     112          63 :   reasm->subtreel = subtreel_join( reasm->_subtrlf );
     113          63 :   reasm->out      = out_join     ( reasm->_out     );
     114          63 :   reasm->bfs      = bfs_join     ( reasm->bfs      );
     115          63 :   reasm->xid      = xid_join     ( reasm->xid      );
     116             : 
     117          63 :   return reasm;
     118          63 : }
     119             : 
     120             : void *
     121          51 : fd_reasm_leave( fd_reasm_t * reasm ) {
     122             : 
     123          51 :   if( FD_UNLIKELY( !reasm ) ) {
     124           0 :     FD_LOG_WARNING(( "NULL reasm" ));
     125           0 :     return NULL;
     126           0 :   }
     127             : 
     128          51 :   return (void *)reasm;
     129          51 : }
     130             : 
     131             : void *
     132          51 : fd_reasm_delete( void * shreasm ) {
     133          51 :   fd_reasm_t * reasm = (fd_reasm_t *)shreasm;
     134             : 
     135          51 :   if( FD_UNLIKELY( !reasm ) ) {
     136           0 :     FD_LOG_WARNING(( "NULL reasm" ));
     137           0 :     return NULL;
     138           0 :   }
     139             : 
     140          51 :   if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)reasm, fd_reasm_align() ) ) ) {
     141           0 :     FD_LOG_WARNING(( "misaligned reasm" ));
     142           0 :     return NULL;
     143           0 :   }
     144             : 
     145          51 :   return reasm;
     146          51 : }
     147             : 
     148           6 : fd_reasm_fec_t       * fd_reasm_root         ( fd_reasm_t       * reasm                                 ) { return pool_ele      ( reasm_pool      ( reasm ), reasm->root      ); }
     149           6 : fd_reasm_fec_t const * fd_reasm_root_const   ( fd_reasm_t const * reasm                                 ) { return pool_ele_const( reasm_pool_const( reasm ), reasm->root      ); }
     150         111 : fd_reasm_fec_t       * fd_reasm_parent       ( fd_reasm_t       * reasm, fd_reasm_fec_t       * child   ) { return pool_ele      ( reasm_pool      ( reasm ), child->parent    ); }
     151           0 : fd_reasm_fec_t const * fd_reasm_parent_const ( fd_reasm_t const * reasm, fd_reasm_fec_t const * child   ) { return pool_ele_const( reasm_pool_const( reasm ), child->parent    ); }
     152       12486 : fd_reasm_fec_t       * fd_reasm_child        ( fd_reasm_t       * reasm, fd_reasm_fec_t       * parent  ) { return pool_ele      ( reasm_pool      ( reasm ), parent->child    ); }
     153          42 : fd_reasm_fec_t const * fd_reasm_child_const  ( fd_reasm_t const * reasm, fd_reasm_fec_t const * parent  ) { return pool_ele_const( reasm_pool_const( reasm ), parent->child    ); }
     154       12312 : fd_reasm_fec_t       * fd_reasm_sibling      ( fd_reasm_t       * reasm, fd_reasm_fec_t       * sibling ) { return pool_ele      ( reasm_pool      ( reasm ), sibling->sibling ); }
     155           0 : fd_reasm_fec_t const * fd_reasm_sibling_const( fd_reasm_t const * reasm, fd_reasm_fec_t const * sibling ) { return pool_ele_const( reasm_pool_const( reasm ), sibling->sibling ); }
     156             : 
     157             : ulong
     158           0 : fd_reasm_slot0( fd_reasm_t * reasm ) {
     159           0 :   return reasm->slot0;
     160           0 : }
     161             : 
     162             : ulong
     163           3 : fd_reasm_free( fd_reasm_t * reasm ) {
     164           3 :   return pool_free( reasm_pool( reasm ) );
     165           3 : }
     166             : 
     167             : fd_reasm_fec_t *
     168           3 : fd_reasm_peek( fd_reasm_t * reasm ) {
     169           3 :   fd_reasm_fec_t * pool = reasm_pool( reasm );
     170           3 :   for( out_iter_t iter = out_iter_fwd_init(       reasm->out, pool );
     171           3 :                         !out_iter_done    ( iter, reasm->out, pool );
     172           3 :                   iter = out_iter_fwd_next( iter, reasm->out, pool ) ) {
     173           0 :     fd_reasm_fec_t * fec = out_iter_ele( iter, reasm->out, pool );
     174           0 :     if( FD_LIKELY( fec && !fec->popped && ( !fec->eqvoc || fec->confirmed ) ) ) return fec;
     175           0 :   }
     176           3 :   return NULL;
     177           3 : }
     178             : 
     179             : fd_reasm_fec_t *
     180         171 : fd_reasm_pop( fd_reasm_t * reasm ) {
     181         171 :   fd_reasm_fec_t * pool = reasm_pool( reasm );
     182         201 :   while( FD_LIKELY( !out_is_empty( reasm->out, pool ) ) ) {
     183         156 :     fd_reasm_fec_t * fec = out_ele_pop_head( reasm->out, pool );
     184         156 :     fec->in_out = 0;
     185         156 :     if( FD_LIKELY( !fec->popped && ( !fec->eqvoc || fec->confirmed ) ) ) {
     186         126 :       fec->popped = 1;
     187         126 :       return fec;
     188         126 :     }
     189         156 :   }
     190          45 :   return NULL;
     191         171 : }
     192             : 
     193             : fd_reasm_fec_t *
     194             : fd_reasm_query( fd_reasm_t       * reasm,
     195       37821 :                 fd_hash_t  const * merkle_root ) {
     196       37821 :   fd_reasm_fec_t * pool = reasm_pool( reasm );
     197       37821 :   fd_reasm_fec_t * fec = NULL;
     198       37821 :   fec =                  ancestry_ele_query( reasm->ancestry, merkle_root, NULL, pool );
     199       37821 :   fec = fd_ptr_if( !fec, frontier_ele_query( reasm->frontier, merkle_root, NULL, pool ), fec );
     200       37821 :   fec = fd_ptr_if( !fec, orphaned_ele_query( reasm->orphaned, merkle_root, NULL, pool ), fec );
     201       37821 :   fec = fd_ptr_if( !fec, subtrees_ele_query( reasm->subtrees, merkle_root, NULL, pool ), fec );
     202       37821 :   return fec;
     203       37821 : }
     204             : 
     205             : void
     206             : fd_reasm_confirm( fd_reasm_t      * reasm,
     207          27 :                   fd_hash_t const * block_id ) {
     208          27 :   fd_reasm_fec_t * pool = reasm_pool( reasm );
     209          27 :   fd_reasm_fec_t * fec = ancestry_ele_query( reasm->ancestry, block_id, NULL, pool );
     210          27 :   fec = fd_ptr_if( !fec, frontier_ele_query( reasm->frontier, block_id, NULL, pool ), fec );
     211             : 
     212             :   /* TODO there is a potential optimization where we don't actually need
     213             :      to confirm every FEC and instead just confirm at the slot-level.
     214             :      Given roughly ~1k shreds per slot at 32 shreds per FEC, this would
     215             :      save ~32 loop iterations.  Punting given the additional complexity
     216             :      of bookkeeping and logic this would require.
     217             : 
     218             :      If this FEC has not been popped, then that means we need to
     219             :      redeliver it for execution.  Reasm could be in a state where by
     220             :      confirming the child of an equivocating chain, the child is
     221             :      confirmed & popped, but the parent of it is confirmed & not
     222             :      *popped*. It was not delivered through the reasm out queue because
     223             :      it was backfilled. In the off chance that the parent confirmation
     224             :      arrives after the child confirmation, we need to make sure to not
     225             :      redeliver the parent again.  The invariant is that if a FEC is
     226             :      already confirmed, either it or its child must have already been
     227             :      delivered for execution. */
     228             : 
     229          27 :   if( FD_LIKELY( fec && !fec->popped && !fec->in_out && !fec->confirmed ) ) {
     230           9 :     out_ele_push_tail( reasm->out, fec, pool );
     231           9 :     fec->in_out = 1;
     232           9 :   }
     233             : 
     234          96 :   while( FD_LIKELY( fec && !fec->confirmed ) ) {
     235          69 :     fec->confirmed = 1;
     236          69 :     fec = fd_reasm_parent( reasm, fec );
     237          69 :   }
     238          27 : }
     239             : 
     240             : /* Mark the entire subtree beginning from root as equivocating.  This is
     241             :    linear in the number of descendants in the subtree, but amortizes
     242             :    because we can short-circuit the BFS at nodes that are already marked
     243             :    equivocating, so each node is visited at most once. */
     244             : 
     245             : static void
     246             : eqvoc( fd_reasm_t     * reasm,
     247         108 :        fd_reasm_fec_t * root ) {
     248         108 :   fd_reasm_fec_t * pool = reasm_pool( reasm );
     249         108 :   ulong *          bfs  = reasm->bfs;
     250         108 :   bfs_push_tail( bfs, pool_idx( pool, root ) );
     251         240 :   while( FD_LIKELY( !bfs_empty( bfs ) ) ) {
     252         132 :     fd_reasm_fec_t * descendant = pool_ele( pool, bfs_pop_head( bfs ) );
     253         132 :     if( FD_LIKELY( descendant->eqvoc ) ) continue;
     254         105 :     descendant->eqvoc      = 1;
     255         105 :     fd_reasm_fec_t * child = fd_reasm_child( reasm, descendant );
     256         129 :     while( FD_LIKELY( child ) ) {
     257          24 :       bfs_push_tail( bfs, pool_idx( pool, child ) );
     258          24 :       child = fd_reasm_sibling( reasm, child );
     259          24 :     }
     260         105 :   }
     261         108 : }
     262             : 
     263             : static int
     264             : validate_intra( fd_reasm_fec_t const * parent,
     265       12606 :                 fd_reasm_fec_t const * child ) {
     266       12606 :   return child->fec_set_idx==parent->fec_set_idx+FD_FEC_SHRED_CNT &&
     267       12606 :          child->parent_off ==parent->parent_off &&
     268       12606 :         !parent->slot_complete;
     269       12606 : }
     270             : 
     271             : static int
     272             : validate_inter( fd_reasm_fec_t const * parent,
     273       12606 :                 fd_reasm_fec_t const * child ) {
     274       12606 :   return child->slot - child->parent_off==parent->slot &&
     275       12606 :          child->fec_set_idx==0 &&
     276       12606 :          parent->slot_complete;
     277       12606 : }
     278             : 
     279             : static inline int
     280             : validate_chained_block_id( fd_reasm_fec_t const * parent,
     281       12606 :                            fd_reasm_fec_t const * child ) {
     282       12606 :   return fd_int_if( parent->slot==child->slot, validate_intra( parent, child ), validate_inter( parent, child ) );
     283       12606 : }
     284             : 
     285             : static void
     286             : link( fd_reasm_t     * reasm,
     287             :       fd_reasm_fec_t * parent,
     288       12570 :       fd_reasm_fec_t * child ) {
     289       12570 :   fd_reasm_fec_t * pool = reasm_pool( reasm );
     290       12570 :   child->parent = pool_idx( pool, parent );
     291       12570 :   if( FD_LIKELY( parent->child == pool_idx_null( pool ) ) ) {
     292       12516 :     parent->child = pool_idx( pool, child ); /* set as left-child. */
     293       12516 :   } else {
     294          54 :     fd_reasm_fec_t * sibling = pool_ele( pool, parent->child );
     295          66 :     while( FD_LIKELY( sibling->sibling != pool_idx_null( pool ) ) ) sibling = pool_ele( pool, sibling->sibling );
     296          54 :     sibling->sibling = pool_idx( pool, child ); /* set as right-sibling. */
     297          54 :   }
     298       12570 : }
     299             : 
     300             : /* Assumes caller is de-duplicating FEC sets of the same merkle root.
     301             :    Updates the xid map and inserts the new FEC into the head of the
     302             :    xid list. */
     303             : static xid_t *
     304       12642 : xid_update( fd_reasm_t * reasm, ulong slot, uint fec_set_idx, ulong pool_idx ) {
     305       12642 :   fd_reasm_fec_t * new_fec = pool_ele( reasm_pool( reasm ), pool_idx );
     306       12642 :   xid_t          * xid     = xid_query( reasm->xid, (slot << 32) | fec_set_idx, NULL );
     307       12642 :   if( FD_UNLIKELY( xid ) ) {
     308          42 :     new_fec->xid_next = xid->idx;
     309          42 :     xid->idx = pool_idx; /* updates head ptr */
     310          42 :     xid->cnt++;
     311       12600 :   } else {
     312       12600 :     xid = xid_insert( reasm->xid, (slot << 32) | fec_set_idx );
     313       12600 :     if( FD_UNLIKELY( !xid ) ) FD_LOG_CRIT(( "xid map full, slot=%lu fec_set_idx=%u", slot, fec_set_idx ));
     314       12600 :     xid->idx = pool_idx;
     315       12600 :     xid->cnt = 1;
     316       12600 :   }
     317       12642 :   return xid;
     318       12642 : }
     319             : 
     320             : static fd_reasm_fec_t *
     321             : clear_xid_metadata( fd_reasm_t     * reasm,
     322       12372 :                      fd_reasm_fec_t * fec ) {
     323       12372 :   fd_reasm_fec_t * pool = reasm_pool( reasm );
     324       12372 :   ulong fec_idx = pool_idx( pool, fec );
     325             : 
     326       12372 :   xid_t * xid = xid_query( reasm->xid, (fec->slot << 32)|fec->fec_set_idx, NULL );
     327       12372 :   xid->cnt--;
     328       12372 :   if( FD_LIKELY( !xid->cnt ) ) {
     329       12360 :     xid_remove( reasm->xid, xid );
     330       12360 :   } else if( xid->idx==fec_idx ) {
     331           3 :     xid->idx = fec->xid_next;
     332           9 :   } else {
     333           9 :     fd_reasm_fec_t * prev = pool_ele( pool, xid->idx );
     334           9 :     while( prev->xid_next!=fec_idx ) prev = pool_ele( pool, prev->xid_next );
     335           9 :     prev->xid_next = fec->xid_next;
     336           9 :   }
     337             : 
     338       12372 :   return fec;
     339       12372 : }
     340             : 
     341             : static void
     342             : subtrees_remove( fd_reasm_t     * reasm,
     343             :                  fd_reasm_fec_t * root,
     344           9 :                  fd_store_t     * opt_store ) {
     345           9 :   fd_reasm_fec_t * pool = reasm_pool( reasm );
     346           9 :   ulong *          bfs  = reasm->bfs;
     347             : 
     348           9 :   FD_TEST( bfs_empty( bfs ) );
     349           9 :   bfs_push_tail( bfs, pool_idx( pool, root ) );
     350       12306 :   while( FD_LIKELY( !bfs_empty( bfs ) ) ) {
     351       12297 :     fd_reasm_fec_t * ele = pool_ele( pool, bfs_pop_head( bfs ) );
     352             : 
     353       12297 :     fd_reasm_fec_t * child = fd_reasm_child( reasm, ele );
     354       24585 :     while( FD_LIKELY( child ) ) {
     355       12288 :       bfs_push_tail( bfs, pool_idx( pool, child ) );
     356       12288 :       child = fd_reasm_sibling( reasm, child );
     357       12288 :     }
     358             : 
     359       12297 :     if( FD_UNLIKELY( subtrees_ele_query( reasm->subtrees, &ele->key, NULL, pool )==ele ) ) {
     360           9 :       subtrees_ele_remove( reasm->subtrees, &ele->key, NULL, pool );
     361           9 :       subtreel_ele_remove( reasm->subtreel,  ele,            pool );
     362       12288 :     } else {
     363       12288 :       FD_TEST( orphaned_ele_remove( reasm->orphaned, &ele->key, NULL, pool )==ele );
     364       12288 :     }
     365             : 
     366       12297 :     clear_xid_metadata( reasm, ele );
     367       12297 :     if( FD_LIKELY( opt_store ) ) fd_store_remove( opt_store, &ele->key );
     368       12297 :     pool_ele_release( pool, ele );
     369       12297 :   }
     370           9 :   FD_TEST( bfs_empty( bfs ) );
     371           9 : }
     372             : 
     373             : void
     374             : fd_reasm_pool_release( fd_reasm_t *     reasm,
     375          21 :                        fd_reasm_fec_t * ele   ){
     376          21 :   pool_ele_release( reasm_pool( reasm ), ele );
     377          21 : }
     378             : 
     379             : ulong
     380           0 : fd_reasm_pool_idx( fd_reasm_t * reasm, fd_reasm_fec_t * ele ) {
     381           0 :   return pool_idx( reasm_pool( reasm ), ele );
     382           0 : }
     383             : 
     384             : fd_reasm_fec_t *
     385             : fd_reasm_remove( fd_reasm_t     * reasm,
     386             :                  fd_reasm_fec_t * head,
     387          15 :                  fd_store_t     * opt_store ) {
     388             :   /* see fd_forest.c clear_leaf */
     389             : 
     390          15 :   fd_reasm_fec_t *    pool     = reasm_pool( reasm );
     391          15 :   orphaned_t *        orphaned = reasm->orphaned;
     392          15 :   frontier_t *        frontier = reasm->frontier;
     393          15 :   ancestry_t *        ancestry = reasm->ancestry;
     394          15 :   subtrees_t *        subtrees = reasm->subtrees;
     395          15 :   subtreel_t *        subtreel = reasm->subtreel;
     396             : 
     397          15 :   FD_TEST( head );
     398             : 
     399          15 :   fd_reasm_fec_t * tail = head;
     400             : 
     401          15 :   if( FD_LIKELY( orphaned_ele_query( orphaned, &head->key, NULL, pool ) ||
     402          15 :                  subtrees_ele_query( subtrees, &head->key, NULL, pool ) ) ) {
     403           3 :     FD_TEST( head->child == ULONG_MAX ); /* must be a leaf node */
     404          12 :   } else {
     405             :     /* Node is in frontier or ancestry.  If the leaf is in the frontier,
     406             :        we could be removing something that has been executed.  Move the
     407             :        head pointer up to where we begin evicting.
     408             : 
     409             :        We search up the tree until the theoretical boundary of a bank.
     410             :        This is usually when we jump to a parent slot, but if an
     411             :        equivocation occured, this could also be the middle of the slot.
     412             : 
     413             :        0 ── 32 ──  64 ──  96          (confirmed)
     414             :               └──  64' ── 96' ── 128' (eqvoc)
     415             : 
     416             :         Note we only have execute a slot twice (have 2 bank idxs for it)
     417             :         if the slot equivocated, we replayed the wrong version, and then
     418             :         replayed the confirmed version afterwards.
     419             : 
     420             :         The state after executing the wrong version first is:
     421             : 
     422             :               0  ────  32 ────  64 ────  96 ──── 128
     423             :         (bank_idx=1) (bank_idx=1) .... (all bank_idx=1)
     424             : 
     425             :         After receiving and executing the confirmed version, the state
     426             :         looks like:
     427             : 
     428             :         0 (b=2) ── 32 (b=2) ──  64  (b=2) ──  96  (b=2)               (confirmed)
     429             :                            └──  64' (b=1) ──  96' (b=1) ── 128' (b=1) (eqvoc)
     430             : 
     431             :         Here we want to evict only until fec 64'. Or let's say we are
     432             :         getting around to executing the confirmed version, but we haven't
     433             :         executed it yet.
     434             : 
     435             :         0 (b=1) ── 32 (b=1) ──  64  (b=ULONG_MAX) ── 96  (b=ULONG_MAX)           (confirmed, but not executed yet)
     436             :                            └──  64' (b=1)         ── 96' (b=1) ── 128'(b=1)      (eqvoc)
     437             : 
     438             :         Now we know we should evict until the max(parent has > 1 child, or fec set idx == 0) */
     439             : 
     440          15 :     while( FD_LIKELY( head ) ) {
     441          15 :       fd_reasm_fec_t * parent = fd_reasm_parent( reasm, head ); /* parent must exist.  It's not possible to walk up past root */
     442             : 
     443          15 :       if( FD_UNLIKELY( head->fec_set_idx==0  ) )                   break;
     444           6 :       if( FD_UNLIKELY( head->sibling != pool_idx_null( pool ) ) )  break; /* if the parent has more than 1 child, we know for sure the parent is a slot boundary or eqvoc point, so we can stop here. */
     445           6 :       if( FD_UNLIKELY( parent->child != pool_idx( pool, head ) ) ) break; /* if the parent has more than 1 child, we know for sure the parent is a slot boundary or eqvoc point, so we can stop here. */
     446           3 :       head = parent;
     447           3 :     }
     448          12 :   }
     449          15 :   FD_LOG_INFO(( "evicting reasm slot %lu, fec idx %u, down to %u bank_idx %lu", head->slot, head->fec_set_idx, tail->fec_set_idx, head->bank_idx ));
     450             : 
     451             :   /* Orphan the entire subtree from the tail :( */
     452          15 :   if( FD_UNLIKELY( fd_reasm_child( reasm, tail ) ) ) {
     453             :     /* subtree this child.  This code path should only be hit if this is
     454             :        banks-driven eviction, so children are guaranteed to be in main
     455             :        tree right now. */
     456           3 :     ulong * bfs = reasm->bfs;
     457           3 :     bfs_push_tail( bfs, pool_idx( pool, tail ) );
     458          27 :     while( FD_LIKELY( !bfs_empty( bfs ) ) ) {
     459          24 :       fd_reasm_fec_t * ele   = pool_ele( pool, bfs_pop_head( bfs ) );
     460          24 :       fd_reasm_fec_t * child = fd_reasm_child( reasm, ele );
     461          45 :       while( FD_LIKELY( child ) ) {
     462          21 :         bfs_push_tail( bfs, pool_idx( pool, child ) );
     463          21 :         child = pool_ele( pool, child->sibling );
     464          21 :       }
     465             : 
     466          24 :       if( FD_UNLIKELY( ele == tail ) ) continue;
     467             :       /* remove each child from the maps */
     468          21 :       if( FD_UNLIKELY( !ancestry_ele_remove( ancestry, &ele->key, NULL, pool ) ) ) frontier_ele_remove( frontier, &ele->key, NULL, pool );
     469          21 :       if( FD_LIKELY( ele->in_out ) ) { out_ele_remove( reasm->out, ele, pool ); ele->in_out = 0; }
     470             : 
     471          21 :       if( FD_UNLIKELY( ele->parent == pool_idx( pool, tail ) ) ) {
     472           3 :         subtrees_ele_insert( subtrees, ele, pool );
     473           3 :         subtreel_ele_push_tail( reasm->subtreel, ele, pool );
     474           3 :         ele->parent  = ULONG_MAX;
     475           3 :         ele->sibling = ULONG_MAX;
     476          18 :       } else {
     477          18 :         orphaned_ele_insert( orphaned, ele, pool );
     478          18 :       }
     479          21 :     }
     480             :     /* unlink the leaf from its children. */
     481           3 :     tail->child = ULONG_MAX;
     482           3 :   }
     483             : 
     484          15 :   fd_reasm_fec_t * parent = fd_reasm_parent( reasm, head );
     485          15 :   if( FD_LIKELY( parent ) ) {
     486             :    /* Clean up the parent pointing to this head, and remove block from the maps
     487             :       remove the block from the parent's child list */
     488             : 
     489          12 :     fd_reasm_fec_t * child = pool_ele( pool, parent->child );
     490          12 :     if( FD_LIKELY( 0==memcmp( &child->key, &head->key, sizeof(fd_hash_t) ) ) ) { /* evicted is left-child (or only child) */
     491           9 :       parent->child = child->sibling;
     492           9 :     } else {
     493             :       /* evicted is a right-sibling */
     494           3 :       fd_reasm_fec_t * sibling = pool_ele( pool, child->sibling );
     495           3 :       fd_reasm_fec_t * prev    = child;
     496           6 :       while( FD_LIKELY( sibling && memcmp( &sibling->key, &head->key, sizeof(fd_hash_t) ) ) ) {
     497           3 :         prev = sibling;
     498           3 :         sibling = pool_ele( pool, sibling->sibling );
     499           3 :       }
     500           3 :       prev->sibling = sibling->sibling;
     501           3 :     }
     502             : 
     503             :     /* remove the chain itself from the maps */
     504             : 
     505          12 :     fd_reasm_fec_t * removed_orphan = NULL;
     506          12 :     if( FD_LIKELY  ( removed_orphan = orphaned_ele_remove( orphaned, &head->key, NULL, pool ) ) ) {
     507           0 :       clear_xid_metadata( reasm, head );
     508           0 :       if( FD_LIKELY( opt_store ) ) fd_store_remove( opt_store, &head->key );
     509           0 :       return head;
     510           0 :     }
     511             : 
     512             :     /* remove from ancestry and frontier */
     513          12 :     fd_reasm_fec_t * curr = head;
     514          27 :     while( FD_LIKELY( curr ) ) {
     515          15 :       fd_reasm_fec_t * removed = ancestry_ele_remove( ancestry, &curr->key, NULL, pool );
     516          15 :       if( !removed )   removed = frontier_ele_remove( frontier, &curr->key, NULL, pool );
     517          15 :       if( FD_LIKELY( removed->in_out ) ) { out_ele_remove( reasm->out, removed, pool ); removed->in_out = 0; }
     518             : 
     519          15 :       curr = fd_reasm_child( reasm, curr );  /* guaranteed only one child */
     520          15 :       clear_xid_metadata( reasm, removed );
     521          15 :       if( FD_LIKELY( opt_store ) ) fd_store_remove( opt_store, &removed->key );
     522          15 :     }
     523             : 
     524             :     /* We removed from the main tree, so we might need to insert parent into the frontier.
     525             :         Only need to add parent to the frontier if it doesn't have any other children. */
     526             : 
     527          12 :     if( parent->child == pool_idx_null( pool ) ) {
     528           6 :       parent = ancestry_ele_remove( ancestry, &parent->key, NULL, pool );
     529           6 :       FD_TEST( parent );
     530           6 :       frontier_ele_insert( frontier, parent, pool );
     531           6 :     }
     532          12 :     return head;
     533          12 :   }
     534             : 
     535             :   /* No parent, remove from subtrees and subtree list */
     536           3 :   subtrees_ele_remove( subtrees, &head->key, NULL, pool );
     537           3 :   subtreel_ele_remove( subtreel,  head,            pool );
     538           3 :   clear_xid_metadata( reasm, head );
     539           3 :   if( FD_LIKELY( opt_store ) ) fd_store_remove( opt_store, &head->key );
     540           3 :   return head;
     541          15 : }
     542             : 
     543             : fd_reasm_fec_t *
     544             : latest_confirmed_fec( fd_reasm_t * reasm,
     545           0 :                       ulong        subtree_root ) {
     546           0 :   ulong *          bfs  = reasm->bfs;
     547           0 :   fd_reasm_fec_t * pool = reasm_pool( reasm );
     548           0 :   bfs_push_tail( bfs, subtree_root );
     549           0 :   fd_reasm_fec_t * latest_confirmed = NULL;
     550           0 :   while( FD_LIKELY( !bfs_empty( bfs ) ) ) {
     551           0 :     fd_reasm_fec_t * ele = pool_ele( pool, bfs_pop_head( bfs ) );
     552           0 :     if( FD_LIKELY( ele->confirmed ) ) {
     553           0 :       if( FD_LIKELY( latest_confirmed == NULL ||
     554           0 :                      latest_confirmed->slot < ele->slot ||
     555           0 :                      (latest_confirmed->slot == ele->slot && latest_confirmed->fec_set_idx < ele->fec_set_idx)) )
     556           0 :         latest_confirmed = ele;
     557           0 :     }
     558           0 :     fd_reasm_fec_t * child = fd_reasm_child( reasm, ele );
     559           0 :     while( FD_LIKELY( child ) ) {
     560           0 :       bfs_push_tail( bfs, pool_idx( pool, child ) );
     561           0 :       child = pool_ele( pool, child->sibling );
     562           0 :     }
     563           0 :   }
     564           0 :   return latest_confirmed;
     565           0 : }
     566             : 
     567             : static fd_reasm_fec_t *
     568             : gca( fd_reasm_t     * reasm,
     569             :      fd_reasm_fec_t * a,
     570           0 :      fd_reasm_fec_t * b ) {
     571           0 :   fd_reasm_fec_t * parent1 = a;
     572           0 :   fd_reasm_fec_t * parent2 = b;
     573           0 :   while( FD_LIKELY( parent1 && parent2 ) ) {
     574           0 :     if( FD_LIKELY( parent1 == parent2 ) ) return parent1;
     575           0 :     if( parent1->slot > parent2->slot ||
     576           0 :       ( parent1->slot == parent2->slot && parent1->fec_set_idx > parent2->fec_set_idx ) ) parent1 = fd_reasm_parent( reasm, parent1 );
     577           0 :     else                                                                                  parent2 = fd_reasm_parent( reasm, parent2 );
     578           0 :   }
     579           0 :   return NULL;
     580           0 : }
     581             : 
     582             : /* Caller guarantees new_root and parent_root are non-NULL */
     583             : static fd_reasm_fec_t *
     584             : evict( fd_reasm_t      * reasm,
     585             :        fd_store_t      * opt_store,
     586             :        fd_hash_t const * new_root FD_PARAM_UNUSED,
     587          12 :        fd_hash_t const * parent_root ) {
     588          12 :   fd_reasm_fec_t * pool     = reasm_pool( reasm );
     589          12 :   frontier_t *     frontier = reasm->frontier;
     590          12 :   orphaned_t *     orphaned = reasm->orphaned;
     591          12 :   subtrees_t *     subtrees = reasm->subtrees;
     592          12 :   subtreel_t *     subtreel = reasm->subtreel;
     593             : 
     594             :   /* Generally, best policy for eviction is to evict in the order of:
     595             :     1. Highest unconfirmed orphan leaf                   - furthest from root
     596             :     2. Highest incomplete, unconfirmed leaf in ancestry  - furthest from tip of execution
     597             :     3. Highest confirmed orphan leaf                     - evictable, since unrelated to banks, but less ideal */
     598             : 
     599          12 :   fd_reasm_fec_t * unconfrmd_orphan = NULL; /* 1st best candidate for eviction is the highest unconfirmed orphan. */
     600          12 :   fd_reasm_fec_t * confirmed_orphan = NULL; /* 3rd best candidate for eviction is the highest confirmed orphan.   */
     601          12 :   for( subtreel_iter_t iter = subtreel_iter_fwd_init(       subtreel, pool );
     602          18 :                              !subtreel_iter_done    ( iter, subtreel, pool );
     603          12 :                        iter = subtreel_iter_fwd_next( iter, subtreel, pool ) ) {
     604           6 :     fd_reasm_fec_t * ele = subtreel_iter_ele( iter, subtreel, pool );
     605           6 :     if( ele->child != ULONG_MAX || memcmp( &ele->key, parent_root, sizeof(fd_hash_t) ) == 0 ) continue;
     606           3 :     if( FD_UNLIKELY( ele->confirmed ) ) confirmed_orphan = fd_ptr_if( !confirmed_orphan || ele->slot > confirmed_orphan->slot, ele, confirmed_orphan );
     607           3 :     else                                unconfrmd_orphan = fd_ptr_if( !unconfrmd_orphan || ele->slot > unconfrmd_orphan->slot, ele, unconfrmd_orphan );
     608           3 :   }
     609          12 :   for( orphaned_iter_t iter = orphaned_iter_init( orphaned, pool );
     610          12 :                               !orphaned_iter_done( iter, orphaned, pool );
     611          12 :                         iter = orphaned_iter_next( iter, orphaned, pool ) ) {
     612           0 :     fd_reasm_fec_t *    ele = orphaned_iter_ele( iter, orphaned, pool );
     613           0 :     if( ele->child != ULONG_MAX || memcmp( &ele->key, parent_root, sizeof(fd_hash_t) ) == 0 ) continue;
     614           0 :     if( FD_UNLIKELY( ele->confirmed ) ) confirmed_orphan = fd_ptr_if( !confirmed_orphan || ele->slot > confirmed_orphan->slot, ele, confirmed_orphan );
     615           0 :     else                                unconfrmd_orphan = fd_ptr_if( !unconfrmd_orphan || ele->slot > unconfrmd_orphan->slot, ele, unconfrmd_orphan );
     616           0 :   }
     617             : 
     618          12 :   if( FD_UNLIKELY( unconfrmd_orphan )) {
     619           3 :     return fd_reasm_remove( reasm, unconfrmd_orphan, opt_store );
     620           3 :   }
     621             : 
     622           9 :   fd_reasm_fec_t * unconfrmd_leaf = NULL; /* 2nd best candidate for eviction is the highest unconfirmed, incomplete slot. */
     623           9 :   for( frontier_iter_t iter = frontier_iter_init( frontier, pool );
     624          24 :                               !frontier_iter_done( iter, frontier, pool );
     625          15 :                         iter = frontier_iter_next( iter, frontier, pool ) ) {
     626          15 :     fd_reasm_fec_t * ele = frontier_iter_ele( iter, frontier, pool );
     627          15 :     if( iter.ele_idx == reasm->root
     628          15 :         || 0==memcmp( &ele->key, parent_root, sizeof(fd_hash_t) )
     629          15 :         || ele->confirmed
     630          15 :         || ele->slot_complete
     631          15 :         || ele->is_leader ) continue; /* not a candidate */
     632           9 :     unconfrmd_leaf = fd_ptr_if( !unconfrmd_leaf || ele->slot > unconfrmd_leaf->slot, ele, unconfrmd_leaf );
     633           9 :   }
     634             : 
     635           9 :   if( FD_UNLIKELY( unconfrmd_leaf )) {
     636           6 :     return fd_reasm_remove( reasm, unconfrmd_leaf, opt_store );
     637           6 :   }
     638             : 
     639             :   /* Already did traversal to find best confirmed orphan candidate,
     640             :      which is the third choice */
     641             : 
     642           3 :   if( FD_UNLIKELY( confirmed_orphan )) {
     643           0 :     fd_reasm_fec_t * parent = fd_reasm_query( reasm, parent_root );
     644           0 :     if( !parent ) {
     645           0 :       return fd_reasm_remove( reasm, confirmed_orphan, opt_store );
     646           0 :     }
     647             :   /* for any subtree:
     648             :       0 ── 1 ── 2 ── 3 (confirmed) ── 4(confirmed) ── 5 ── 6 ──> add 7 here is valid.
     649             :                                       └──> add 7 here is valid.
     650             :                         └──> add 7 here is invalid. */
     651           0 :     ulong subtree_root = reasm->root;
     652           0 :     if( subtrees_ele_query( subtrees, parent_root, NULL, pool )  ||
     653           0 :         orphaned_ele_query( orphaned, parent_root, NULL, pool ) ) {
     654             :       /* if adding to an orphan, find the root of the orphan subtree. */
     655           0 :       fd_reasm_fec_t * root = parent;
     656           0 :       while( FD_LIKELY( root->parent != ULONG_MAX ) ) {
     657           0 :         root = pool_ele( pool, root->parent );
     658           0 :       }
     659           0 :       subtree_root = pool_idx( pool, root );
     660           0 :     }
     661             : 
     662           0 :     fd_reasm_fec_t * latest_confirmed_leaf = latest_confirmed_fec( reasm, subtree_root );
     663           0 :     if( !latest_confirmed_leaf || latest_confirmed_leaf == gca( reasm, latest_confirmed_leaf, parent )) {
     664           0 :       return fd_reasm_remove( reasm, confirmed_orphan, opt_store );
     665           0 :     }
     666             :     /* is a useless new fork. */
     667           0 :     return NULL;
     668           0 :   }
     669           3 :   return NULL; /* nothing else could be evicted */
     670           3 : }
     671             : 
     672             : fd_reasm_fec_t *
     673             : fd_reasm_init( fd_reasm_t *      reasm,
     674             :                fd_hash_t const * initial_block_id,
     675          57 :                ulong             slot ) {
     676             : 
     677          57 :   fd_reasm_fec_t * pool = reasm_pool( reasm );
     678          57 :   ulong            null = pool_idx_null( pool );
     679             : 
     680          57 :   FD_TEST( pool_free( pool ) );
     681          57 :   fd_reasm_fec_t * fec = pool_ele_acquire( pool );
     682          57 :   fec->key             = *initial_block_id;
     683          57 :   fec->next            = null;
     684          57 :   fec->parent          = null;
     685          57 :   fec->child           = null;
     686          57 :   fec->sibling         = null;
     687          57 :   fec->slot            = slot;
     688          57 :   fec->parent_off      = 0;
     689          57 :   fec->fec_set_idx     = 0U;
     690          57 :   fec->data_cnt        = 0U;
     691          57 :   fec->data_complete   = 0;
     692          57 :   fec->slot_complete   = 1;
     693          57 :   fec->is_leader       = 0;
     694          57 :   fec->eqvoc           = 0;
     695          57 :   fec->confirmed       = 0;
     696          57 :   fec->popped          = 0;
     697          57 :   fec->bank_dead       = 0;
     698          57 :   fec->bank_idx        = null;
     699          57 :   fec->parent_bank_idx = null;
     700          57 :   fec->bank_seq        = null;
     701          57 :   fec->out.next        = null;
     702          57 :   fec->out.prev        = null;
     703          57 :   fec->in_out          = 0;
     704          57 :   fec->subtreel.next   = null;
     705          57 :   fec->subtreel.prev   = null;
     706             : 
     707             : 
     708          57 :   FD_TEST( reasm->root==pool_idx_null( pool ) );
     709          57 :   fec->confirmed      = 1;
     710          57 :   fec->popped         = 1;
     711          57 :   /*                 */ xid_update( reasm, slot, 0U,       pool_idx( pool, fec ) );
     712          57 :   reasm->root         = pool_idx( pool, fec );
     713          57 :   reasm->slot0        = slot;
     714          57 :   frontier_ele_insert( reasm->frontier, fec, pool );
     715          57 :   return fec;
     716          57 : }
     717             : 
     718             : fd_reasm_fec_t *
     719             : fd_reasm_insert( fd_reasm_t *      reasm,
     720             :                  fd_hash_t const * merkle_root,
     721             :                  fd_hash_t const * chained_merkle_root,
     722             :                  ulong             slot,
     723             :                  uint              fec_set_idx,
     724             :                  ushort            parent_off,
     725             :                  ushort            data_cnt,
     726             :                  int               data_complete,
     727             :                  int               slot_complete,
     728             :                  int               is_leader,
     729             :                  fd_store_t      * opt_store,
     730       12615 :                  fd_reasm_fec_t ** evicted ) {
     731             : 
     732             : # if LOGGING
     733             :   FD_BASE58_ENCODE_32_BYTES( merkle_root->key,         merkle_root_b58         );
     734             :   FD_BASE58_ENCODE_32_BYTES( chained_merkle_root->key, chained_merkle_root_b58 );
     735             :   FD_LOG_NOTICE(( "inserting (%lu %u) %s %s. %u %d %d", slot, fec_set_idx, merkle_root_b58, chained_merkle_root_b58, data_cnt, data_complete, slot_complete ));
     736             : # endif
     737             : 
     738       12615 :   fd_reasm_fec_t * pool = reasm_pool( reasm );
     739       12615 : # if FD_REASM_USE_HANDHOLDING
     740       12615 :   FD_TEST( !fd_reasm_query( reasm, merkle_root ) );
     741       12615 : # endif
     742             : 
     743       12615 :   FD_TEST( chained_merkle_root );
     744             : 
     745       12615 :   ulong        null     = pool_idx_null( pool );
     746       12615 :   ancestry_t * ancestry = reasm->ancestry;
     747       12615 :   frontier_t * frontier = reasm->frontier;
     748       12615 :   orphaned_t * orphaned = reasm->orphaned;
     749       12615 :   subtrees_t * subtrees = reasm->subtrees;
     750       12615 :   subtreel_t * subtreel = reasm->subtreel;
     751             : 
     752       12615 :   ulong     * bfs = reasm->bfs;
     753       12615 :   out_t     * out = reasm->out;
     754             : 
     755       12615 :   *evicted = NULL;
     756             : 
     757       12615 :   if( FD_UNLIKELY( pool_free( pool )==1UL ) ) {
     758          12 :     FD_TEST( reasm->root!=pool_idx_null( pool ) );
     759             :     /* The eviction removes evicted elements from the maps, but leaves
     760             :        the elements in the pool for caller to release.  Thus, in order
     761             :        for the following insert/acquire to succeed, we have to start
     762             :        evicting when we have 1 remaining free element in the pool.  This
     763             :        element is the one that will be acquired below.  reasm is
     764             :        dependent on the caller to then release the evicted elements back
     765             :        to the pool before the next insert/acquire. */
     766          12 :     fd_reasm_fec_t * evicted_fec = evict( reasm, opt_store, merkle_root, chained_merkle_root );
     767          12 :     if( FD_UNLIKELY( evicted_fec == NULL ) ) {
     768           3 :       FD_LOG_INFO(("reasm failed to evict a fec set when inserting slot %lu fec set %u", slot, fec_set_idx));
     769             : 
     770             :       /* in this case we want to signal to the replay tile that we
     771             :          failed to insert the FEC set.  This is effectively is the same
     772             :          logic as if we had this FEC set, and then it got evicted, and
     773             :          then the caller now needs to process the evicted FEC set.  So
     774             :          here we acquire the final pool element for it and return it
     775             :          to the caller as the evicted FEC set. */
     776             : 
     777           3 :       fd_reasm_fec_t * fec = pool_ele_acquire( pool );
     778           3 :       fec->key             = *merkle_root;
     779           3 :       fec->cmr             = *chained_merkle_root;
     780           3 :       fec->parent          = null;
     781           3 :       fec->child           = null;
     782           3 :       fec->slot            = slot;
     783           3 :       fec->parent_off      = parent_off;
     784           3 :       fec->fec_set_idx     = fec_set_idx;
     785           3 :       fec->bank_idx        = null;
     786             : 
     787           3 :       *evicted = fec;
     788           3 :       return NULL;
     789           3 :     }
     790             : 
     791           9 :     *evicted = evicted_fec;
     792           9 :   }
     793             : 
     794       12612 :   FD_TEST( pool_free( pool ) );
     795       12612 :   fd_reasm_fec_t * fec = pool_ele_acquire( pool );
     796       12612 :   fec->key             = *merkle_root;
     797       12612 :   fec->next            = null;
     798       12612 :   fec->parent          = null;
     799       12612 :   fec->child           = null;
     800       12612 :   fec->sibling         = null;
     801       12612 :   fec->slot            = slot;
     802       12612 :   fec->parent_off      = parent_off;
     803       12612 :   fec->fec_set_idx     = fec_set_idx;
     804       12612 :   fec->data_cnt        = data_cnt;
     805       12612 :   fec->data_complete   = data_complete;
     806       12612 :   fec->slot_complete   = slot_complete;
     807       12612 :   fec->is_leader       = is_leader;
     808       12612 :   fec->eqvoc           = 0;
     809       12612 :   fec->confirmed       = 0;
     810       12612 :   fec->popped          = 0;
     811       12612 :   fec->bank_dead       = 0;
     812       12612 :   fec->bank_idx        = null;
     813       12612 :   fec->parent_bank_idx = null;
     814       12612 :   fec->bank_seq        = null;
     815             : 
     816             :   /* set the out and subtreel pointers to null */
     817       12612 :   fec->out.next = null;
     818       12612 :   fec->out.prev = null;
     819       12612 :   fec->in_out   = 0;
     820       12612 :   fec->xid_next = null;
     821       12612 :   fec->subtreel.next = null;
     822       12612 :   fec->subtreel.prev = null;
     823             : 
     824       12612 :   fec->cmr = *chained_merkle_root;
     825             : 
     826       12612 :   fd_reasm_fec_t * parent = fd_reasm_query( reasm, chained_merkle_root );
     827       12612 :   if( FD_UNLIKELY( parent && !validate_chained_block_id( parent, fec ) ) ) {
     828          27 :     FD_BASE58_ENCODE_32_BYTES( fec->key.key,    child_key_cstr  );
     829          27 :     FD_BASE58_ENCODE_32_BYTES( parent->key.key, parent_key_cstr );
     830          27 :     FD_LOG_INFO(( "[%s] failed to validate chained block id FEC: (%lu %u %s). parent (%lu %u %s).", __func__, fec->slot, fec->fec_set_idx, child_key_cstr, parent->slot, parent->fec_set_idx, parent_key_cstr ));
     831          27 :     pool_ele_release( pool, fec );
     832          27 :     return NULL;
     833          27 :   }
     834             : 
     835             :   /* If the FEC's parent already exists link it correctly: the new FEC
     836             :      set may result in a new leaf or a new orphan tree root so we need
     837             :      to check that. */
     838             : 
     839       12585 :   parent = NULL;
     840       12585 :   if( FD_LIKELY( parent = ancestry_ele_query( ancestry, &fec->cmr, NULL, pool ) ) ) { /* parent is connected non-leaf */
     841          48 :     frontier_ele_insert( frontier, fec, pool );
     842          48 :     out_ele_push_tail( out, fec, pool );
     843          48 :     fec->in_out = 1;
     844       12537 :   } else if( FD_LIKELY ( parent = frontier_ele_remove( frontier, &fec->cmr, NULL, pool ) ) ) { /* parent is connected leaf */
     845         192 :     ancestry_ele_insert( ancestry, parent, pool );
     846         192 :     frontier_ele_insert( frontier, fec, pool );
     847         192 :     out_ele_push_tail( out, fec, pool );
     848         192 :     fec->in_out = 1;
     849       12345 :   } else if( FD_LIKELY ( parent = orphaned_ele_query( orphaned, &fec->cmr, NULL, pool ) ) ) { /* parent is orphaned non-root */
     850       12282 :     orphaned_ele_insert( orphaned, fec, pool );
     851       12282 :   } else if( FD_LIKELY ( parent = subtrees_ele_query( subtrees, &fec->cmr, NULL, pool ) ) ) { /* parent is orphaned root */
     852          12 :     orphaned_ele_insert( orphaned, fec, pool );
     853          51 :   } else { /* parent not found */
     854          51 :     subtrees_ele_insert( subtrees, fec, pool );
     855          51 :     subtreel_ele_push_tail( subtreel, fec, pool );
     856          51 :   }
     857             : 
     858       12585 :   if( FD_LIKELY( parent ) ) link( reasm, parent, fec );
     859             : 
     860             :   /* Second, we search for children of this new FEC and link them to it.
     861             :      By definition any children must be orphaned (a child cannot be part
     862             :      of a connected tree before its parent).  Therefore, we only search
     863             :      through the orphaned subtrees.  As part of this operation, we also
     864             :      coalesce orphans into orphan subtrees.  An orphan may be connected
     865             :      to its parent, but part of an orphaned subtree.  This way we only
     866             :      need to search for children the orphan subtree roots (vs. all
     867             :      orphaned nodes). */
     868             : 
     869       12585 :   ulong min_descendant = ULONG_MAX; /* needed for eqvoc checks below */
     870       12585 :   FD_TEST( bfs_empty( bfs ) );
     871       12585 :   for( subtreel_iter_t iter = subtreel_iter_fwd_init(       subtreel, pool );
     872       24999 :                              !subtreel_iter_done    ( iter, subtreel, pool );
     873       12585 :                        iter = subtreel_iter_fwd_next( iter, subtreel, pool ) ) {
     874       12414 :     fd_reasm_fec_t * parent = fec;
     875       12414 :     fd_reasm_fec_t * child  = subtreel_iter_ele( iter, subtreel, pool );
     876       12414 :     if( FD_UNLIKELY( !fd_hash_eq( &parent->key, &child->cmr ) ) ) continue;
     877          45 :     if( FD_UNLIKELY( !validate_chained_block_id( parent, child ) ) ) {
     878           9 :       FD_BASE58_ENCODE_32_BYTES( child->key.key, child_key_cstr  );
     879           9 :       FD_BASE58_ENCODE_32_BYTES( parent->key.key, parent_key_cstr );
     880           9 :       FD_LOG_INFO(( "[%s] failed to validate chained block id FEC: (%lu %u %s). parent (%lu %u %s).", __func__, child->slot, child->fec_set_idx, child_key_cstr, parent->slot, parent->fec_set_idx, parent_key_cstr ));
     881           9 :       subtrees_remove( reasm, child, opt_store );
     882           9 :       continue;
     883           9 :     }
     884          36 :     link( reasm, parent, child );
     885          36 :     subtrees_ele_remove( subtrees, &child->key, NULL, pool );
     886          36 :     subtreel_ele_remove( subtreel, child, pool );
     887          36 :     orphaned_ele_insert( orphaned, child, pool );
     888          36 :     min_descendant = fd_ulong_min( min_descendant, child->slot );
     889          36 :   }
     890             : 
     891             :   /* Third, we advance the frontier outward beginning from fec as we may
     892             :      have connected orphaned descendants to fec in the above step.  This
     893             :      does a BFS outward from fec until it reaches leaves, moving fec and
     894             :      its non-leaf descendants into ancestry and leaves into frontier.
     895             : 
     896             :      parent (ancestry)     orphan root  (subtrees)
     897             :        |                        |
     898             :       fec   (frontier)     orphan child (orphaned)
     899             : 
     900             :      parent
     901             :        |
     902             :       fec         <- frontier is here
     903             :        |
     904             :      orphan root
     905             :        |
     906             :      orphan child <- advance to here */
     907             : 
     908       12585 :   if( FD_LIKELY( frontier_ele_query( frontier, &fec->key, NULL, pool ) ) ) bfs_push_tail( bfs, pool_idx( pool, fec ) );
     909       12864 :   while( FD_LIKELY( !bfs_empty( bfs ) ) ) {
     910         279 :     fd_reasm_fec_t * parent = pool_ele( pool, bfs_pop_head( bfs ) );
     911         279 :     fd_reasm_fec_t * child  = pool_ele( pool, parent->child );
     912         279 :     if( FD_LIKELY( child ) ) {
     913          33 :       frontier_ele_remove( frontier, &parent->key, NULL, pool );
     914          33 :       ancestry_ele_insert( ancestry, parent,             pool );
     915          33 :     }
     916         318 :     while( FD_LIKELY( child ) ) {
     917          39 :       FD_TEST( orphaned_ele_remove( orphaned, &child->key, NULL, pool ) );
     918          39 :       frontier_ele_insert( frontier, child, pool );
     919          39 :       bfs_push_tail( bfs, pool_idx( pool, child ) );
     920          39 :       out_ele_push_tail( out, child, pool );
     921          39 :       child->in_out = 1;
     922          39 :       child = pool_ele( pool, child->sibling );
     923          39 :     }
     924         279 :   }
     925             : 
     926             :   /* Fourth, check and handle equivocation.  There are three cases.
     927             : 
     928             :      1. we've already seen this FEC's xid (slot, fec_set_idx)
     929             :      2. this FEC's parent equivocates. */
     930             : 
     931       12585 :   xid_t * xid = xid_query( reasm->xid, (slot<<32) | fec_set_idx, NULL );
     932       12585 :   if( FD_UNLIKELY( xid ) ) {
     933          42 :     eqvoc( reasm, fec );
     934          42 :     eqvoc( reasm, pool_ele( pool, xid->idx ) ); /* most recent appearance of this xid */
     935             :     /* We can call eqvoc on the head of the xid list because we maintain
     936             :        the order of most recent to least recent. Inductively, this marks
     937             :        all FECs with the same xid as equivocating. */
     938          42 :   }
     939       12585 :   xid_update( reasm, slot, fec_set_idx, pool_idx( pool, fec ) );
     940       12585 :   if( FD_UNLIKELY( parent && parent->eqvoc && !parent->confirmed ) ) eqvoc( reasm, fec );
     941             : 
     942             :   /* Finally, return the newly inserted FEC. */
     943       12585 :   return fec;
     944       12585 : }
     945             : 
     946             : fd_reasm_fec_t *
     947             : fd_reasm_publish( fd_reasm_t      * reasm,
     948             :                   fd_hash_t const * merkle_root,
     949          15 :                   fd_store_t      * opt_store ) {
     950             : 
     951          15 : # if FD_REASM_USE_HANDHOLDING
     952          15 :   if( FD_UNLIKELY( !pool_ele( reasm_pool( reasm ), reasm->root ) ) ) { FD_LOG_WARNING(( "missing root" )); return NULL; }
     953          15 :   if( FD_UNLIKELY( !fd_reasm_query( reasm, merkle_root ) ) ) {
     954           0 :     FD_BASE58_ENCODE_32_BYTES( merkle_root->key, merkle_root_b58 );
     955           0 :     FD_LOG_WARNING(( "merkle root %s not found", merkle_root_b58 ));
     956           0 :     return NULL;
     957           0 :   }
     958          15 : # endif
     959             : 
     960          15 :   fd_reasm_fec_t *  pool = reasm_pool( reasm );
     961          15 :   ulong             null = pool_idx_null( pool );
     962          15 :   fd_reasm_fec_t  * oldr = pool_ele( pool, reasm->root );
     963          15 :   fd_reasm_fec_t  * newr = fd_reasm_query( reasm, merkle_root );
     964          15 :   ulong *           bfs  = reasm->bfs;
     965             : 
     966          15 :   bfs_push_tail( bfs, pool_idx( pool, oldr ) );
     967             : 
     968             :   /* First, BFS down the tree, pruning all of root's ancestors and also
     969             :      any descendants of those ancestors. */
     970             : 
     971             :   /* Also, prune any subtrees who's root is less than the new root. */
     972             : 
     973          15 :   subtreel_t * subtreel = reasm->subtreel;
     974          15 :   for( subtreel_iter_t iter = subtreel_iter_fwd_init( subtreel, pool );
     975          15 :                              !subtreel_iter_done    ( iter, subtreel, pool );
     976          15 :                        iter = subtreel_iter_fwd_next( iter, subtreel, pool ) ) {
     977           0 :     fd_reasm_fec_t * ele = subtreel_iter_ele( iter, subtreel, pool );
     978           0 :     if( ele->slot < newr->slot ) {
     979           0 :       bfs_push_tail( bfs, pool_idx( pool, ele ) );
     980           0 :     }
     981           0 :   }
     982             : 
     983          72 :   while( FD_LIKELY( !bfs_empty( bfs ) ) ) {
     984          57 :     fd_reasm_fec_t * head  = pool_ele( pool, bfs_pop_head( bfs ) );
     985             : 
     986          57 :     fd_reasm_fec_t *          fec = ancestry_ele_remove( reasm->ancestry, &head->key, NULL, pool );
     987          57 :     if( FD_UNLIKELY( !fec ) ) fec = frontier_ele_remove( reasm->frontier, &head->key, NULL, pool );
     988          57 :     if( FD_UNLIKELY( !fec ) ) fec = orphaned_ele_remove( reasm->orphaned, &head->key, NULL, pool );
     989          57 :     if( FD_UNLIKELY( !fec ) ) {
     990           0 :       fec = subtrees_ele_remove( reasm->subtrees, &head->key, NULL, pool );
     991           0 :       subtreel_ele_remove( reasm->subtreel, head, pool );
     992           0 :     }
     993             : 
     994          57 :     fd_reasm_fec_t * child = pool_ele( pool, head->child );
     995         114 :     while( FD_LIKELY( child ) ) {                                                       /* iterate over children */
     996          57 :       if( FD_LIKELY( child != newr ) ) {                                                /* stop at new root */
     997          42 :         bfs_push_tail( bfs, pool_idx( pool, child ) );
     998          42 :       }
     999          57 :       child = pool_ele( pool, child->sibling );                                         /* right-sibling */
    1000          57 :     }
    1001          57 :     clear_xid_metadata( reasm, head );
    1002          57 :     if( FD_LIKELY( opt_store ) ) fd_store_remove( opt_store, &head->key );
    1003          57 :     if( FD_LIKELY( head->in_out ) ) { out_ele_remove( reasm->out, head, pool ); head->in_out = 0; }
    1004          57 :     pool_ele_release( pool, head );
    1005          57 :   }
    1006             : 
    1007          15 :   newr->parent  = null;                   /* unlink old root */
    1008          15 :   newr->sibling = null;
    1009          15 :   reasm->root   = pool_idx( pool, newr ); /* replace with new root */
    1010          15 :   return newr;
    1011          15 : }
    1012             : 
    1013             : #include <stdio.h>
    1014             : 
    1015             : FD_FN_UNUSED static void
    1016           0 : print( fd_reasm_t const * reasm, fd_reasm_fec_t const * fec, int space, const char * prefix ) {
    1017           0 :   fd_reasm_fec_t const * pool = reasm_pool_const( reasm );
    1018           0 : 
    1019           0 :   if( fec == NULL ) return;
    1020           0 : 
    1021           0 :   if( space > 0 ) printf( "\n" );
    1022           0 :   for( int i = 0; i < space; i++ ) printf( " " );
    1023           0 :   FD_BASE58_ENCODE_32_BYTES( fec->key.key, key_b58 );
    1024           0 :   printf( "%s%s", prefix, key_b58 );
    1025           0 : 
    1026           0 :   fd_reasm_fec_t const * curr = pool_ele_const( pool, fec->child );
    1027           0 :   char new_prefix[1024]; /* FIXME size this correctly */
    1028           0 :   while( curr ) {
    1029           0 :     if( pool_ele_const( pool, curr->sibling ) ) {
    1030           0 :       sprintf( new_prefix, "├── " ); /* branch indicating more siblings follow */
    1031           0 :       print( reasm, curr, space + 4, new_prefix );
    1032           0 :     } else {
    1033           0 :       sprintf( new_prefix, "└── " ); /* end branch */
    1034           0 :       print( reasm, curr, space + 4, new_prefix );
    1035           0 :     }
    1036           0 :     curr = pool_ele_const( pool, curr->sibling );
    1037           0 :   }
    1038           0 : }
    1039             : 
    1040             : static void
    1041          42 : ancestry_print( fd_reasm_t const * reasm, fd_reasm_fec_t const * fec, int space, const char * prefix, fd_reasm_fec_t const * prev, ulong recurse_depth ) {
    1042          42 :   fd_reasm_fec_t const * pool = reasm_pool_const( reasm );
    1043          42 :   if( fec == NULL ) return;
    1044          42 :   recurse_depth++;
    1045          42 :   if( recurse_depth == 2048 ) {
    1046           0 :     FD_BASE58_ENCODE_32_BYTES( fec->key.key, key_b58 );
    1047           0 :     FD_LOG_NOTICE(("Cutting off ancestry print at depth %lu, slot %lu. Continue printing with this root key %s.", recurse_depth, fec->slot, key_b58 ));
    1048           0 :     return;
    1049           0 :   }
    1050          42 :   fd_reasm_fec_t const * child = fd_reasm_child_const( reasm, fec );
    1051             : 
    1052          42 :   if( !prev ||  /* root OR */
    1053          42 :       ( fec->slot_complete || (!prev->eqvoc && fec->eqvoc) || fec->child == pool_idx_null( pool ) || child->sibling != pool_idx_null( pool ) )) {
    1054          39 :     if( space > 0 ) printf( "\n" );
    1055         384 :     for( int i = 0; i < space; i++ ) printf( " " );
    1056          39 :     printf( "%s", prefix );
    1057             : 
    1058          39 :     FD_BASE58_ENCODE_32_BYTES( fec->key.key, key_b58 );
    1059          39 :     key_b58[5] = '\0'; /* only print first 5 characters of key_b58 */
    1060          39 :     printf( "%lu(%u) %s", fec->slot, fec->fec_set_idx, key_b58 );
    1061          39 :     if( fec->eqvoc )     printf( " [eqvoc]" );
    1062          39 :     if( fec->is_leader ) printf( " [leader]" );
    1063          39 :     space += 5;
    1064          39 :     fflush(stdout);
    1065          39 :   }
    1066             : 
    1067          42 :   char new_prefix[1024]; /* FIXME size this correctly */
    1068             : 
    1069          75 :   while( child ) {
    1070          33 :     if( pool_ele_const( pool, child->sibling ) ) {
    1071           9 :       sprintf( new_prefix, "├── " ); /* branch indicating more siblings follow */
    1072           9 :       ancestry_print( reasm, child, space, new_prefix, fec, recurse_depth );
    1073          24 :     } else {
    1074          24 :       sprintf( new_prefix, "└── " ); /* end branch */
    1075          24 :       ancestry_print( reasm, child, space, new_prefix, fec, recurse_depth );
    1076          24 :     }
    1077          33 :     child = pool_ele_const( pool, child->sibling );
    1078          33 :   }
    1079          42 : }
    1080             : 
    1081             : void
    1082           6 : fd_reasm_print( fd_reasm_t const * reasm ) {
    1083           6 :   FD_LOG_NOTICE( ( "\n\n[Reasm - showing only leaves, slot completes, and branches]" ) );
    1084           6 :   fd_reasm_fec_t const * pool = reasm_pool_const( reasm );
    1085           6 :   printf( "ele cnt: %lu\n", pool_used( pool ) );
    1086             : 
    1087           6 :   if( FD_LIKELY( reasm->root != pool_idx_null( pool ) ) ) {
    1088           6 :     printf( "\n\n[Connected Fecs]\n" );
    1089           6 :     ancestry_print( reasm, fd_reasm_root_const( reasm ), 0, "", NULL, 0 );
    1090           6 :   }
    1091             : 
    1092           6 :   printf( "\n\n[Unconnected Fecs]\n" );
    1093           6 :   subtreel_t const * subtreel = reasm->_subtrlf;
    1094           6 :   for( subtreel_iter_t iter = subtreel_iter_fwd_init( subtreel, pool );
    1095           9 :                              !subtreel_iter_done    ( iter, subtreel, pool );
    1096           6 :                        iter = subtreel_iter_fwd_next( iter, subtreel, pool ) ) {
    1097           3 :     fd_reasm_fec_t const * fec = subtreel_iter_ele_const( iter, subtreel, pool );
    1098           3 :     ancestry_print( reasm, fec, 0, "", NULL, 0 );
    1099           3 :   }
    1100             : 
    1101           6 :   printf( "\n\n" );
    1102             :   fflush(stdout);
    1103           6 : }

Generated by: LCOV version 1.14