LCOV - code coverage report
Current view: top level - discof/forest - fd_forest.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 1332 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 46 0.0 %

          Line data    Source code
       1             : #include "fd_forest.h"
       2             : 
       3             : static fd_hash_t empty_mr   = { .ul = { 0, 0, 0, 0 } };
       4             : static fd_hash_t invalid_mr = { .ul = { ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX } };
       5             : 
       6             : static ulong *
       7           0 : fd_forest_deque( fd_forest_t * forest ) {
       8           0 :   return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->deque_gaddr );
       9           0 : }
      10             : 
      11             : void *
      12           0 : fd_forest_new( void * shmem, ulong ele_max, ulong shred_max, ulong seed ) {
      13           0 :   FD_TEST( fd_ulong_is_pow2( ele_max ) );
      14             : 
      15           0 :   if( FD_UNLIKELY( !shmem ) ) {
      16           0 :     FD_LOG_WARNING(( "NULL mem" ));
      17           0 :     return NULL;
      18           0 :   }
      19             : 
      20           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_forest_align() ) ) ) {
      21           0 :     FD_LOG_WARNING(( "misaligned mem" ));
      22           0 :     return NULL;
      23           0 :   }
      24             : 
      25           0 :   if( FD_UNLIKELY( !shred_max || shred_max%FD_FEC_SHRED_CNT || shred_max>UINT_MAX ) ) {
      26           0 :     FD_LOG_WARNING(( "bad shred_max (%lu)", shred_max ));
      27           0 :     return NULL;
      28           0 :   }
      29             : 
      30           0 :   ulong footprint = fd_forest_footprint( ele_max, shred_max );
      31           0 :   if( FD_UNLIKELY( !footprint ) ) {
      32           0 :     FD_LOG_WARNING(( "bad ele_max (%lu)", ele_max ));
      33           0 :     return NULL;
      34           0 :   }
      35             : 
      36           0 :   fd_wksp_t * wksp = fd_wksp_containing( shmem );
      37           0 :   if( FD_UNLIKELY( !wksp ) ) {
      38           0 :     FD_LOG_WARNING(( "shmem must be part of a workspace" ));
      39           0 :     return NULL;
      40           0 :   }
      41             : 
      42           0 :   fd_memset( shmem, 0, footprint );
      43           0 :   fd_forest_t * forest;
      44             : 
      45           0 :   ulong idxs_sz   = ele_max*fd_forest_blk_idxs_word_cnt( shred_max )*sizeof(fd_forest_blk_idxs_t);
      46           0 :   ulong mroots_sz = ele_max*(shred_max/FD_FEC_SHRED_CNT)*sizeof(fd_forest_mr_t);
      47           0 :   ulong recv_sz   = ele_max*(shred_max/FD_FEC_SHRED_CNT)*sizeof(fd_forest_recv_t);
      48             : 
      49           0 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
      50           0 :   forest          = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_align(),          sizeof(fd_forest_t)                     );
      51           0 :   void * pool     = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_pool_align(),     fd_forest_pool_footprint    ( ele_max ) );
      52           0 :   void * idxs     = FD_SCRATCH_ALLOC_APPEND( l, 128UL,                      idxs_sz                                 );
      53           0 :   void * code     = FD_SCRATCH_ALLOC_APPEND( l, 128UL,                      idxs_sz                                 );
      54           0 :   void * mroots   = FD_SCRATCH_ALLOC_APPEND( l, 128UL,                      mroots_sz                               );
      55           0 :   void * recv     = FD_SCRATCH_ALLOC_APPEND( l, 128UL,                      recv_sz                                 );
      56           0 :   void * ancestry = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_ancestry_align(), fd_forest_ancestry_footprint( ele_max ) );
      57           0 :   void * frontier = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_frontier_align(), fd_forest_frontier_footprint( ele_max ) );
      58           0 :   void * subtrees = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_subtrees_align(), fd_forest_subtrees_footprint( ele_max ) );
      59           0 :   void * orphaned = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_orphaned_align(), fd_forest_orphaned_footprint( ele_max ) );
      60           0 :   void * subtlist = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_subtlist_align(), fd_forest_subtlist_footprint(         ) );
      61           0 :   void * orphanq  = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_orphanq_align(),  fd_forest_orphanq_footprint ( 2UL*ele_max ) );
      62             : 
      63             :     /* indexers */
      64             : 
      65           0 :   void * requestd = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_requests_align(), fd_forest_requests_footprint( ele_max ) );
      66           0 :   void * reqslist = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_reqslist_align(), fd_forest_reqslist_footprint(         ) );
      67           0 :   void * reqspool = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_reqspool_align(), fd_forest_reqspool_footprint( ele_max ) );
      68           0 :   void * consumed = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_consumed_align(), fd_forest_consumed_footprint( ele_max ) );
      69           0 :   void * conslist = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_conslist_align(), fd_forest_conslist_footprint(         ) );
      70           0 :   void * conspool = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_conspool_align(), fd_forest_conspool_footprint( ele_max ) );
      71           0 :   void * orphreqs = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_requests_align(), fd_forest_requests_footprint( ele_max ) );
      72           0 :   void * orphlist = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_reqslist_align(), fd_forest_reqslist_footprint(         ) );
      73           0 :   void * deque    = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_deque_align(),    fd_forest_deque_footprint   ( ele_max ) );
      74           0 :   FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_forest_align() ) == (ulong)shmem + footprint );
      75             : 
      76           0 :   forest->root           = ULONG_MAX;
      77           0 :   forest->wksp_gaddr     = fd_wksp_gaddr_fast( wksp, forest );
      78           0 :   forest->pool_gaddr     = fd_wksp_gaddr_fast( wksp, fd_forest_pool_join    ( fd_forest_pool_new    ( pool,     ele_max              ) ) );
      79           0 :   forest->shred_max      = shred_max;
      80           0 :   forest->idxs_gaddr     = fd_wksp_gaddr_fast( wksp, idxs   );
      81           0 :   forest->code_gaddr     = fd_wksp_gaddr_fast( wksp, code   );
      82           0 :   forest->mroots_gaddr   = fd_wksp_gaddr_fast( wksp, mroots );
      83           0 :   forest->recv_gaddr     = fd_wksp_gaddr_fast( wksp, recv   );
      84           0 :   forest->ancestry_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_ancestry_join( fd_forest_ancestry_new( ancestry, ele_max, seed        ) ) );
      85           0 :   forest->frontier_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_frontier_join( fd_forest_frontier_new( frontier, ele_max, seed        ) ) );
      86           0 :   forest->subtrees_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_subtrees_join( fd_forest_subtrees_new( subtrees, ele_max, seed        ) ) );
      87           0 :   forest->orphaned_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_orphaned_join( fd_forest_orphaned_new( orphaned, ele_max, seed        ) ) );
      88           0 :   forest->subtlist_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_subtlist_join( fd_forest_subtlist_new( subtlist                       ) ) );
      89           0 :   forest->orphanq_gaddr  = fd_wksp_gaddr_fast( wksp, fd_forest_orphanq_join ( fd_forest_orphanq_new ( orphanq,  2UL*ele_max          ) ) );
      90           0 :   forest->orphan_seq_next = 0UL;
      91             : 
      92             :   /* indexers */
      93             : 
      94           0 :   forest->requests_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_requests_join( fd_forest_requests_new( requestd, ele_max, seed        ) ) );
      95           0 :   forest->reqslist_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_reqslist_join( fd_forest_reqslist_new( reqslist                       ) ) );
      96           0 :   forest->reqspool_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_reqspool_join( fd_forest_reqspool_new( reqspool, ele_max              ) ) );
      97           0 :   forest->consumed_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_consumed_join( fd_forest_consumed_new( consumed, ele_max, seed        ) ) );
      98           0 :   forest->conslist_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_conslist_join( fd_forest_conslist_new( conslist                       ) ) );
      99           0 :   forest->conspool_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_conspool_join( fd_forest_conspool_new( conspool, ele_max              ) ) );
     100           0 :   forest->orphreqs_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_requests_join( fd_forest_requests_new( orphreqs, ele_max, seed        ) ) );
     101           0 :   forest->orphlist_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_reqslist_join( fd_forest_reqslist_new( orphlist                       ) ) );
     102           0 :   forest->deque_gaddr    = fd_wksp_gaddr_fast( wksp, fd_forest_deque_join   ( fd_forest_deque_new   ( deque,    ele_max              ) ) );
     103           0 :   forest->iter     = (fd_forest_iter_t){ .ele_idx = ULONG_MAX, .list_gaddr = forest->reqslist_gaddr };
     104           0 :   forest->orphiter = (fd_forest_iter_t){ .ele_idx = ULONG_MAX, .list_gaddr = forest->orphlist_gaddr };
     105             : 
     106           0 :   FD_COMPILER_MFENCE();
     107           0 :   FD_VOLATILE( forest->magic ) = FD_FOREST_MAGIC;
     108           0 :   FD_COMPILER_MFENCE();
     109             : 
     110           0 :   return shmem;
     111           0 : }
     112             : 
     113             : fd_forest_t *
     114           0 : fd_forest_join( void * shforest ) {
     115           0 :   fd_forest_t * forest = (fd_forest_t *)shforest;
     116             : 
     117           0 :   if( FD_UNLIKELY( !forest ) ) {
     118           0 :     FD_LOG_WARNING(( "NULL forest" ));
     119           0 :     return NULL;
     120           0 :   }
     121             : 
     122           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)forest, fd_forest_align() ) ) ) {
     123           0 :     FD_LOG_WARNING(( "misaligned forest" ));
     124           0 :     return NULL;
     125           0 :   }
     126             : 
     127           0 :   fd_wksp_t * wksp = fd_wksp_containing( forest );
     128           0 :   if( FD_UNLIKELY( !wksp ) ) {
     129           0 :     FD_LOG_WARNING(( "forest must be part of a workspace" ));
     130           0 :     return NULL;
     131           0 :   }
     132             : 
     133           0 :   return forest;
     134           0 : }
     135             : 
     136             : void *
     137           0 : fd_forest_leave( fd_forest_t const * forest ) {
     138             : 
     139           0 :   if( FD_UNLIKELY( !forest ) ) {
     140           0 :     FD_LOG_WARNING(( "NULL forest" ));
     141           0 :     return NULL;
     142           0 :   }
     143             : 
     144           0 :   return (void *)forest;
     145           0 : }
     146             : 
     147             : void *
     148           0 : fd_forest_delete( void * forest ) {
     149             : 
     150           0 :   if( FD_UNLIKELY( !forest ) ) {
     151           0 :     FD_LOG_WARNING(( "NULL forest" ));
     152           0 :     return NULL;
     153           0 :   }
     154             : 
     155           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)forest, fd_forest_align() ) ) ) {
     156           0 :     FD_LOG_WARNING(( "misaligned forest" ));
     157           0 :     return NULL;
     158           0 :   }
     159             : 
     160             :   // TODO: zero out mem?
     161             : 
     162           0 :   return forest;
     163           0 : }
     164             : 
     165             : static void
     166             : requests_insert( fd_forest_t * forest,
     167             :                  fd_forest_requests_t * reqsmap,
     168             :                  fd_forest_reqslist_t * reqslist,
     169           0 :                  ulong pool_idx ) {
     170           0 :   fd_forest_ref_t * pool = fd_forest_reqspool( forest );
     171           0 :   if( fd_forest_requests_ele_query( reqsmap, &pool_idx, NULL, pool ) ) return;
     172           0 :   fd_forest_ref_t * ele = fd_forest_reqspool_ele_acquire( pool );
     173           0 :   ele->idx = pool_idx;
     174           0 :   fd_forest_requests_ele_insert( reqsmap, ele, pool );
     175           0 :   fd_forest_reqslist_ele_push_tail( reqslist, ele, pool );
     176           0 : }
     177             : 
     178             : static void
     179             : requests_remove( fd_forest_t * forest,
     180             :                  fd_forest_requests_t * reqsmap,
     181             :                  fd_forest_reqslist_t * reqslist,
     182             :                  fd_forest_iter_t * reqiter,
     183           0 :                  ulong pool_idx ) {
     184           0 :   fd_forest_ref_t * pool = fd_forest_reqspool( forest );
     185           0 :   fd_forest_ref_t * ele;
     186           0 :   if( FD_LIKELY( ele = fd_forest_requests_ele_remove( reqsmap, &pool_idx, NULL, pool ) ) ) {
     187             :     /* invalidate the iterator if it is on the removed slot. */
     188           0 :     if( FD_UNLIKELY( reqiter->ele_idx == pool_idx ) ) {
     189           0 :       reqiter->ele_idx = ULONG_MAX;
     190           0 :     }
     191           0 :     fd_forest_reqslist_ele_remove( reqslist, ele, pool );
     192           0 :     fd_forest_reqspool_ele_release( pool, ele );
     193           0 :   }
     194           0 : }
     195             : 
     196             : static void
     197           0 : consumed_insert( fd_forest_t * forest, ulong pool_idx ) {
     198           0 :   fd_forest_consumed_t * consumed = fd_forest_consumed( forest );
     199           0 :   fd_forest_ref_t      * pool     = fd_forest_conspool( forest );
     200           0 :   fd_forest_ref_t      * ele      = fd_forest_conspool_ele_acquire( pool );
     201           0 :   ele->idx = pool_idx;
     202           0 :   fd_forest_consumed_ele_insert( consumed, ele, pool );
     203           0 :   fd_forest_conslist_ele_push_tail( fd_forest_conslist( forest ), ele, pool );
     204           0 : }
     205             : 
     206             : static void
     207           0 : consumed_remove( fd_forest_t * forest, ulong forest_pool_idx ) {
     208           0 :   fd_forest_consumed_t * consumed = fd_forest_consumed( forest );
     209           0 :   fd_forest_ref_t      * pool     = fd_forest_conspool( forest );
     210           0 :   fd_forest_ref_t      * ele;
     211           0 :   if( FD_LIKELY( ele = fd_forest_consumed_ele_remove( consumed, &forest_pool_idx, NULL, pool ) ) ) {
     212           0 :     fd_forest_conslist_ele_remove( fd_forest_conslist( forest ), ele, pool );
     213           0 :     fd_forest_conspool_ele_release( pool, ele );
     214           0 :   }
     215           0 : }
     216             : 
     217             : fd_forest_t *
     218           0 : fd_forest_init( fd_forest_t * forest, ulong root_slot ) {
     219           0 :   fd_forest_blk_t *      pool     = fd_forest_pool( forest );
     220           0 :   ulong                  null     = fd_forest_pool_idx_null( pool );
     221           0 :   fd_forest_frontier_t * frontier = fd_forest_frontier( forest );
     222             : 
     223             :   /* Initialize the root node from a pool element. */
     224             : 
     225           0 :   fd_forest_blk_t * root_ele = fd_forest_pool_ele_acquire( pool );
     226           0 :   root_ele->slot             = root_slot;
     227           0 :   root_ele->parent           = null;
     228           0 :   root_ele->child            = null;
     229           0 :   root_ele->sibling          = null;
     230           0 :   root_ele->orphan_seq       = ULONG_MAX;
     231           0 :   root_ele->buffered_idx     = 0;
     232           0 :   root_ele->complete_idx     = 0;
     233           0 :   root_ele->chain_confirmed  = 1;
     234             : 
     235           0 :   fd_forest_blk_mroots( forest, root_ele )[0].mr = (fd_hash_t){ .key = { 0 } };
     236             : 
     237           0 :   forest->root = fd_forest_pool_idx( pool, root_ele );
     238           0 :   fd_forest_frontier_ele_insert( frontier, root_ele, pool ); /* cannot fail */
     239           0 :   consumed_insert( forest, fd_forest_pool_idx( pool, root_ele ) );
     240             : 
     241             :   /* Sanity checks. */
     242             : 
     243           0 :   FD_TEST( root_ele == fd_forest_frontier_ele_query( frontier, &root_slot, NULL, pool ));
     244           0 :   FD_TEST( root_ele->slot == root_slot );
     245             : 
     246           0 :   return forest;
     247           0 : }
     248             : 
     249             : int
     250           0 : fd_forest_verify( fd_forest_t const * forest ) {
     251           0 :   #define FAIL( msg ) do { FD_LOG_WARNING(( "fd_forest_verify: %s", msg )); return -1; } while(0)
     252           0 :   if( FD_UNLIKELY( !forest ) ) {
     253           0 :     FAIL( "NULL forest" );
     254           0 :   }
     255             : 
     256           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)forest, fd_forest_align() ) ) ) {
     257           0 :     FAIL( "misaligned forest" );
     258           0 :   }
     259             : 
     260           0 :   fd_wksp_t * wksp = fd_wksp_containing( forest );
     261           0 :   if( FD_UNLIKELY( !wksp ) ) {
     262           0 :     FAIL( "forest must be part of a workspace" );
     263           0 :   }
     264             : 
     265           0 :   if( FD_UNLIKELY( forest->magic!=FD_FOREST_MAGIC ) ) {
     266           0 :     FAIL( "bad magic" );
     267           0 :   }
     268             : 
     269           0 :   fd_forest_blk_t const * pool = fd_forest_pool_const( forest );
     270             : 
     271           0 :   fd_forest_frontier_t const * frontier = fd_forest_frontier_const( forest );
     272           0 :   fd_forest_orphaned_t const * orphaned = fd_forest_orphaned_const( forest );
     273           0 :   fd_forest_ancestry_t const * ancestry = fd_forest_ancestry_const( forest );
     274           0 :   fd_forest_subtrees_t const * subtrees = fd_forest_subtrees_const( forest );
     275             : 
     276           0 :   if( fd_forest_ancestry_verify( ancestry, fd_forest_pool_max( pool ), pool ) == -1 ) FAIL( "ancestry map corrupted" );
     277           0 :   if( fd_forest_frontier_verify( frontier, fd_forest_pool_max( pool ), pool ) == -1 ) FAIL( "frontier map corrupted" );
     278           0 :   if( fd_forest_subtrees_verify( subtrees, fd_forest_pool_max( pool ), pool ) == -1 ) FAIL( "subtrees map corrupted" );
     279           0 :   if( fd_forest_orphaned_verify( orphaned, fd_forest_pool_max( pool ), pool ) == -1 ) FAIL( "orphaned map corrupted" );
     280             : 
     281             :   /* Invariant: elements can only appear in one of the four maps. */
     282           0 :   for( fd_forest_frontier_iter_t iter = fd_forest_frontier_iter_init( frontier, pool ); !fd_forest_frontier_iter_done( iter, frontier, pool ); iter = fd_forest_frontier_iter_next( iter, frontier, pool ) ) {
     283           0 :     fd_forest_blk_t const * ele = fd_forest_frontier_iter_ele_const( iter, frontier, pool );
     284           0 :     if( fd_forest_ancestry_ele_query_const( ancestry, &ele->slot, NULL, pool ) ) FAIL( "element in frontier map also in ancestry map" );
     285           0 :     if( fd_forest_orphaned_ele_query_const( orphaned, &ele->slot, NULL, pool ) ) FAIL( "element in frontier map also in orphaned map" );
     286           0 :     if( fd_forest_subtrees_ele_query_const( subtrees, &ele->slot, NULL, pool ) ) FAIL( "element in frontier map also in subtrees map" );
     287           0 :   }
     288             : 
     289           0 :   for( fd_forest_orphaned_iter_t iter = fd_forest_orphaned_iter_init( orphaned, pool ); !fd_forest_orphaned_iter_done( iter, orphaned, pool ); iter = fd_forest_orphaned_iter_next( iter, orphaned, pool ) ) {
     290           0 :     fd_forest_blk_t const * ele = fd_forest_orphaned_iter_ele_const( iter, orphaned, pool );
     291           0 :     if( fd_forest_ancestry_ele_query_const( ancestry, &ele->slot, NULL, pool ) ) FAIL( "element in orphaned map also in ancestry map" );
     292           0 :     if( fd_forest_frontier_ele_query_const( frontier, &ele->slot, NULL, pool ) ) FAIL( "element in orphaned map also in frontier map" );
     293           0 :     if( fd_forest_subtrees_ele_query_const( subtrees, &ele->slot, NULL, pool ) ) FAIL( "element in orphaned map also in subtrees map" );
     294           0 :   }
     295             : 
     296           0 :   for( fd_forest_subtrees_iter_t iter = fd_forest_subtrees_iter_init( subtrees, pool ); !fd_forest_subtrees_iter_done( iter, subtrees, pool ); iter = fd_forest_subtrees_iter_next( iter, subtrees, pool ) ) {
     297           0 :     fd_forest_blk_t const * ele = fd_forest_subtrees_iter_ele_const( iter, subtrees, pool );
     298           0 :     if( fd_forest_ancestry_ele_query_const( ancestry, &ele->slot, NULL, pool ) ) FAIL( "element in subtrees map also in ancestry map" );
     299           0 :     if( fd_forest_frontier_ele_query_const( frontier, &ele->slot, NULL, pool ) ) FAIL( "element in subtrees map also in frontier map" );
     300           0 :     if( fd_forest_orphaned_ele_query_const( orphaned, &ele->slot, NULL, pool ) ) FAIL( "element in subtrees map also in orphaned map" );
     301           0 :   }
     302             : 
     303           0 :   fd_forest_subtlist_t const *   subtlist = fd_forest_subtlist_const( forest );
     304           0 :   fd_forest_orphan_ent_t const * orphanq  = fd_forest_orphanq_const( forest );
     305           0 :   ulong live = 0UL;
     306           0 :   for( ulong i=0UL; i<fd_forest_orphanq_cnt( orphanq ); i++ ) {
     307           0 :     fd_forest_blk_t const * blk = fd_forest_subtrees_ele_query_const( subtrees, &orphanq[ i ].slot, NULL, pool );
     308           0 :     if( blk && blk->orphan_seq==orphanq[ i ].seq ) live++;
     309           0 :   }
     310           0 :   ulong head_cnt = 0UL;
     311           0 :   for( fd_forest_subtlist_iter_t iter = fd_forest_subtlist_iter_fwd_init( subtlist, pool );
     312           0 :        !fd_forest_subtlist_iter_done( iter, subtlist, pool );
     313           0 :        iter = fd_forest_subtlist_iter_fwd_next( iter, subtlist, pool ) ) head_cnt++;
     314           0 :   if( FD_UNLIKELY( live!=head_cnt ) ) FAIL( "orphanq live entries != subtree heads" );
     315             : 
     316           0 :   fd_forest_consumed_t const * consumed = fd_forest_consumed_const( forest );
     317           0 :   fd_forest_ref_t const *      conspool = fd_forest_conspool_const( forest );
     318             : 
     319             :   /* from every frontier walk back and verify that there is an ancestor in the consumed map */
     320           0 :   for( fd_forest_frontier_iter_t iter = fd_forest_frontier_iter_init( frontier, pool ); !fd_forest_frontier_iter_done( iter, frontier, pool ); iter = fd_forest_frontier_iter_next( iter, frontier, pool ) ) {
     321           0 :     fd_forest_blk_t const * ele = fd_forest_frontier_iter_ele_const( iter, frontier, pool );
     322           0 :     int found = 0;
     323           0 :     ulong steps = 0;
     324           0 :     while( FD_LIKELY( ele ) ) {
     325           0 :       if( FD_UNLIKELY( ++steps > fd_forest_pool_max( pool ) ) ) FAIL( "frontier parent chain longer than pool (cycle detected)" );
     326           0 :       ulong ele_idx = fd_forest_pool_idx( pool, ele );
     327           0 :       if( fd_forest_consumed_ele_query_const( consumed, &ele_idx, NULL, conspool ) ) {
     328           0 :         found = 1;
     329           0 :         break;
     330           0 :       }
     331           0 :       ele = fd_forest_pool_ele_const( pool, ele->parent );
     332           0 :     }
     333           0 :     if( FD_UNLIKELY( !found ) ) FAIL( "element in frontier map does not have an ancestor in the consumed map" );
     334           0 :   }
     335             : 
     336             :   /* Consumed map elements must be in the frontier or ancestry map. */
     337             : 
     338           0 :   for( fd_forest_consumed_iter_t iter = fd_forest_consumed_iter_init( consumed, conspool ); !fd_forest_consumed_iter_done( iter, consumed, conspool ); iter = fd_forest_consumed_iter_next( iter, consumed, conspool ) ) {
     339           0 :     fd_forest_ref_t const * ele = fd_forest_consumed_iter_ele_const( iter, consumed, conspool );
     340           0 :     fd_forest_blk_t const * ele_ = fd_forest_pool_ele_const( pool, ele->idx );
     341           0 :     if( !fd_forest_ancestry_ele_query_const( ancestry, &ele_->slot, NULL, pool ) && !fd_forest_frontier_ele_query_const( frontier, &ele_->slot, NULL, pool ) ) {
     342           0 :       FAIL( "element in consumed map not in the ancestry or frontier map" );
     343           0 :     }
     344           0 :   }
     345             : 
     346             :   /* Request map + list invariants */
     347           0 :   fd_forest_requests_t const * requests = fd_forest_requests_const( forest );
     348           0 :   fd_forest_reqslist_t const * reqslist = fd_forest_reqslist_const( forest );
     349           0 :   fd_forest_ref_t const *      reqspool = fd_forest_reqspool_const( forest );
     350             : 
     351           0 :   if( forest->iter.ele_idx != fd_forest_pool_idx_null( pool ) &&
     352           0 :       forest->iter.ele_idx != fd_forest_reqslist_ele_peek_head_const( reqslist, reqspool )->idx ) {
     353           0 :     FAIL( "iterator is not at the head of the request list" );
     354           0 :   }
     355             : 
     356             :   /* Every element in the request list must be in the request map */
     357           0 :   for( fd_forest_reqslist_iter_t iter = fd_forest_reqslist_iter_fwd_init( reqslist, reqspool ); !fd_forest_reqslist_iter_done( iter, reqslist, reqspool ); iter = fd_forest_reqslist_iter_fwd_next( iter, reqslist, reqspool ) ) {
     358           0 :     fd_forest_ref_t const * ele = fd_forest_reqslist_iter_ele_const( iter, reqslist, reqspool );
     359           0 :     fd_forest_blk_t const * ele_ = fd_forest_pool_ele_const( pool, ele->idx );
     360           0 :     if( !fd_forest_ancestry_ele_query_const( ancestry, &ele_->slot, NULL, pool ) && !fd_forest_frontier_ele_query_const( frontier, &ele_->slot, NULL, pool ) ) {
     361           0 :       FAIL( "element in request list not in the ancestry or frontier map" );
     362           0 :     }
     363           0 :     if( !fd_forest_requests_ele_query_const( requests, &ele->idx, NULL, reqspool ) ) FAIL( "element in request list not in the request map" );
     364           0 :   }
     365             : 
     366             :   /* Orphan request map + list invariants.  The orphan request map and
     367             :      list share the same reqspool with the main request map and list, so
     368             :      in addition to being internally consistent, a block must never be in
     369             :      both the main request map and the orphan request map - that would
     370             :      leave duplicate references to the same pool idx and corrupt the
     371             :      shared pool when one of them is removed. */
     372             : 
     373           0 :   fd_forest_requests_t const * orphreqs = fd_forest_orphreqs_const( forest );
     374           0 :   fd_forest_reqslist_t const * orphlist = fd_forest_orphlist_const( forest );
     375             : 
     376           0 :   if( forest->orphiter.ele_idx != fd_forest_pool_idx_null( pool ) &&
     377           0 :       forest->orphiter.ele_idx != fd_forest_reqslist_ele_peek_head_const( orphlist, reqspool )->idx ) {
     378           0 :     FAIL( "orphan iterator is not at the head of the orphan request list" );
     379           0 :   }
     380             : 
     381           0 :   for( fd_forest_reqslist_iter_t iter = fd_forest_reqslist_iter_fwd_init( orphlist, reqspool ); !fd_forest_reqslist_iter_done( iter, orphlist, reqspool ); iter = fd_forest_reqslist_iter_fwd_next( iter, orphlist, reqspool ) ) {
     382           0 :     fd_forest_ref_t const * ele = fd_forest_reqslist_iter_ele_const( iter, orphlist, reqspool );
     383           0 :     fd_forest_blk_t const * ele_ = fd_forest_pool_ele_const( pool, ele->idx );
     384           0 :     if( !fd_forest_subtrees_ele_query_const( subtrees, &ele_->slot, NULL, pool ) && !fd_forest_orphaned_ele_query_const( orphaned, &ele_->slot, NULL, pool ) ) {
     385           0 :       FAIL( "element in orphan request list not in the subtrees or orphaned map" );
     386           0 :     }
     387           0 :     if( !fd_forest_requests_ele_query_const( orphreqs, &ele->idx, NULL, reqspool ) ) FAIL( "element in orphan request list not in the orphan request map" );
     388           0 :     if( fd_forest_requests_ele_query_const( requests, &ele->idx, NULL, reqspool ) ) FAIL( "element in both the main request map and the orphan request map" );
     389           0 :   }
     390             : 
     391             :   /* Tree structure invariants.  Walk every element in every map and
     392             :      verify the left-child / right-sibling / parent links are mutually
     393             :      consistent, that slots strictly increase from parent to child, that
     394             :      there are no cycles, and that each element lives in the correct map
     395             :      for its position in the tree. */
     396             : 
     397           0 :   ulong null = fd_forest_pool_idx_null( pool );
     398           0 :   ulong max  = fd_forest_pool_max( pool );
     399             : 
     400             :   /* Root must exist, be parentless, and live in ancestry or frontier. */
     401             : 
     402           0 :   if( FD_UNLIKELY( forest->root == null ) ) FAIL( "root is null" );
     403           0 :   {
     404           0 :     fd_forest_blk_t const * root = fd_forest_pool_ele_const( pool, forest->root );
     405           0 :     if( FD_UNLIKELY( root->parent != null ) ) FAIL( "root has a parent" );
     406           0 :     if( FD_UNLIKELY( !fd_forest_ancestry_ele_query_const( ancestry, &root->slot, NULL, pool ) &&
     407           0 :                      !fd_forest_frontier_ele_query_const( frontier, &root->slot, NULL, pool ) ) ) {
     408           0 :       FAIL( "root not in ancestry or frontier" );
     409           0 :     }
     410           0 :   }
     411             : 
     412             :   /* Per-element parent/child/sibling link checks, applied to every
     413             :      element regardless of which map it lives in. */
     414             : 
     415           0 : # define CHECK_TREE_ELE( ele ) do {                                                                 \
     416           0 :     ulong ele_idx = fd_forest_pool_idx( pool, (ele) );                                              \
     417           0 :     if( (ele)->parent != null ) {                                                                   \
     418           0 :       fd_forest_blk_t const * p = fd_forest_pool_ele_const( pool, (ele)->parent );                  \
     419           0 :       if( FD_UNLIKELY( (ele)->parent_slot != p->slot ) ) FAIL( "parent_slot != parent ele slot" );  \
     420           0 :       if( FD_UNLIKELY( (ele)->slot       <= p->slot ) ) FAIL( "child slot <= parent slot" );        \
     421           0 :     }                                                                                               \
     422           0 :     ulong steps = 0;                                                                                \
     423           0 :     ulong child_idx = (ele)->child;                                                                 \
     424           0 :     while( child_idx != null ) {                                                                    \
     425           0 :       if( FD_UNLIKELY( ++steps > max ) ) FAIL( "sibling chain longer than pool (cycle)" );          \
     426           0 :       fd_forest_blk_t const * c = fd_forest_pool_ele_const( pool, child_idx );                      \
     427           0 :       if( FD_UNLIKELY( c->parent != ele_idx ) ) FAIL( "child->parent does not point back to ele" ); \
     428           0 :       if( FD_UNLIKELY( c->parent_slot != (ele)->slot ) ) FAIL( "child->parent_slot mismatch" );     \
     429           0 :       if( FD_UNLIKELY( c->slot <= (ele)->slot ) ) FAIL( "child slot <= parent slot" );              \
     430           0 :       child_idx = c->sibling;                                                                       \
     431           0 :     }                                                                                               \
     432           0 :   } while(0)
     433             : 
     434             :   /* ancestry: connected interior nodes - must have at least one child,
     435             :      and may only be parentless if it is the root. */
     436             : 
     437           0 :   for( fd_forest_ancestry_iter_t iter = fd_forest_ancestry_iter_init( ancestry, pool ); !fd_forest_ancestry_iter_done( iter, ancestry, pool ); iter = fd_forest_ancestry_iter_next( iter, ancestry, pool ) ) {
     438           0 :     fd_forest_blk_t const * ele = fd_forest_ancestry_iter_ele_const( iter, ancestry, pool );
     439           0 :     CHECK_TREE_ELE( ele );
     440           0 :     if( FD_UNLIKELY( ele->child == null ) ) FAIL( "ancestry element has no child" );
     441           0 :     if( FD_UNLIKELY( ele->parent == null && fd_forest_pool_idx( pool, ele ) != forest->root ) ) FAIL( "ancestry element parentless but not root" );
     442           0 :   }
     443             : 
     444             :   /* frontier: tips of the connected tree - must be childless, and may
     445             :      only be parentless if it is the root. */
     446             : 
     447           0 :   for( fd_forest_frontier_iter_t iter = fd_forest_frontier_iter_init( frontier, pool ); !fd_forest_frontier_iter_done( iter, frontier, pool ); iter = fd_forest_frontier_iter_next( iter, frontier, pool ) ) {
     448           0 :     fd_forest_blk_t const * ele = fd_forest_frontier_iter_ele_const( iter, frontier, pool );
     449           0 :     CHECK_TREE_ELE( ele );
     450           0 :     if( FD_UNLIKELY( ele->child != null ) ) FAIL( "frontier element has a child" );
     451           0 :     if( FD_UNLIKELY( ele->parent == null && fd_forest_pool_idx( pool, ele ) != forest->root ) ) FAIL( "frontier element parentless but not root" );
     452           0 :   }
     453             : 
     454             :   /* subtrees: heads of orphan trees - must be parentless. */
     455             : 
     456           0 :   for( fd_forest_subtrees_iter_t iter = fd_forest_subtrees_iter_init( subtrees, pool ); !fd_forest_subtrees_iter_done( iter, subtrees, pool ); iter = fd_forest_subtrees_iter_next( iter, subtrees, pool ) ) {
     457           0 :     fd_forest_blk_t const * ele = fd_forest_subtrees_iter_ele_const( iter, subtrees, pool );
     458           0 :     CHECK_TREE_ELE( ele );
     459           0 :     if( FD_UNLIKELY( ele->parent != null ) ) FAIL( "subtree head has a parent" );
     460           0 :   }
     461             : 
     462             :   /* orphaned: interior orphan nodes - must have a parent, and walking
     463             :      up the parent chain must terminate at a subtree head. */
     464             : 
     465           0 :   for( fd_forest_orphaned_iter_t iter = fd_forest_orphaned_iter_init( orphaned, pool ); !fd_forest_orphaned_iter_done( iter, orphaned, pool ); iter = fd_forest_orphaned_iter_next( iter, orphaned, pool ) ) {
     466           0 :     fd_forest_blk_t const * ele = fd_forest_orphaned_iter_ele_const( iter, orphaned, pool );
     467           0 :     CHECK_TREE_ELE( ele );
     468           0 :     if( FD_UNLIKELY( ele->parent == null ) ) FAIL( "orphaned element has no parent" );
     469           0 :     fd_forest_blk_t const * cur   = ele;
     470           0 :     ulong                   steps = 0;
     471           0 :     while( cur->parent != null ) {
     472           0 :       if( FD_UNLIKELY( ++steps > max ) ) FAIL( "orphan parent chain longer than pool (cycle)" );
     473           0 :       cur = fd_forest_pool_ele_const( pool, cur->parent );
     474           0 :     }
     475           0 :     if( FD_UNLIKELY( !fd_forest_subtrees_ele_query_const( subtrees, &cur->slot, NULL, pool ) ) ) FAIL( "orphan tree head not in subtrees map" );
     476           0 :   }
     477             : 
     478           0 : # undef CHECK_TREE_ELE
     479             : 
     480           0 :   return 0;
     481           0 : }
     482             : #undef FAIL
     483             : 
     484             : /* {maps}_remove removes an ele from {maps}. does not unlink ele. */
     485             : 
     486             : static fd_forest_blk_t *
     487           0 : ancestry_frontier_remove( fd_forest_t * forest, ulong slot ) {
     488           0 :   fd_forest_blk_t * pool = fd_forest_pool( forest );
     489           0 :   fd_forest_blk_t * ele;
     490           0 :   ele = fd_forest_ancestry_ele_remove( fd_forest_ancestry( forest ), &slot, NULL, pool );
     491           0 :   if( !ele ) ele = fd_forest_frontier_ele_remove( fd_forest_frontier( forest ), &slot, NULL, pool );
     492           0 :   return ele;
     493           0 : }
     494             : 
     495             : static void
     496             : subtrees_insert( fd_forest_t * forest,
     497           0 :                  fd_forest_blk_t * ele ) {
     498           0 :   fd_forest_blk_t * pool = fd_forest_pool( forest );
     499           0 :   fd_forest_subtrees_ele_insert( fd_forest_subtrees( forest ), ele, pool );
     500           0 :   fd_forest_subtlist_ele_push_tail( fd_forest_subtlist( forest ), ele, pool );
     501             : 
     502           0 :   ulong seq = forest->orphan_seq_next++;
     503           0 :   ele->orphan_seq = seq;
     504           0 :   fd_forest_orphan_ent_t * orphanq = fd_forest_orphanq( forest );
     505           0 :   if( FD_UNLIKELY( fd_forest_orphanq_cnt( orphanq )==fd_forest_orphanq_max( orphanq ) ) ) {
     506           0 :     ulong cnt = fd_forest_orphanq_cnt( orphanq );
     507           0 :     ulong i;
     508           0 :     for( i=0UL; i<cnt; i++ ) {
     509           0 :       fd_forest_blk_t * blk = fd_forest_subtrees_ele_query( fd_forest_subtrees( forest ), &orphanq[ i ].slot, NULL, pool );
     510           0 :       if( FD_UNLIKELY( !blk || blk->orphan_seq!=orphanq[ i ].seq ) ) { fd_forest_orphanq_remove( orphanq, i ); break; }
     511           0 :     }
     512           0 :     FD_TEST( i<cnt ); /* live entries <= ele_max = max/2 */
     513           0 :   }
     514           0 :   fd_forest_orphan_ent_t ent = { .due = LONG_MIN+(long)seq, .slot = ele->slot, .seq = seq };
     515           0 :   fd_forest_orphanq_insert( orphanq, &ent );
     516           0 : }
     517             : 
     518             : static fd_forest_blk_t *
     519           0 : subtrees_orphaned_remove( fd_forest_t * forest, ulong slot ) {
     520           0 :   fd_forest_blk_t * pool = fd_forest_pool( forest );
     521           0 :   fd_forest_blk_t * ele  = NULL;
     522           0 :   ele = fd_forest_orphaned_ele_remove( fd_forest_orphaned( forest ), &slot, NULL, pool );
     523           0 :   if( ele ) return ele;
     524           0 :   ele = fd_forest_subtrees_ele_remove( fd_forest_subtrees( forest ), &slot, NULL, pool );
     525           0 :   if( ele ) fd_forest_subtlist_ele_remove( fd_forest_subtlist( forest ), ele, pool );
     526           0 :   return ele;
     527           0 : }
     528             : 
     529             : /* link ele to the tree via its sibling. */
     530             : 
     531             : static void
     532           0 : link_sibling( fd_forest_t * forest, fd_forest_blk_t * sibling, fd_forest_blk_t * ele ) {
     533           0 :   fd_forest_blk_t * pool = fd_forest_pool( forest );
     534           0 :   ulong             null = fd_forest_pool_idx_null( pool );
     535           0 :   while( FD_UNLIKELY( sibling->sibling != null )) sibling = fd_forest_pool_ele( pool, sibling->sibling );
     536           0 :   sibling->sibling = fd_forest_pool_idx( pool, ele );
     537           0 : }
     538             : 
     539             : /* link child to the tree via its parent. */
     540             : 
     541             : static void
     542           0 : link( fd_forest_t * forest, fd_forest_blk_t * parent, fd_forest_blk_t * child ) {
     543           0 :   fd_forest_blk_t * pool = fd_forest_pool( forest );
     544           0 :   ulong             null = fd_forest_pool_idx_null( pool );
     545           0 :   if( FD_LIKELY( parent->child == null ) ) parent->child = fd_forest_pool_idx( pool, child ); /* left-child */
     546           0 :   else link_sibling( forest, fd_forest_pool_ele( pool, parent->child ), child );          /* right-sibling */
     547           0 :   child->parent = fd_forest_pool_idx( pool, parent );
     548           0 : }
     549             : 
     550             : /* advance_consumed_frontier attempts to advance the consumed frontier beginning from slot
     551             :    using BFS.  head is the first element of a linked list representing
     552             :    the BFS queue.  A slot can be advanced if all shreds for the block
     553             :    are received ie. consumed_idx = complete_idx. */
     554             : 
     555             : static void
     556           0 : advance_consumed_frontier( fd_forest_t * forest, ulong slot, ulong parent_slot ) {
     557           0 :   fd_forest_blk_t *      pool     = fd_forest_pool( forest );
     558           0 :   fd_forest_ref_t *      conspool = fd_forest_conspool( forest );
     559           0 :   fd_forest_consumed_t * consumed = fd_forest_consumed( forest );
     560           0 :   ulong                * queue    = fd_forest_deque( forest );
     561             : 
     562           0 :   ulong slot_pool_idx   = fd_forest_pool_idx( pool, fd_forest_query( forest, slot ) );
     563           0 :   ulong parent_pool_idx = fd_forest_pool_idx( pool, fd_forest_query( forest, parent_slot ) );
     564           0 :   fd_forest_ref_t * ele;
     565           0 :   ele = fd_forest_consumed_ele_query( consumed, &slot_pool_idx, NULL, conspool );
     566           0 :   if( !ele ) ele = fd_forest_consumed_ele_query( consumed, &parent_pool_idx, NULL, conspool );
     567           0 :   if( FD_UNLIKELY( !ele ) ) return;
     568             : 
     569           0 :   FD_CHECK_CRIT( fd_forest_deque_cnt( queue ) == 0, "invariant violation" );
     570             : 
     571             :   /* BFS elements as pool idxs.
     572             :      Invariant: whatever is in the queue, must be in the consumed map. */
     573           0 :   fd_forest_deque_push_tail( queue, ele->idx );
     574           0 :   while( FD_LIKELY( fd_forest_deque_cnt( queue ) ) ) {
     575           0 :     fd_forest_blk_t * head  = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) );
     576           0 :     fd_forest_blk_t * child = fd_forest_pool_ele( pool, head->child );
     577             : 
     578           0 :     int all_shreds_received = head->complete_idx != UINT_MAX && head->complete_idx == head->buffered_idx;
     579             : 
     580           0 :     if( FD_LIKELY( child && all_shreds_received ) )  { /* we've received all the shreds for the slot - not all the FECs for the slot need to be completed */
     581           0 :       consumed_remove( forest, fd_forest_pool_idx( pool, head ) );
     582           0 :       while( FD_LIKELY( child ) ) { /* add children to consumed frontier */
     583           0 :         consumed_insert( forest, fd_forest_pool_idx( pool, child ) );
     584           0 :         fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) );
     585           0 :         child = fd_forest_pool_ele( pool, child->sibling );
     586           0 :       }
     587           0 :     }
     588           0 :   }
     589           0 : }
     590             : 
     591             : fd_forest_blk_t *
     592           0 : fd_forest_query( fd_forest_t * forest, ulong slot ) {
     593           0 :   fd_forest_blk_t *      pool      = fd_forest_pool( forest );
     594           0 :   fd_forest_ancestry_t * ancestry  = fd_forest_ancestry( forest );
     595           0 :   fd_forest_frontier_t * frontier  = fd_forest_frontier( forest );
     596           0 :   fd_forest_subtrees_t * subtrees  = fd_forest_subtrees( forest );
     597           0 :   fd_forest_orphaned_t * orphaned  = fd_forest_orphaned( forest );
     598             : 
     599           0 :   fd_forest_blk_t * ele;
     600           0 :   ele =            fd_forest_ancestry_ele_query( ancestry, &slot, NULL, pool );
     601           0 :   if( !ele ) ele = fd_forest_frontier_ele_query( frontier, &slot, NULL, pool );
     602           0 :   if( !ele ) ele = fd_forest_subtrees_ele_query( subtrees, &slot, NULL, pool );
     603           0 :   if( !ele ) ele = fd_forest_orphaned_ele_query( orphaned, &slot, NULL, pool );
     604           0 :   return ele;
     605           0 : }
     606             : 
     607             : /* remove_and_unlink removes a block from the forest and unlinks it from
     608             :    its parent.  Orphans all the descendants of the block. Also removes
     609             :    from sibling chain, consumed map, and requests map if needed.  Does
     610             :    NOT release the block from the pool. */
     611             : static void
     612           0 : remove_and_unlink( fd_forest_t * forest, fd_forest_blk_t * blk ) {
     613           0 :   fd_forest_blk_t      * pool     = fd_forest_pool( forest );
     614           0 :   fd_forest_orphaned_t * orphaned = fd_forest_orphaned( forest );
     615           0 :   fd_forest_frontier_t * frontier = fd_forest_frontier( forest );
     616           0 :   fd_forest_ancestry_t * ancestry = fd_forest_ancestry( forest );
     617           0 :   fd_forest_consumed_t * consumed = fd_forest_consumed( forest );
     618           0 :   fd_forest_ref_t *      conspool = fd_forest_conspool( forest );
     619           0 :   ulong                  null    = fd_forest_pool_idx_null( pool );
     620             : 
     621             :   /* Clean up the parent, and remove block from the maps */
     622           0 :   fd_forest_blk_t * parent = fd_forest_pool_ele( pool, blk->parent );
     623           0 :   if( FD_UNLIKELY( !parent ) ) {
     624           0 :     subtrees_orphaned_remove( forest, blk->slot ); /* remove from subtrees and subtree list */
     625           0 :   } else {
     626             :     /* remove the block from the parent's child list */
     627             : 
     628           0 :     blk->parent = null;
     629           0 :     fd_forest_blk_t * child = fd_forest_pool_ele( pool, parent->child );
     630           0 :     if( FD_LIKELY( child->slot == blk->slot ) ) {
     631           0 :       parent->child = child->sibling;
     632           0 :     } else {
     633             :       /* go through the sibling list, and remove the block */
     634           0 :       fd_forest_blk_t * sibling = fd_forest_pool_ele( pool, child->sibling );
     635           0 :       fd_forest_blk_t * prev    = child;
     636           0 :       while( FD_LIKELY( sibling ) ) {
     637           0 :         if( FD_LIKELY( sibling->slot == blk->slot ) ) {
     638           0 :           prev->sibling = sibling->sibling;
     639           0 :           break;
     640           0 :         }
     641           0 :         prev = sibling;
     642           0 :         sibling = fd_forest_pool_ele( pool, sibling->sibling );
     643           0 :       }
     644           0 :     }
     645           0 :     blk->sibling = null;
     646             : 
     647             :     /* remove the block itself from the maps */
     648             : 
     649           0 :     fd_forest_blk_t * removed = fd_forest_orphaned_ele_remove( orphaned, &blk->slot, NULL, pool );
     650           0 :     if( !removed ) {
     651           0 :       removed = ancestry_frontier_remove( forest, blk->slot ); FD_TEST( removed );
     652             : 
     653             :       /* We removed from the main tree, so we possible need to insert parent into the frontier.
     654             :           Only need to add parent to the frontier if it doesn't have any other children. */
     655             : 
     656           0 :       if( parent->child == null ) {
     657           0 :         parent = fd_forest_ancestry_ele_remove( ancestry, &blk->parent_slot, NULL, pool );
     658           0 :         fd_forest_frontier_ele_insert( frontier, parent, pool );
     659             :         /* ensure parent is reachable from consumed frontier */
     660           0 :         ulong ancestor = fd_forest_pool_idx( pool, parent );
     661           0 :         while( FD_UNLIKELY( ancestor!=null &&
     662           0 :                             !fd_forest_consumed_ele_query( consumed, &ancestor, NULL, conspool ) ) ) {
     663           0 :           ancestor = fd_forest_pool_ele( pool, ancestor )->parent;
     664           0 :         }
     665           0 :         if( FD_UNLIKELY( ancestor==null ) ) consumed_insert( forest, fd_forest_pool_idx( pool, parent ) );
     666           0 :       }
     667           0 :     }
     668           0 :   }
     669             : 
     670             :   /* finally, release the block from reference maps */
     671           0 :   consumed_remove( forest, fd_forest_pool_idx( pool, blk ) );
     672           0 :   requests_remove( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), &forest->orphiter, fd_forest_pool_idx( pool, blk ) );
     673           0 :   requests_remove( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), &forest->iter,     fd_forest_pool_idx( pool, blk ) );
     674             : 
     675             :   /* orphan/subtree all the descendants of ele, if there are any. In
     676             :      eviction case, this is no-op. */
     677           0 :   ulong * queue = fd_forest_deque( forest );
     678           0 :   fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, blk ) );
     679             : 
     680           0 :   while( FD_LIKELY( fd_forest_deque_cnt( queue ) ) ) {
     681           0 :     fd_forest_blk_t * curr  = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) );
     682           0 :     fd_forest_blk_t * child = fd_forest_pool_ele( pool, curr->child );
     683           0 :     while( FD_LIKELY( child ) ) {
     684             :       /* remove all descendants from all structures */
     685           0 :       ancestry_frontier_remove( forest, child->slot );
     686           0 :       subtrees_orphaned_remove( forest, child->slot );
     687           0 :       consumed_remove( forest, fd_forest_pool_idx( pool, child ) );
     688           0 :       requests_remove( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), &forest->iter, fd_forest_pool_idx( pool, child ) );
     689             : 
     690           0 :       fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) );
     691           0 :       child = fd_forest_pool_ele( pool, child->sibling );
     692           0 :     }
     693             :     /* this is the ele itself, do not reinsert */
     694           0 :     if( FD_UNLIKELY( curr==blk ) ) continue;
     695             : 
     696           0 :     else if( FD_UNLIKELY( fd_forest_pool_ele( pool, curr->parent ) == blk ) ) { /* direct child of the ele, insert it into subtrees */
     697           0 :       curr->parent  = fd_forest_pool_idx_null( pool );
     698           0 :       curr->sibling = fd_forest_pool_idx_null( pool );
     699           0 :       subtrees_insert( forest, curr );
     700           0 :       requests_insert( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), fd_forest_pool_idx( pool, curr ) );
     701             : 
     702           0 :     } else { /* otherwise, not direct descendant of ele, insert it into orphaned */
     703           0 :       fd_forest_orphaned_ele_insert( orphaned, curr, pool );
     704           0 :     }
     705           0 :   }
     706           0 : }
     707             : 
     708             : static ulong
     709           0 : clear_leaf( fd_forest_t * forest, ulong slot ) {
     710           0 :   fd_forest_blk_t * pool = fd_forest_pool( forest );
     711           0 :   fd_forest_blk_t * blk  = fd_forest_query( forest, slot );
     712           0 :   FD_TEST( blk );
     713             : 
     714           0 :   remove_and_unlink( forest, blk );
     715           0 :   fd_forest_pool_ele_release( pool, blk );
     716             : 
     717           0 :   return slot;
     718           0 : }
     719             : 
     720             : /* returns latest confirmed leaf in the subtree rooted at root */
     721             : static fd_forest_blk_t *
     722           0 : latest_confirmed_slot( fd_forest_t * forest, ulong root_idx ) {
     723           0 :   ulong * queue = fd_forest_deque( forest );
     724           0 :   fd_forest_blk_t * latest_confirmed = NULL;
     725           0 :   fd_forest_blk_t * pool             = fd_forest_pool( forest );
     726           0 :   fd_forest_deque_remove_all( queue );
     727           0 :   fd_forest_deque_push_tail( queue, root_idx );
     728             : 
     729             :   /* BFS through the tree.  Since there can only be one confirmed fork,
     730             :      the last confirmed node we find must be the latest confirmed slot.
     731             :      We could be more efficient by limiting the search when we find a
     732             :      confirmed node, but left like this for now. */
     733             : 
     734           0 :   while( FD_LIKELY( !fd_forest_deque_empty( queue ) ) ) {
     735           0 :     fd_forest_blk_t * blk = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) );
     736           0 :     if( FD_LIKELY( blk->chain_confirmed || memcmp( &blk->confirmed_bid, &empty_mr, sizeof( fd_hash_t ) ) != 0 ) ) {
     737           0 :       latest_confirmed = blk;
     738           0 :     }
     739           0 :     fd_forest_blk_t * child = fd_forest_pool_ele( pool, blk->child );
     740           0 :     while( FD_LIKELY( child ) ) {
     741           0 :       fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) );
     742           0 :       child = fd_forest_pool_ele( pool, child->sibling );
     743           0 :     }
     744           0 :   }
     745           0 :   return latest_confirmed;
     746           0 : }
     747             : 
     748             : static fd_forest_blk_t *
     749           0 : gca( fd_forest_t * forest, fd_forest_blk_t * blk1, fd_forest_blk_t * blk2 ) {
     750           0 :   fd_forest_blk_t * pool = fd_forest_pool( forest );
     751           0 :   fd_forest_blk_t * parent1 = blk1;
     752           0 :   fd_forest_blk_t * parent2 = blk2;
     753           0 :   while( FD_LIKELY( parent1 && parent2 ) ) {
     754           0 :     if( FD_LIKELY( parent1->slot == parent2->slot ) ) return parent1;
     755           0 :     if( parent1->slot > parent2->slot ) parent1 = fd_forest_pool_ele( pool, parent1->parent );
     756           0 :     else                                parent2 = fd_forest_pool_ele( pool, parent2->parent );
     757           0 :   }
     758           0 :   return NULL;
     759           0 : }
     760             : 
     761             : #define UPDATE_BEST_CANDIDATE( best_confrmd, best_unconfrmd, ele, filter )                         \
     762           0 :   if( FD_UNLIKELY( filter ) ) continue;                                                            \
     763           0 :   do {                                                                                             \
     764           0 :     int _confirmed = ele->chain_confirmed || !fd_hash_eq( &ele->confirmed_bid, &empty_mr );        \
     765           0 :     if( FD_UNLIKELY( _confirmed ) ) {                                                              \
     766           0 :       if( FD_LIKELY( !best_confrmd ) ) best_confrmd = ele;                                         \
     767           0 :       else                             best_confrmd = fd_ptr_if( best_confrmd->slot < ele->slot, ele, best_confrmd ); \
     768           0 :     } else {                                                                                                          \
     769           0 :       if( FD_LIKELY( !best_unconfrmd ) ) best_unconfrmd = ele;                                                        \
     770           0 :       else                               best_unconfrmd = fd_ptr_if( best_unconfrmd->slot < ele->slot, ele, best_unconfrmd ); \
     771           0 :     }                                                                                                                \
     772           0 :   } while(0)
     773             : 
     774             : /* fd_forest_evict is called when the forest has no more free elements,
     775             :    but we are trying to insert a new block.
     776             :    When this happens, forest begins evicting in the following order:
     777             : 
     778             :      1. Orphaned,  unconfirmed leaves
     779             :      2. Connected, unconfirmed leaves
     780             :      3. Orphaned,  confirmed   leaves
     781             : 
     782             :   We follow a general heuristic of evicting the leaf (youngest
     783             :   descendant) in each category first, with an exception.  If the leaf is
     784             :   the parent of the slot we are adding, we pick a different leaf to
     785             :   evict.  This is to avoid getting stuck in a cycle of creating an
     786             :   orphan that would immediately get evicted again by its parent getting
     787             :   requested.
     788             : 
     789             :   If we have confirmations we also avoid adding new slots that we are
     790             :   certain won't get confirmed.
     791             : 
     792             :   The likely most common case of eviction being called is when we have
     793             :   disconnected from the cluster for a while, or if we are catching up
     794             :   from far behind.  In these cases, the distance from the last root to
     795             :   current turbine could be > slot max. But if we just blindly evict
     796             :   orphans at will, this could make the problem worse. Imagine slot_max =
     797             :   1000, and we are 2000 slots behind.
     798             : 
     799             :   slot       [unconnected]      slot  -  slot  - ... - slot
     800             :    1                            1001     1002          2000
     801             : 
     802             :   At this point if we receive slot 1000 -- this is a good case. Ideally
     803             :   we would evict slot 2000, and add slot 1000, and we make progress
     804             :   towards completing catchup.  But if we receive slot 2002, and we evict
     805             :   slot 2000, then the next orphan request would give us 2001, 2000 again
     806             :   and again, and theoretically we never make progress towards completing
     807             :   catchup.
     808             : 
     809             :   It's unclear if we should evict orphans ONLY if the slot being added
     810             :   is closer to the root.  It's possible that the new, later orphan is
     811             :   actually closer to the root than the older, earlier orphan, and those
     812             :   were just dud slots sent to us by an ancestor.  In practice, the need
     813             :   repair orphan process is much faster than the turbine process, so for
     814             :   now we make the choice to optimistically keep orphans, and rely on the
     815             :   repair orphan process to quickly connect ancestry, faster than the
     816             :   future slots can come and evict it.
     817             : 
     818             :   WHAT IF WE HAVE NO ORPHANS.
     819             : 
     820             :   If we don't have orphans, we need to evict the newest unconfirmed
     821             :   leaf. I.e. start by trimming from the tip of the tree, but on
     822             :   a fork that is a minority.
     823             : 
     824             :   i.e best case:
     825             : 
     826             :   1 ── 2 ── 4 ── 6 ── 7 ── 8 ...... ── 1000           <- 1001 would like to be added to the rree
     827             :        └── 3 ── 5
     828             : 
     829             :   If 1000 is confirmed, and 5 is not, we should evict 5 first, and then add 1001.
     830             : 
     831             :   1 ── 2 ── 4 ── 6 ── 7 ── 8 ...... ── 1000 ── 1001   <- 1002 would like to be added to the rree
     832             :        └── 3
     833             : 
     834             :   Similarly, after 1002 arrives:
     835             :   1 ── 2 ── 4 ── 6 ── 7 ── 8 ...... ── 1000 ── 1001 ── 1002   <- 1003 would like to be added to the rree
     836             : 
     837             :   Now we have one fork, with 1003 chaining to 1002.  If 1002 is
     838             :   confirmed, then it's truly unfortunate... We (and most likely the
     839             :   cluster) hasn't rooted in max_live_slots!  As long as 2 is also
     840             :   confirmed, then we are just going to optimistically publish forward to
     841             :   slot 2 and make it our new root. Note 2 MUST have undergone
     842             :   fec_chain_verify before it can be confirmed.  If 2 is still not
     843             :   confirmed, we could still be in the process of evicting + repairing
     844             :   duplicates, so we must wait for 2 to be confirmed before we can
     845             :   publish forward.
     846             : 
     847             :   If 1002 is NOT confirmed, we cannot evict it and add 1003. This puts
     848             :   us under the case where we can't evict our parent.  At this point we
     849             :   would rely on a confirmation to occur eventually that prunes state and
     850             :   frees up pool elements.
     851             : 
     852             :   This also works in the degenerate DoS case, where we have an extremely
     853             :   wide tree. Imagine someone someone with leader slots 1001 thru 1995 is
     854             :   doing the following attck:
     855             : 
     856             :   1 ── 2 ── 3 ── 4 ── 5 ──       <-- when we try to add 6, we run into eviction policy
     857             :                  ├── 1001'
     858             :                  ├── 1003'
     859             :                  ...
     860             :                  └── 1995'
     861             :   Even if the confirmation for 5 is lagging coming in (or it requires us
     862             :   to replay 6 to see it), we can follow a general policy of evicting the
     863             :   newest unconfirmed leaf. Newest implies furthest from the root. So we
     864             :   would evict 1995' first, and then add 6. */
     865             : 
     866             : 
     867             : static ulong
     868           0 : evict( fd_forest_t * forest, ulong new_slot, ulong parent_slot ) {
     869             :   /* TODO If we've reached the point that we need to evict,
     870             :      should we stop using the orphan iterator to make requests? i.e.
     871             :      focus only on rebuilding ancestry.  */
     872             : 
     873           0 :   (void)new_slot;
     874           0 :   fd_forest_frontier_t * frontier = fd_forest_frontier( forest );
     875           0 :   fd_forest_subtlist_t * subtlist = fd_forest_subtlist( forest );
     876           0 :   fd_forest_subtrees_t * subtrees = fd_forest_subtrees( forest );
     877           0 :   fd_forest_orphaned_t * orphaned = fd_forest_orphaned( forest );
     878           0 :   fd_forest_blk_t *      pool     = fd_forest_pool( forest );
     879             : 
     880             :   /* Generally, best policy for eviction is to evict in the order of:
     881             :       1. Highest unconfirmed orphan leaf       - furthest from root
     882             :       2. Highest unconfirmed leaf in ancestry  - furthest from tip of execution
     883             :       3. Highest confirmed orphan leaf
     884             :       4. Highest confirmed leaf in ancestry    - at this point we would not evict this candidate.
     885             : 
     886             :       Since there can only be one confirmed fork, if we have more than
     887             :       one fork,  then we should always be able to evict the unconfirmed
     888             :       slots with ease.
     889             : 
     890             :       There's some exceptions. We cannot evict slots that would be our
     891             :       parent, because this would create a loop of evictions. Or, if the
     892             :       slot we are adding is older than the rest of our orphans, we
     893             :       shouldn't add it. or maybe we should? FAAAAA currently we will. */
     894             : 
     895           0 :   fd_forest_blk_t * unconfrmd_orphan = NULL; /* 1st best candidate for eviction is the highest unconfirmed orphan. */
     896           0 :   fd_forest_blk_t * confirmed_orphan = NULL; /* 3rd best candidate for eviction is the highest confirmed orphan.   */
     897           0 :   for( fd_forest_subtlist_iter_t iter = fd_forest_subtlist_iter_fwd_init( subtlist, pool );
     898           0 :                                        !fd_forest_subtlist_iter_done( iter, subtlist, pool );
     899           0 :                                  iter = fd_forest_subtlist_iter_fwd_next( iter, subtlist, pool ) ) {
     900           0 :     fd_forest_blk_t * ele = fd_forest_subtlist_iter_ele( iter, subtlist, pool );
     901           0 :     UPDATE_BEST_CANDIDATE( confirmed_orphan, unconfrmd_orphan, ele, ele->child != ULONG_MAX || ele->slot == parent_slot );
     902           0 :   }
     903           0 :   for( fd_forest_orphaned_iter_t iter = fd_forest_orphaned_iter_init( orphaned, pool );
     904           0 :                                        !fd_forest_orphaned_iter_done( iter, orphaned, pool );
     905           0 :                                  iter = fd_forest_orphaned_iter_next( iter, orphaned, pool ) ) {
     906           0 :     fd_forest_blk_t *  ele = fd_forest_orphaned_iter_ele( iter, orphaned, pool );
     907           0 :     UPDATE_BEST_CANDIDATE( confirmed_orphan, unconfrmd_orphan, ele, ele->child != ULONG_MAX || ele->slot == parent_slot );
     908           0 :   }
     909             : 
     910           0 :   fd_forest_blk_t * unconfrmd_leaf = NULL; /* 2nd best candidate for eviction is the highest unconfirmed leaf. */
     911           0 :   fd_forest_blk_t * confirmed_leaf = NULL; /* 4th best candidate for eviction is the highest confirmed leaf. */
     912           0 :   for( fd_forest_frontier_iter_t iter = fd_forest_frontier_iter_init( frontier, pool );
     913           0 :                                        !fd_forest_frontier_iter_done( iter, frontier, pool );
     914           0 :                                  iter = fd_forest_frontier_iter_next( iter, frontier, pool ) ) {
     915           0 :     fd_forest_blk_t * ele = fd_forest_frontier_iter_ele( iter, frontier, pool );
     916           0 :     UPDATE_BEST_CANDIDATE( confirmed_leaf, unconfrmd_leaf, ele, iter.ele_idx == forest->root || ele->slot == parent_slot );
     917           0 :   }
     918             : 
     919           0 :   if( FD_UNLIKELY( !unconfrmd_leaf && !confirmed_leaf && !unconfrmd_orphan && !confirmed_orphan ) ) {
     920             :     /* This can only happen 1 of two ways:
     921             :         1. One fork in orphans, and root is alone (common situation in
     922             :            catchup). The new slot's parent is the tip of the orphan
     923             :            fork.  Ignore the slot in this case.
     924             :         2. One long fork, and the new slot's parent is the tip of the
     925             :            fork. Force a root in this case. */
     926           0 :     if( fd_forest_orphaned_ele_query( orphaned, &parent_slot, NULL, pool ) ) return ULONG_MAX;
     927             : 
     928           0 :     ulong             new_root     = fd_forest_pool_ele( pool, forest->root )->child;
     929           0 :     fd_forest_blk_t * new_root_ele = new_root==fd_forest_pool_idx_null( pool ) ? NULL : fd_forest_pool_ele( pool, new_root );
     930             : 
     931           0 :     if( FD_UNLIKELY( !new_root_ele || !new_root_ele->chain_confirmed ) ) return ULONG_MAX;
     932             : 
     933           0 :     FD_LOG_INFO(( "[%s] forest force rooting on slot %lu", __func__, new_root_ele->slot ));
     934           0 :     ulong evicted_slot = fd_forest_pool_ele( pool, forest->root )->slot;
     935           0 :     fd_forest_publish( forest, new_root_ele->slot );
     936           0 :     return evicted_slot;
     937           0 :   }
     938           0 :   if( FD_UNLIKELY( unconfrmd_orphan )) {
     939           0 :     return clear_leaf( forest, unconfrmd_orphan->slot );
     940           0 :   }
     941           0 :   if( FD_UNLIKELY( unconfrmd_leaf )) {
     942           0 :     return clear_leaf( forest, unconfrmd_leaf->slot );
     943           0 :   }
     944           0 :   if( FD_UNLIKELY( confirmed_orphan )) {
     945           0 :     fd_forest_blk_t * parent = fd_forest_query( forest, parent_slot );
     946             :     /* Always accept a new orphan subtree root, as it could bring us
     947             :     closer to confirmation */
     948           0 :     if( !parent ) {
     949           0 :       return clear_leaf( forest, confirmed_orphan->slot );
     950           0 :     }
     951             : 
     952             :     /* While in general it's safe to evict a confirmed orphan, we don't
     953             :        want to evict them if this new slot is uselessly adding to a
     954             :        fork we KNOW isn't confirmed. i.e., if there is another fork in
     955             :        this subtree that isn't confirmed, but it's parent is parent_slot.
     956             : 
     957             :        Ex. We shouldn't evict a confirmed orphan leaf if the parent_slot
     958             :        is the other fork that is unconfirmed. Also can't evict a
     959             :        confirmed orphan if we are creating a new fork in the main tree
     960             :        that doesn't continue the singular confirmed fork.
     961             : 
     962             :        i.e. for any subtree:
     963             : 
     964             :         0 ── 1 ── 2 ── 3 (confirmed) ── 4(confirmed) ── 5 ── 6 ──> add 7 here is valid.
     965             :                                         └──> add 7 here is valid.
     966             :                        └──> add 7 here is invalid. */
     967           0 :     ulong subtree_root = forest->root;
     968           0 :     if( fd_forest_subtrees_ele_query( subtrees, &parent_slot, NULL, pool )  ||
     969           0 :         fd_forest_orphaned_ele_query( orphaned, &parent_slot, NULL, pool ) ) {
     970             :       /* if adding to an orphan, find the root of the orphan subtree. */
     971           0 :       fd_forest_blk_t * root = parent;
     972           0 :       while( FD_LIKELY( root->parent != ULONG_MAX ) ) {
     973           0 :         root = fd_forest_pool_ele( pool, root->parent );
     974           0 :       }
     975           0 :       subtree_root = fd_forest_pool_idx( pool, root );
     976           0 :     }
     977             : 
     978           0 :     fd_forest_blk_t * latest_confirmed_leaf = latest_confirmed_slot( forest, subtree_root );
     979           0 :     if( !latest_confirmed_leaf || latest_confirmed_leaf == gca( forest, latest_confirmed_leaf, parent )) {
     980           0 :       return clear_leaf( forest, confirmed_orphan->slot ); /* is not a useless new fork. */
     981           0 :     }
     982             :     /* is a useless new fork. */
     983           0 :     return ULONG_MAX;
     984           0 :   } else {
     985             :     /* Should never be evicting a confirmed leaf. This is only non-NULL
     986             :        if:
     987             :          (1) we have no orphans, and there's only two forks in the main
     988             :        tree, and the parent of the non confirmed fork is is our parent.
     989             :        in this case we should just ignore this insert. TODO: optionally
     990             :        we could evict the non confirmed fork if its a separate fork.
     991             :          (2) we could have one orphan fork where parent_slot is at the
     992             :        tip, and everything in main tree is confirmed. in this case we
     993             :        should also ignore this insert. */
     994           0 :     return ULONG_MAX;
     995           0 :   }
     996           0 : }
     997             : #undef UPDATE_BEST_CANDIDATE
     998             : 
     999             : static fd_forest_blk_t *
    1000           0 : acquire( fd_forest_t * forest, ulong slot, ulong parent_slot, ulong * evicted ) {
    1001           0 :   fd_forest_blk_t * pool = fd_forest_pool( forest );
    1002           0 :   if( FD_UNLIKELY( !fd_forest_pool_free( pool ) ) ) {
    1003           0 :     ulong evicted_ = evict( forest, slot, parent_slot );
    1004           0 :     if( FD_LIKELY( evicted )) *evicted = evicted_;
    1005           0 :     if( FD_UNLIKELY( evicted_ == ULONG_MAX ) ) {
    1006           0 :       return NULL;
    1007           0 :     }
    1008           0 :   }
    1009           0 :   fd_forest_blk_t * blk  = fd_forest_pool_ele_acquire( pool );
    1010           0 :   ulong             null = fd_forest_pool_idx_null( pool );
    1011             : 
    1012           0 :   blk->slot            = slot;
    1013           0 :   blk->parent_slot     = parent_slot;
    1014           0 :   blk->next            = null;
    1015           0 :   blk->parent          = null;
    1016           0 :   blk->child           = null;
    1017           0 :   blk->sibling         = null;
    1018           0 :   blk->orphan_seq      = ULONG_MAX;
    1019           0 :   blk->chain_confirmed = 0;
    1020             : 
    1021           0 :   blk->buffered_idx = UINT_MAX;
    1022           0 :   blk->complete_idx = UINT_MAX;
    1023             : 
    1024           0 :   ulong idxs_sz = fd_forest_blk_idxs_word_cnt( forest->shred_max )*sizeof(fd_forest_blk_idxs_t);
    1025           0 :   memset( fd_forest_blk_idxs  ( forest, blk ), 0, idxs_sz );
    1026           0 :   blk->lowest_verified_fec = UINT_MAX;
    1027           0 :   memset( fd_forest_blk_mroots( forest, blk ), 0, (forest->shred_max/FD_FEC_SHRED_CNT)*sizeof(fd_forest_mr_t) ); /* expensive*/
    1028           0 :   blk->confirmed_bid = empty_mr;
    1029           0 :   memset( fd_forest_blk_recv( forest, blk ), 0, (forest->shred_max/FD_FEC_SHRED_CNT)*sizeof(fd_forest_recv_t) );
    1030             : 
    1031             :   /* Metrics tracking */
    1032             : 
    1033           0 :   memset( fd_forest_blk_code( forest, blk ), 0, idxs_sz );
    1034           0 :   blk->first_shred_ts = 0;
    1035           0 :   blk->last_shred_ts  = 0;
    1036           0 :   blk->first_req_ts   = 0;
    1037           0 :   blk->last_repair_resp_ts = 0;
    1038           0 :   blk->turbine_cnt    = 0;
    1039           0 :   blk->repair_cnt     = 0;
    1040           0 :   blk->recovered_cnt  = 0;
    1041           0 :   blk->req_window_cnt  = 0;
    1042           0 :   blk->req_highest_cnt = 0;
    1043           0 :   blk->req_orphan_cnt  = 0;
    1044           0 :   blk->req_retransmit_cnt = 0;
    1045           0 :   blk->response_cnt    = 0;
    1046           0 :   blk->chain_verify_failed = 0;
    1047             : 
    1048           0 :   return blk;
    1049           0 : }
    1050             : 
    1051             : fd_forest_blk_t *
    1052           0 : fd_forest_blk_insert( fd_forest_t * forest, ulong slot, ulong parent_slot, ulong * evicted ) {
    1053           0 :   FD_CHECK_CRIT( slot > fd_forest_root_slot( forest ), "invalid argument" );
    1054             : 
    1055           0 :   fd_forest_ancestry_t * ancestry = fd_forest_ancestry( forest );
    1056           0 :   fd_forest_frontier_t * frontier = fd_forest_frontier( forest );
    1057           0 :   fd_forest_subtrees_t * subtrees = fd_forest_subtrees( forest );
    1058           0 :   fd_forest_subtlist_t * subtlist = fd_forest_subtlist( forest );
    1059           0 :   fd_forest_orphaned_t * orphaned = fd_forest_orphaned( forest );
    1060           0 :   fd_forest_consumed_t * consumed = fd_forest_consumed( forest );
    1061           0 :   fd_forest_ref_t *      conspool = fd_forest_conspool( forest );
    1062           0 :   fd_forest_requests_t * requests = fd_forest_requests( forest );
    1063           0 :   fd_forest_ref_t *      reqspool = fd_forest_reqspool( forest );
    1064           0 :   fd_forest_blk_t *      pool     = fd_forest_pool    ( forest );
    1065           0 :   ulong *                bfs      = fd_forest_deque( forest );
    1066           0 :   ulong                  null     = fd_forest_pool_idx_null( pool );
    1067             : 
    1068           0 :   fd_forest_blk_t * ele = fd_forest_query( forest, slot );
    1069           0 :   if( FD_LIKELY( ele ) ) {
    1070             :     /* May need to update the parent_slot, if this
    1071             :        this was a sentinel block that was created for a confirmed msg.
    1072             :        A parent update for a sentinel block only occurs once.  This
    1073             :        is separate from the parent update for a confirmed equivocating
    1074             :        block. */
    1075           0 :     if( FD_UNLIKELY( ele->parent_slot == ULONG_MAX && parent_slot != ULONG_MAX ) ) {
    1076           0 :       ele->parent_slot = parent_slot;
    1077           0 :       FD_TEST( fd_forest_subtrees_ele_query( subtrees, &slot, NULL, pool ) || fd_forest_orphaned_ele_query( orphaned, &slot, NULL, pool ) );
    1078           0 :       subtrees_orphaned_remove( forest, slot ); // if this is a sentinel block, then it must be orphaned
    1079             :       /* The sentinel was an orphan subtree head, so it is in the orphan
    1080             :          requests list.  Now that its parent is known it will be re-linked
    1081             :          below and may join the main tree (frontier) or become an interior
    1082             :          orphan, neither of which belongs in the orphan requests list.  Drop
    1083             :          the orphan request entry now; if it ends up a subtree head again the
    1084             :          subtrees branch below will re-add it.  Failing to remove it here
    1085             :          leaves the same pool idx in both the orphan and main request maps
    1086             :          (which share a single reqspool), corrupting both lists. */
    1087           0 :       requests_remove( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), &forest->orphiter, fd_forest_pool_idx( pool, ele ) );
    1088           0 :     } else {
    1089           0 :       return ele;
    1090           0 :     }
    1091           0 :   } else {
    1092           0 :     ele = acquire( forest, slot, parent_slot, evicted );
    1093           0 :     if( FD_UNLIKELY( !ele ) ) return NULL; /* no space in pool, so we can't add this slot */
    1094           0 :   }
    1095             : 
    1096           0 :   fd_forest_blk_t * parent = NULL;
    1097             : 
    1098           0 :   if(        FD_LIKELY  ( parent = fd_forest_ancestry_ele_query ( ancestry, &parent_slot, NULL, pool ) ) ) { /* parent is in ancestry, ele makes new frontier */
    1099           0 :     fd_forest_frontier_ele_insert( frontier, ele, pool );
    1100           0 :   } else if( FD_UNLIKELY( parent = fd_forest_frontier_ele_remove( frontier, &parent_slot, NULL, pool ) ) ) { /* parent is in frontier, ele makes new frontier */
    1101           0 :     fd_forest_ancestry_ele_insert( ancestry, parent, pool );
    1102           0 :     fd_forest_frontier_ele_insert( frontier, ele,    pool );
    1103           0 :   } else if( FD_UNLIKELY( parent = fd_forest_orphaned_ele_query ( orphaned, &parent_slot, NULL, pool ) ) ) { /* parent is in orphaned, ele makes new orphaned */
    1104           0 :     fd_forest_orphaned_ele_insert( orphaned, ele, pool );
    1105           0 :   } else if( FD_UNLIKELY( parent = fd_forest_subtrees_ele_query ( subtrees, &parent_slot, NULL, pool ) ) ) { /* parent is in subtrees, ele makes new orphaned */
    1106           0 :     fd_forest_orphaned_ele_insert( orphaned, ele, pool );
    1107           0 :   } else {                                                                                                   /* parent is not in any map, ele makes new subtree */
    1108           0 :     subtrees_insert( forest, ele );
    1109             : 
    1110           0 :     requests_insert( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), fd_forest_pool_idx( pool, ele ) );
    1111           0 :   }
    1112             : 
    1113           0 :   if( FD_LIKELY( parent ) ) link( forest, parent, ele );
    1114             : 
    1115             :   /* Iterate subtrees and connect ones where the parent slot matches up
    1116             :      to the new ele.*/
    1117             : 
    1118           0 :   for( fd_forest_subtlist_iter_t iter = fd_forest_subtlist_iter_fwd_init( subtlist, pool );
    1119           0 :        !fd_forest_subtlist_iter_done( iter, subtlist, pool );
    1120           0 :        iter = fd_forest_subtlist_iter_fwd_next( iter, subtlist, pool ) ) {
    1121           0 :     fd_forest_blk_t * orphan = fd_forest_subtlist_iter_ele( iter, subtlist, pool );
    1122             :     // edge case where for a sentinel node the parent_slot == slot, so we want to avoid linking it to itself
    1123           0 :     if( FD_LIKELY( orphan->slot != ele->slot ) ) fd_forest_deque_push_tail( bfs, fd_forest_pool_idx( pool, orphan ) );
    1124           0 :   }
    1125           0 :   while( FD_LIKELY( fd_forest_deque_cnt( bfs ) ) ) {
    1126           0 :     fd_forest_blk_t * orphan = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( bfs ) );
    1127           0 :     if( FD_UNLIKELY( orphan->parent_slot == ele->slot ) ) {
    1128           0 :       link( forest, ele, orphan );
    1129           0 :       fd_forest_subtrees_ele_remove( subtrees, &orphan->slot, NULL, pool );
    1130           0 :       fd_forest_subtlist_ele_remove( fd_forest_subtlist( forest ), orphan, pool );
    1131           0 :       requests_remove( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), &forest->orphiter, fd_forest_pool_idx( pool, orphan ) );
    1132           0 :       fd_forest_orphaned_ele_insert( orphaned, orphan,              pool );
    1133           0 :     }
    1134           0 :   }
    1135             : 
    1136             :   /* At this point we are in the state where:
    1137             : 
    1138             :     ele      < in frontier/subtrees/orphaned >
    1139             :      |
    1140             :     children < all in orphaned >
    1141             : 
    1142             :     if ele is in frontier, we need to extend the frontier from this child.
    1143             :     if ele is in orphaned/subtrees, we are done. don't do anything, */
    1144             : 
    1145           0 :   if( FD_LIKELY( fd_forest_frontier_ele_query( frontier, &ele->slot, NULL, pool ) ) ) fd_forest_deque_push_tail( bfs, fd_forest_pool_idx( pool, ele ) );
    1146           0 :   while( FD_LIKELY( !fd_forest_deque_empty( bfs ) ) ) {
    1147           0 :     fd_forest_blk_t * parent = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( bfs ) );
    1148           0 :     fd_forest_blk_t * child  = fd_forest_pool_ele( pool, parent->child );
    1149           0 :     if( FD_LIKELY( child ) ) {
    1150           0 :       fd_forest_frontier_ele_remove( frontier, &parent->slot, NULL, pool );
    1151           0 :       fd_forest_ancestry_ele_insert( ancestry, parent,              pool );
    1152           0 :     }
    1153           0 :     while( FD_LIKELY( child ) ) {
    1154           0 :       fd_forest_orphaned_ele_remove( orphaned, &child->slot, NULL, pool );
    1155           0 :       requests_remove( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), &forest->orphiter, fd_forest_pool_idx( pool, child ) );
    1156           0 :       fd_forest_frontier_ele_insert( frontier, child,              pool );
    1157           0 :       fd_forest_deque_push_tail( bfs, fd_forest_pool_idx( pool, child ) );
    1158           0 :       child = fd_forest_pool_ele( pool, child->sibling );
    1159           0 :     }
    1160           0 :   }
    1161             : 
    1162           0 :   if( FD_LIKELY( fd_forest_ancestry_ele_query( ancestry, &ele->slot, NULL, pool ) ||
    1163           0 :                  fd_forest_frontier_ele_query( frontier, &ele->slot, NULL, pool ) ) ) {
    1164             :     /* There is a chance that we connected this ele to the main tree. If
    1165             :        this ele doesn't have a parent in the consumed/requests map, add it to the
    1166             :        consumed/requests map. */
    1167           0 :     ulong ancestor = fd_forest_pool_idx( pool, ele );
    1168           0 :     int   has_requests_anc = 0;
    1169           0 :     int   has_consumed_anc = 0;
    1170           0 :     while( ancestor != null && (!has_requests_anc || !has_consumed_anc) ) {
    1171           0 :       if( fd_forest_consumed_ele_query( consumed, &ancestor, NULL, conspool ) ) has_consumed_anc = 1;
    1172           0 :       if( fd_forest_requests_ele_query( requests, &ancestor, NULL, reqspool ) ) has_requests_anc = 1;
    1173           0 :       ancestor = fd_forest_pool_ele( pool, ancestor )->parent;
    1174           0 :     }
    1175           0 :     if( FD_UNLIKELY( !has_requests_anc ) ) {
    1176           0 :       requests_insert( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), fd_forest_pool_idx( pool, ele ) );
    1177             :       /* we want to remove any children than are in the requests list. This isn't necessary during any regular boot.
    1178             :          However if we are booting from very far behind (>30k slots), the requests list will be very large and in
    1179             :          nearly reverse order.  */
    1180           0 :       ulong * queue = fd_forest_deque( forest );
    1181           0 :       fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, ele ) );
    1182           0 :       while( FD_LIKELY( fd_forest_deque_cnt( queue ) ) ) {
    1183           0 :         fd_forest_blk_t * child = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) );
    1184           0 :         if( FD_LIKELY( child != ele ) ) {
    1185           0 :           requests_remove( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), &forest->iter, fd_forest_pool_idx( pool, child ) );
    1186           0 :         }
    1187           0 :         child = fd_forest_pool_ele( pool, child->child );
    1188           0 :         while( FD_LIKELY( child ) ) {
    1189           0 :           fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) );
    1190           0 :           child = fd_forest_pool_ele( pool, child->sibling );
    1191           0 :         }
    1192           0 :       }
    1193           0 :     }
    1194           0 :     if( FD_UNLIKELY( !has_consumed_anc ) ) consumed_insert( forest, fd_forest_pool_idx( pool, ele ) );
    1195           0 :   }
    1196           0 :   return ele;
    1197           0 : }
    1198             : 
    1199             : /* Updates a forest_blk_t's parent, which requires updates to the blk
    1200             :    itself, the blk's old parent, the new parent, and all its
    1201             :    descendants.  The blk is released and re-inserted fresh
    1202             :    under the new parent: all recorded state (shred idxs, merkle roots,
    1203             :    verification progress) is discarded except confirmed_bid, on the
    1204             :    assumption it belonged to a different version of the slot.  Returns
    1205             :    the new ele; the old pointer must not be reused. */
    1206             : static fd_forest_blk_t *
    1207           0 : verified_parent_update( fd_forest_t * forest, fd_forest_blk_t * ele, ulong parent_slot ) {
    1208           0 :   fd_forest_blk_t * pool          = fd_forest_pool( forest );
    1209           0 :   fd_hash_t         confirmed_bid = ele->confirmed_bid; /* save confirmation status for re-insertion */
    1210             : 
    1211             :   /* remove from maps, unlink from old parent. children orphaned. */
    1212           0 :   remove_and_unlink( forest, ele );
    1213             : 
    1214           0 :   ulong slot = ele->slot;
    1215           0 :   fd_forest_pool_ele_release( pool, ele );
    1216             : 
    1217             :   /* ele is now gone. blk_insert it! and then restore saved verified state */
    1218             : 
    1219           0 :   fd_forest_blk_t * new_ele = fd_forest_blk_insert( forest, slot, parent_slot, NULL );
    1220           0 :   new_ele->confirmed_bid = confirmed_bid;
    1221             : 
    1222           0 :   return new_ele;
    1223           0 : }
    1224             : 
    1225             : static inline int
    1226           0 : merkle_recvd( fd_forest_mr_t const * mroots, uint fec_idx ) {
    1227           0 :   return memcmp( &mroots[fec_idx].mr, &empty_mr, sizeof(fd_hash_t) ) != 0;
    1228           0 : }
    1229             : 
    1230             : /* returns 1 if the FEC set after the given FEC set has been confirmed */
    1231             : static inline int
    1232           0 : next_merkle_confirmed( fd_forest_blk_t * ele, uint fec_idx ) {
    1233             :   // not possible for anything to be verified if the slot doesn't know the last index
    1234           0 :   if( ele->complete_idx == UINT_MAX ) return 0;
    1235             :   /* if we are asking about the block_id, it's stored in the confirmed_bid field */
    1236           0 :   if( FD_UNLIKELY( fec_idx == (ele->complete_idx / 32UL) ) ) {
    1237           0 :     return !fd_hash_eq( &ele->confirmed_bid, &empty_mr );
    1238           0 :   }
    1239           0 :   return ele->lowest_verified_fec <= (fec_idx + 1UL);
    1240           0 : }
    1241             : 
    1242             : /* Returns the chained merkle root that commits to the given FEC set.
    1243             :    Only meaningful when next_merkle_confirmed() is true; otherwise the
    1244             :    returned cmr may be uninitialized. For most FEC sets this is
    1245             :    mroots[fec_idx+1].cmr.  For the last FEC in the slot (fec_idx ==
    1246             :    complete_idx/32) it is confirmed_bid.
    1247             : 
    1248             :    Guard against OOB read: an attacker can send a fec_idx beyond
    1249             :    complete_idx. The shred gets filtered upstream, so we don't need to
    1250             :    guard against it here. */
    1251             : 
    1252             : static inline fd_hash_t *
    1253           0 : next_chained_merkle( fd_forest_blk_t * ele, fd_forest_mr_t * mroots, uint fec_idx ) {
    1254           0 :   if( FD_UNLIKELY( fec_idx == ele->complete_idx / 32UL ) ) {
    1255           0 :     return &ele->confirmed_bid;
    1256           0 :   }
    1257           0 :   return &mroots[fec_idx + 1].cmr;
    1258           0 : }
    1259             : 
    1260             : /* recv_ts_stamp records the first time turbine was observed reaching
    1261             :    fec_idx, as a ms offset from first_shred_ts, and backfills any
    1262             :    still-unstamped FEC sets below it: first_shred_ts must
    1263             :    already be set. */
    1264             : 
    1265             : static void
    1266           0 : recv_ts_stamp( fd_forest_t * forest, fd_forest_blk_t * ele, uint fec_idx, long now ) {
    1267           0 :   fd_forest_recv_t * recv = fd_forest_blk_recv( forest, ele );
    1268           0 :   if( FD_LIKELY( recv[ fec_idx ].first ) ) return;
    1269           0 :   now = fd_long_max( now, ele->first_shred_ts );
    1270           0 :   ulong  off_ms = (ulong)( (double)(now - ele->first_shred_ts) / fd_tempo_tick_per_ns( NULL ) * 1e-6 );
    1271           0 :   ushort stamp  = (ushort)fd_ulong_min( fd_ulong_max( off_ms, 1UL ), (ulong)USHORT_MAX ); /* clamp to >=1: 0 means unstamped */
    1272           0 :   recv[ fec_idx ].first = stamp;
    1273           0 :   for( uint i=fec_idx; i>0U && !recv[ i-1U ].first; i-- ) recv[ i-1U ].first = stamp;
    1274           0 : }
    1275             : 
    1276             : /* data_shred_insert accepts the first complete_idx it sees while
    1277             :    complete_idx is UINT_MAX, and rejects any subsequent shreds that are
    1278             :    greater than the complete_idx.  This is applies for the very first
    1279             :    slot_complete seen (could be incorrect), or after the complete_idx has
    1280             :    been cleared by fec_clear due to an incorrect FEC. */
    1281             : 
    1282             : fd_forest_blk_t *
    1283             : fd_forest_data_shred_insert( fd_forest_t * forest,
    1284             :                              ulong         slot,
    1285             :                              ulong         parent_slot,
    1286             :                              uint          shred_idx,
    1287             :                              uint          fec_set_idx,
    1288             :                              int           slot_complete,
    1289             :                              int           ref_tick FD_PARAM_UNUSED,
    1290             :                              int           src,
    1291             :                              fd_hash_t   * mr,
    1292             :                              fd_hash_t   * cmr,
    1293           0 :                              long          rx_tick ) {
    1294           0 :   FD_TEST( shred_idx < forest->shred_max ); /* guaranteed by fec_resolver */
    1295           0 :   fd_forest_blk_t * ele = fd_forest_query( forest, slot );
    1296           0 :   FD_CHECK_ERR( !!ele, "ele is not in the forest. data_shred_insert should be preceded by blk_insert" );
    1297           0 :   fd_forest_mr_t * mroots = fd_forest_blk_mroots( forest, ele );
    1298             : 
    1299             :   /* Pre-filtering on merkle root.
    1300             :      If we have knowledge of the confirmed merkle root, we can reject
    1301             :      shreds that don't match it.  Else, we'll accept any and all shreds,
    1302             :      and invalidating the merkle root if we see more than 1 version of
    1303             :      the FEC. */
    1304             : 
    1305           0 :   uint fec_idx = fec_set_idx / 32UL;
    1306             : 
    1307             :   /* We can automatically reject if the shred index is greater than the
    1308             :      complete index, as it clearly signifies some duplicity is
    1309             :      occurring.  What if this shred is part of the canonical chain
    1310             :      though?  When the duplicate confirmation arrives from tower, the
    1311             :      false complete_idx will be cleared, and shreds higher than the
    1312             :      false complete_idx will be accepted. */
    1313             : 
    1314           0 :   if( FD_UNLIKELY( shred_idx > ele->complete_idx ) ) {
    1315           0 :     FD_LOG_WARNING(( "[%s] slot %lu shred index %u is greater than known complete_idx %u. rejecting shred", __func__, slot, shred_idx, ele->complete_idx ));
    1316           0 :     return NULL;
    1317           0 :   }
    1318             : 
    1319             :   /* If this is a slot_complete shred and we know the confirmed
    1320             :      block_id, we can immediately verify or reject.  This check is
    1321             :      independent of the complete_idx / lowest_verified_fec state, so it
    1322             :      covers the case after fec_clear resets those fields. */
    1323             : 
    1324           0 :   if( FD_UNLIKELY( slot_complete && !fd_hash_eq( &ele->confirmed_bid, &empty_mr ) ) ) {
    1325           0 :     if( FD_UNLIKELY( !fd_hash_eq( &ele->confirmed_bid, mr ) ) ) return NULL; /* wrong version */
    1326           0 :     if( FD_UNLIKELY( ele->parent_slot != parent_slot ) ) { ele = verified_parent_update( forest, ele, parent_slot ); mroots = fd_forest_blk_mroots( forest, ele ); }
    1327           0 :     ele->lowest_verified_fec = fec_idx; /* last FEC verified */
    1328           0 :     mroots[fec_idx].mr  = *mr;
    1329           0 :     mroots[fec_idx].cmr = *cmr;
    1330           0 :   }
    1331             : 
    1332             :   /* Otherwise if this is any other shred and we know the verification
    1333             :      status, we can immediately verify or reject. */
    1334             : 
    1335           0 :   if( FD_UNLIKELY( next_merkle_confirmed( ele, fec_idx ) ) ) { /* if the cmr pointing to this FEC has been confirmed, then... */
    1336           0 :     if( FD_UNLIKELY( !fd_hash_eq( next_chained_merkle( ele, mroots, fec_idx ), mr ) ) ) {
    1337             :       /* merkle root doesn't match the verified CMR  */
    1338           0 :       return NULL; /* do not accept this shred. */
    1339           0 :     } else {
    1340             : 
    1341             :       /* A validated mr, but the parent slot is wrong.  This means we
    1342             :          initially received the wrong version of the slot that also
    1343             :          had a different parent slot.  We need to update the parent
    1344             :          slot to the correct one. */
    1345             : 
    1346           0 :       if( FD_UNLIKELY( ele->parent_slot != parent_slot ) ) { ele = verified_parent_update( forest, ele, parent_slot ); mroots = fd_forest_blk_mroots( forest, ele ); }
    1347           0 :       mroots[fec_idx].mr  = *mr;
    1348           0 :       mroots[fec_idx].cmr = *cmr;
    1349             : 
    1350           0 :     }
    1351           0 :   } else { /* No verification / knowledge of canonical merkle root */
    1352           0 :     if( FD_UNLIKELY( !merkle_recvd( mroots, fec_idx ) ) ) {
    1353             : 
    1354             :       /* On first mr received for FEC set 0, adopt the shred's parent.
    1355             :          Otherwise later confirmation would chain verify into the wrong
    1356             :          ancestor. */
    1357             : 
    1358           0 :       if( FD_UNLIKELY( ele->parent_slot != parent_slot && fec_idx == 0 ) ) { ele = verified_parent_update( forest, ele, parent_slot ); mroots = fd_forest_blk_mroots( forest, ele ); }
    1359             : 
    1360           0 :       mroots[fec_idx].mr  = *mr;
    1361           0 :       mroots[fec_idx].cmr = *cmr;
    1362           0 :     } else {
    1363             :       /* verify that the received merkle root is consistent with the current merkle root.
    1364             :          No need to check the cmr, because matching mr implies matching cmr. */
    1365           0 :       fd_hash_t * current_mr = &mroots[fec_idx].mr;
    1366           0 :       if( FD_UNLIKELY( !fd_hash_eq( current_mr, mr ) ) ) {
    1367           0 :         FD_BASE58_ENCODE_32_BYTES( current_mr->key, current_mr_b58 ); FD_BASE58_ENCODE_32_BYTES( mr->key, mr_b58 );
    1368           0 :         FD_LOG_INFO(( "[%s] multiple versions detected for slot %lu fec set %u, invalidating. current_mr %s, received_mr %s", __func__, slot, fec_set_idx, current_mr_b58, mr_b58 ));
    1369           0 :         mroots[fec_idx].mr = invalid_mr; /* invalidate the merkle root */
    1370           0 :       }
    1371           0 :     }
    1372           0 :   }
    1373             : 
    1374             :   /* Shred accepted, merkle root verified (as much as possible). */
    1375             : 
    1376           0 :   if( FD_UNLIKELY( slot_complete && ele->complete_idx != UINT_MAX && shred_idx != ele->complete_idx ) ) {
    1377             :     /* It is always beneficial for us to take the minimum slot complete
    1378             :        index because this enables the chain verification to start
    1379             :        earlier. */
    1380           0 :     FD_LOG_WARNING(( "[%s] slot %lu shred_idx %u is slot_complete, but recorded complete_idx is %u. Updating complete_idx to %u", __func__, slot, shred_idx, ele->complete_idx, shred_idx ));
    1381           0 :   }
    1382           0 :   ele->complete_idx = fd_uint_if( slot_complete, shred_idx, ele->complete_idx );
    1383             : 
    1384           0 :   fd_forest_blk_idxs_t * idxs = fd_forest_blk_idxs( forest, ele );
    1385           0 :   if( !fd_forest_blk_idxs_test( idxs, shred_idx ) ) { /* newly seen shred */
    1386           0 :     ele->turbine_cnt   += (src==SHRED_SRC_TURBINE);
    1387           0 :     ele->repair_cnt    += (src==SHRED_SRC_REPAIR);
    1388           0 :     ele->recovered_cnt += (src==SHRED_SRC_RECOVERED);
    1389           0 :   }
    1390           0 :   if( FD_UNLIKELY( !ele->first_shred_ts || rx_tick<ele->first_shred_ts ) ) ele->first_shred_ts = rx_tick;
    1391           0 :   if( FD_LIKELY( src==SHRED_SRC_TURBINE ) ) recv_ts_stamp( forest, ele, fec_idx, rx_tick );
    1392             : 
    1393           0 :   fd_forest_blk_idxs_insert( idxs, shred_idx );
    1394           0 :   while( ele->buffered_idx + 1 < forest->shred_max && fd_forest_blk_idxs_test( idxs, ele->buffered_idx + 1U ) ) {
    1395           0 :     ele->buffered_idx++;
    1396           0 :   }
    1397             : 
    1398             :   /* If equivocating, buffered_idx needs to be clamped to complete_idx */
    1399           0 :   if( FD_UNLIKELY( ele->buffered_idx != UINT_MAX && ele->buffered_idx > ele->complete_idx ) ) ele->buffered_idx = ele->complete_idx;
    1400             : 
    1401           0 :   if( FD_UNLIKELY( !ele->last_shred_ts && ele->complete_idx!=UINT_MAX && ele->buffered_idx==ele->complete_idx ) ) {
    1402           0 :     ele->last_shred_ts = rx_tick;
    1403           0 :   }
    1404             : 
    1405           0 :   advance_consumed_frontier( forest, slot, parent_slot );
    1406           0 :   return ele;
    1407           0 : }
    1408             : 
    1409             : fd_forest_blk_t *
    1410           0 : fd_forest_fec_insert( fd_forest_t * forest, ulong slot, ulong parent_slot, uint last_shred_idx, uint fec_set_idx, int slot_complete, int ref_tick, fd_hash_t * mr, fd_hash_t * cmr, long rx_tick ) {
    1411           0 :   FD_TEST( last_shred_idx < forest->shred_max ); /* guaranteed by fec_resolver */
    1412             : 
    1413           0 :   fd_forest_blk_t * ele = fd_forest_query( forest, slot );
    1414           0 :   FD_CHECK_ERR( !!ele, "ele is not in the forest. fec_insert should be preceded by blk_insert" );
    1415           0 :   fd_forest_mr_t * mroots = fd_forest_blk_mroots( forest, ele );
    1416             : 
    1417           0 :   uint fec_idx = fec_set_idx / 32UL; /* index into merkle root array */
    1418             : 
    1419             :   /* if the FEC set is beyond the complete_idx, then we reject the FEC */
    1420           0 :   if( FD_UNLIKELY( fec_set_idx > ele->complete_idx ) ) {
    1421           0 :     FD_LOG_WARNING(( "[%s] slot %lu fec set %u is greater than known complete_idx %u. rejecting FEC", __func__, slot, fec_set_idx, ele->complete_idx ));
    1422           0 :     return NULL;
    1423           0 :   }
    1424             : 
    1425             :   /* reject if the fec is confirmed and the merkle root doesn't match */
    1426           0 :   if( FD_UNLIKELY( next_merkle_confirmed( ele, fec_idx ) && !fd_hash_eq( next_chained_merkle( ele, mroots, fec_idx ), mr ) ) ) return NULL;
    1427             : 
    1428           0 :   if( FD_UNLIKELY( merkle_recvd( mroots, fec_idx ) && !fd_hash_eq( &mroots[fec_idx].mr, mr ) ) ) {
    1429             :     /* overwrite the merkle root with the new one */
    1430           0 :     FD_BASE58_ENCODE_32_BYTES( mroots[fec_idx].mr.key, mr_b58 );
    1431           0 :     FD_BASE58_ENCODE_32_BYTES( mr->key, mr_recv_b58 );
    1432           0 :     FD_LOG_WARNING(( "[%s] received a version of slot %lu fec_set_idx %u that isn't recorded. current_mr %s, received_mr %s", __func__, slot, fec_set_idx, mr_b58, mr_recv_b58 ));
    1433             : 
    1434             :     /* Overwriting FEC 0's merkle root means we've adopted a different
    1435             :        version of the slot, whose first shred may name a different
    1436             :        parent. Re-link to that parent so chained verification walks the
    1437             :        correct ancestor, instead of walking into the previously-linked
    1438             :        slot. */
    1439             : 
    1440           0 :     if( FD_UNLIKELY( fec_idx == 0 && ele->parent_slot != parent_slot ) ) {
    1441           0 :       ele    = verified_parent_update( forest, ele, parent_slot );
    1442           0 :       mroots = fd_forest_blk_mroots( forest, ele );
    1443           0 :     }
    1444             :     /* there are two cases:
    1445             :         (1) the first and common case is that we've received a mix of
    1446             :         shreds from equivocating FEC siblings A & B.  In forest we have
    1447             :         recorded hash = { invalid_mr } for this fec set because we've
    1448             :         received a mix of merkle roots, so we nulled the FEC set. Let's
    1449             :         say fec_resolver then completes version B, and delivers it.  We
    1450             :         can safely overwrite our null merkle root with B because we know
    1451             :         we must've received all the data for version B!
    1452             : 
    1453             :         (2) the second case is that we get two FEC completion msgs: one
    1454             :         for both version B and A. They get completed, one after the
    1455             :         other.  We first overwrite from { invalid_mr } to B.  But if
    1456             :         version A arrives, what should we do?  If B is the correct
    1457             :         version, but we choose to overwrite the fec when A arrive, then
    1458             :         we need to ask shred to re-deliver the FEC set. Since we
    1459             :         don't know at this time if B or A is correct, we optimize for
    1460             :         case 1, and overwrite the merkle root with the new one. */
    1461           0 :     mroots[fec_idx].mr  = *mr;
    1462           0 :     mroots[fec_idx].cmr = *cmr;
    1463           0 :   }
    1464             : 
    1465           0 :   if( FD_UNLIKELY( slot_complete && ele->child != ULONG_MAX ) ) {
    1466             :     /* check for a child that is confirmed */
    1467           0 :     fd_forest_blk_t * child = fd_forest_pool_ele( fd_forest_pool( forest ), ele->child );
    1468           0 :     while( FD_UNLIKELY( child ) ) {
    1469           0 :       if( FD_UNLIKELY( child->chain_confirmed ) ) {
    1470           0 :         ele->confirmed_bid = fd_forest_blk_mroots( forest, child )[0].cmr;
    1471           0 :         break;
    1472           0 :       }
    1473           0 :       child = fd_forest_pool_ele( fd_forest_pool( forest ), child->sibling );
    1474           0 :     }
    1475           0 :   }
    1476             : 
    1477             :   /* It's important that we set the cmpl idx here. If this happens to be
    1478             :      the last fec_complete we needed to finish the slot, then we rely on
    1479             :      the advance_consumed_frontier call in the below data_shred_insert
    1480             :      to move forward the consumed frontier.  */
    1481           0 :   for( uint idx = fec_set_idx; idx <= last_shred_idx; idx++ ) {
    1482           0 :     ele = fd_forest_data_shred_insert( forest, slot, parent_slot, idx, fec_set_idx, slot_complete & (idx == last_shred_idx), ref_tick, SHRED_SRC_RECOVERED, mr, cmr, rx_tick );
    1483           0 :   }
    1484           0 :   return ele;
    1485           0 : }
    1486             : 
    1487             : fd_forest_blk_t *
    1488           0 : fd_forest_code_shred_insert( fd_forest_t * forest, ulong slot, uint shred_idx, long rx_tick ) {
    1489           0 :   fd_forest_blk_t * ele  = fd_forest_query( forest, slot );
    1490           0 :   if( FD_UNLIKELY( !ele ) ) {
    1491           0 :     return NULL;
    1492           0 :   }
    1493           0 :   if( FD_UNLIKELY( !ele->first_shred_ts || rx_tick<ele->first_shred_ts ) ) ele->first_shred_ts = rx_tick;
    1494             : 
    1495           0 :   if( FD_UNLIKELY( shred_idx >= forest->shred_max ) ) {
    1496           0 :     ele->turbine_cnt += 1;
    1497           0 :     return ele;
    1498           0 :   }
    1499             : 
    1500           0 :   recv_ts_stamp( forest, ele, (uint)(shred_idx/FD_FEC_SHRED_CNT), rx_tick );
    1501             : 
    1502           0 :   fd_forest_blk_idxs_t * code = fd_forest_blk_code( forest, ele );
    1503           0 :   if( FD_LIKELY( !fd_forest_blk_idxs_test( code, shred_idx ) ) ) { /* newly seen shred */
    1504           0 :     ele->turbine_cnt += 1;
    1505           0 :     fd_forest_blk_idxs_insert( code, shred_idx );
    1506           0 :   }
    1507           0 :   return ele;
    1508           0 : }
    1509             : 
    1510             : fd_forest_blk_t *
    1511           0 : fd_forest_fec_chain_verify( fd_forest_t * forest, fd_forest_blk_t * ele, fd_hash_t const * bid ) {
    1512           0 :   uint fec_idx = ele->complete_idx / 32UL;
    1513             : 
    1514           0 :   ele->confirmed_bid            = *bid; /* confirmed */
    1515           0 :   fd_hash_t const * expected_mr = bid;
    1516             : 
    1517           0 :   while( FD_UNLIKELY( !ele->chain_confirmed ) ) {
    1518           0 :     fd_forest_mr_t * mroots = fd_forest_blk_mroots( forest, ele );
    1519           0 :     if( FD_UNLIKELY(  fd_hash_eq( &mroots[fec_idx].mr, &empty_mr   ) ) ) return NULL; /* can't verify the chain further */
    1520           0 :     if( FD_UNLIKELY( !fd_hash_eq( expected_mr, &mroots[fec_idx].mr ) ) ) return ele;
    1521             : 
    1522             :     /* This FEC merkle is correct, and the chained merkle is correct. */
    1523           0 :     ele->lowest_verified_fec = fec_idx;
    1524           0 :     expected_mr = &mroots[fec_idx].cmr;
    1525             : 
    1526           0 :     if( FD_UNLIKELY( fec_idx==0 ) ) {
    1527             :       /* hop to the parent slot, but first we've made it through this
    1528             :          slot successfully verifying the chain! mark it confirmed! */
    1529           0 :       ele->chain_confirmed = 1;
    1530           0 :       FD_LOG_DEBUG(( "[%s] confirmed full slot %lu", __func__, ele->slot ));
    1531           0 :       ele = fd_forest_pool_ele( fd_forest_pool( forest ), ele->parent );
    1532             : 
    1533           0 :       if( FD_UNLIKELY( !ele ) ) return NULL; /* can't verify the chain further */
    1534             : 
    1535           0 :       ele->confirmed_bid = *expected_mr; /* CMR of child slot */
    1536           0 :       if( FD_UNLIKELY( ele->complete_idx == UINT_MAX ) ) return NULL; /* can't verify the chain further */
    1537             : 
    1538           0 :       fec_idx = ele->complete_idx / 32UL;
    1539           0 :       continue;
    1540           0 :     }
    1541           0 :     fec_idx--; /* go back one FEC set */
    1542           0 :   }
    1543           0 :   return NULL;
    1544           0 : }
    1545             : 
    1546             : void
    1547           0 : fd_forest_fec_clear( fd_forest_t * forest, ulong slot, uint fec_set_idx, uint max_shred_idx ) {
    1548           0 :   if( FD_UNLIKELY( slot <= fd_forest_root_slot( forest ) ) ) return;
    1549           0 :   FD_TEST( (ulong)fec_set_idx+max_shred_idx<forest->shred_max );
    1550           0 :   fd_forest_blk_t * ele = fd_forest_query( forest, slot );
    1551           0 :   if( FD_UNLIKELY( !ele ) ) return;
    1552             : 
    1553           0 :   fd_forest_blk_idxs_t * idxs = fd_forest_blk_idxs( forest, ele );
    1554           0 :   for( uint i=fec_set_idx; i<=fec_set_idx+max_shred_idx; i++ ) {
    1555           0 :     fd_forest_blk_idxs_remove( idxs, i );
    1556           0 :   }
    1557             : 
    1558             :   /* clear complete_idx if we've cleared the last FEC in the slot */
    1559           0 :   if( FD_UNLIKELY( fec_set_idx+max_shred_idx == ele->complete_idx ) ) {
    1560           0 :     ele->complete_idx = UINT_MAX;
    1561           0 :     ele->lowest_verified_fec = UINT_MAX;
    1562           0 :   }
    1563             : 
    1564             :   /* There is a chance that the repair iterator is on this exact slot.
    1565             :      This means that this slot is in the requests list, and also at the
    1566             :      head of it. If we fec_clear on a range that is less than the
    1567             :      iterator's next_shred_idx, then the iterator will pop the slot as
    1568             :      "done" (next_shred_idx > complete_idx) without ever rerequesting
    1569             :      this fec. We must mark the slot incomplete so that the iterator can
    1570             :      re-request everything.  Don't particularly care about the clear of
    1571             :      orphan slots as they are guaranteed to be iterated again. */
    1572             : 
    1573           0 :   if( FD_UNLIKELY( forest->iter.ele_idx == fd_forest_pool_idx( fd_forest_pool( forest ), ele ) ) ) {
    1574           0 :     forest->iter.shred_idx = UINT_MAX;
    1575           0 :   }
    1576             : 
    1577           0 :   if( FD_UNLIKELY( fec_set_idx == 0 ) ) ele->buffered_idx = UINT_MAX;
    1578           0 :   else                                  ele->buffered_idx = fd_uint_if( ele->buffered_idx != UINT_MAX, fd_uint_min( ele->buffered_idx, fec_set_idx - 1 ), UINT_MAX );
    1579             : 
    1580           0 :   ele->last_shred_ts = 0;
    1581             : 
    1582           0 :   uint fec_idx = fec_set_idx / 32UL;
    1583           0 :   memset( &fd_forest_blk_mroots( forest, ele )[fec_idx].mr, 0, sizeof(fd_hash_t) );
    1584             : 
    1585             :   /* Add this slot back to requests map */
    1586           0 :   fd_forest_blk_t      * pool     = fd_forest_pool( forest );
    1587           0 :   fd_forest_ancestry_t * ancestry = fd_forest_ancestry( forest );
    1588           0 :   fd_forest_frontier_t * frontier = fd_forest_frontier( forest );
    1589           0 :   if( FD_LIKELY( fd_forest_ancestry_ele_query( ancestry, &ele->slot, NULL, pool ) ||
    1590           0 :                  fd_forest_frontier_ele_query( frontier, &ele->slot, NULL, pool ) ) ) {
    1591           0 :     int   has_requests_anc = 0;
    1592           0 :     ulong ancestor = fd_forest_pool_idx( pool, ele );
    1593           0 :     while( ancestor != fd_forest_pool_idx_null( pool ) && !has_requests_anc ) {
    1594           0 :       if( fd_forest_requests_ele_query( fd_forest_requests( forest ), &ancestor, NULL, fd_forest_reqspool( forest ) ) ) {
    1595           0 :         has_requests_anc = 1;
    1596           0 :         break;
    1597           0 :       }
    1598           0 :       ancestor = fd_forest_pool_ele( pool, ancestor )->parent;
    1599           0 :     }
    1600           0 :     if( FD_UNLIKELY( !has_requests_anc ) ) {
    1601           0 :       requests_insert( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), fd_forest_pool_idx( pool, ele ) );
    1602             : 
    1603             :       /* remove any children than are in the requests list */
    1604           0 :       ulong * queue = fd_forest_deque( forest );
    1605           0 :       fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, ele ) );
    1606           0 :       while( FD_LIKELY( fd_forest_deque_cnt( queue ) ) ) {
    1607           0 :         fd_forest_blk_t * child = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) );
    1608           0 :         if( FD_LIKELY( child != ele ) ) requests_remove( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), &forest->iter, fd_forest_pool_idx( pool, child ) );
    1609           0 :         child = fd_forest_pool_ele( pool, child->child );
    1610           0 :         while( FD_LIKELY( child ) ) {
    1611           0 :           fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) );
    1612           0 :           child = fd_forest_pool_ele( pool, child->sibling );
    1613           0 :         }
    1614           0 :       }
    1615           0 :     }
    1616             :     /* TODO we could update consumed, but it's not that necessary since
    1617             :        clearing a fec of a completed slot shouldn't really affect the
    1618             :        notion of when we completed the slot.  consumed is also updated
    1619             :        mainly for metrics.  For now we leave it alone. */
    1620           0 :   }
    1621             :   // FD_LOG_INFO(( "cleared slot %lu fec set %u", slot, fec_set_idx ));
    1622           0 : }
    1623             : 
    1624             : fd_forest_blk_t const *
    1625           0 : fd_forest_publish( fd_forest_t * forest, ulong new_root_slot ) {
    1626           0 :   fd_forest_ancestry_t * ancestry = fd_forest_ancestry( forest );
    1627           0 :   fd_forest_orphaned_t * orphaned = fd_forest_orphaned( forest );
    1628           0 :   fd_forest_frontier_t * frontier = fd_forest_frontier( forest );
    1629           0 :   fd_forest_subtrees_t * subtrees = fd_forest_subtrees( forest );
    1630           0 :   fd_forest_subtlist_t * subtlist = fd_forest_subtlist( forest );
    1631           0 :   fd_forest_ref_t *      conspool = fd_forest_conspool( forest );
    1632           0 :   fd_forest_blk_t *      pool     = fd_forest_pool( forest );
    1633           0 :   ulong                  null     = fd_forest_pool_idx_null( pool );
    1634           0 :   ulong *                queue    = fd_forest_deque( forest );
    1635             : 
    1636           0 :   fd_forest_blk_t * old_root_ele = fd_forest_pool_ele( pool, forest->root );
    1637           0 :   fd_forest_blk_t * new_root_ele = fd_forest_query( forest, new_root_slot );
    1638             : 
    1639             :   /* As an unfortunate side effect of maintaining forest slots in such
    1640             :      a fine-grained way, and also the possibility we can publish forwards
    1641             :      and backwards non-monotically, we have to consider every possible case of
    1642             :      what the new root could be.
    1643             :      1. new root not in forest.
    1644             :      2. new root in ancestry or frontier.
    1645             :      3. new root in orphaned or subtrees. */
    1646             : 
    1647             :   /* 1. If we haven't been getting repairs, and we have a gap between
    1648             :         the root and orphans. we publish forward to a slot that we don't
    1649             :         have. In that case this isn't a bug, but we should be treating
    1650             :         this new root like the snapshot slot / init root. TODO: possible
    1651             :         could be publishing backwards to a slot that we don't have. */
    1652             : 
    1653           0 :   if( FD_UNLIKELY( !new_root_ele ) ) {
    1654             :     /* TODO remove this codepath, we should never be publishing to a slot that we don't have any more */
    1655           0 :     new_root_ele = fd_forest_blk_insert( forest, new_root_slot, old_root_ele->slot, NULL ); /* ensures new root is inserted as a frontier element */
    1656           0 :     new_root_ele->complete_idx = 0;
    1657           0 :     new_root_ele->buffered_idx = 0;
    1658           0 :     requests_insert( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), fd_forest_pool_idx( pool, new_root_ele ) );
    1659           0 :     advance_consumed_frontier( forest, new_root_slot, 0 ); /* advances consumed frontier if possible */
    1660           0 :   }
    1661             : 
    1662             :   /* First, remove the previous root, and add it to a FIFO prune queue.
    1663             :      head points to the queue head (initialized with old_root_ele). */
    1664           0 :   FD_CHECK_CRIT( fd_forest_deque_cnt( queue ) == 0, "invariant violation" );
    1665             : 
    1666             :   /* 2. New root is in forest, and is either in ancestry or frontier
    1667             :         (means it is part of the main repair tree).  This is the common
    1668             :         case.  */
    1669             : 
    1670           0 :   fd_forest_blk_t * head = ancestry_frontier_remove( forest, old_root_ele->slot );
    1671           0 :   if( FD_LIKELY( head ) ) fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, head ) );
    1672             : 
    1673             :   /* BFS down the tree, inserting each ele into the prune queue except
    1674             :      for the new root.  Loop invariant: head always descends from
    1675             :      old_root_ele and never descends from new_root_ele. */
    1676             : 
    1677           0 :   while( FD_LIKELY( fd_forest_deque_cnt( queue ) ) ) {
    1678           0 :     head = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) );
    1679           0 :     fd_forest_blk_t * child = fd_forest_pool_ele( pool, head->child );
    1680           0 :     while( FD_LIKELY( child ) ) {
    1681           0 :       if( FD_LIKELY( child != new_root_ele ) ) { /* do not prune new root or descendants */
    1682           0 :         child = ancestry_frontier_remove( forest, child->slot );
    1683           0 :         fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) );
    1684           0 :       }
    1685           0 :       child = fd_forest_pool_ele( pool, child->sibling );
    1686           0 :     }
    1687             : 
    1688           0 :     consumed_remove( forest, fd_forest_pool_idx( pool, head ) );
    1689           0 :     requests_remove( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), &forest->iter, fd_forest_pool_idx( pool, head ) );
    1690           0 :     fd_forest_pool_ele_release( pool, head );
    1691           0 :   }
    1692             : 
    1693           0 :   new_root_ele->parent          = null; /* unlink new root from parent */
    1694           0 :   new_root_ele->chain_confirmed = 1;
    1695           0 :   forest->root                  = fd_forest_pool_idx( pool, new_root_ele );
    1696             : 
    1697             :   /* 3. New root is in orphaned. This is the case where maybe the
    1698             :         expected snapshot slot has jumped far ahead.  Invariants tell
    1699             :         us that the entire ancestry and frontier must have been pruned
    1700             :         above, so the consumed list and requests list must be empty.*/
    1701             : 
    1702           0 :   int new_root_is_orphan = fd_forest_subtrees_ele_query( subtrees, &new_root_ele->slot, NULL, pool ) ||
    1703           0 :                            fd_forest_orphaned_ele_query( orphaned, &new_root_ele->slot, NULL, pool );
    1704             : 
    1705           0 :   if( FD_UNLIKELY( new_root_is_orphan ) ) {
    1706             : 
    1707             :     /* Extend the frontier from the new root */
    1708             : 
    1709           0 :     fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, new_root_ele ) );
    1710           0 :     while( FD_LIKELY( fd_forest_deque_cnt( queue ) ) ) {
    1711           0 :       head = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) );
    1712           0 :       subtrees_orphaned_remove( forest, head->slot );
    1713             : 
    1714           0 :       fd_forest_blk_t * child = fd_forest_pool_ele( pool, head->child );
    1715           0 :       if( FD_LIKELY( child ) ) fd_forest_ancestry_ele_insert( ancestry, head, pool );
    1716           0 :       else                     fd_forest_frontier_ele_insert( frontier, head, pool );
    1717           0 :       while( child ) {
    1718           0 :         fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) );
    1719           0 :         child = fd_forest_pool_ele( pool, child->sibling );
    1720           0 :       }
    1721           0 :       requests_remove( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), &forest->orphiter, fd_forest_pool_idx( pool, head ) );
    1722           0 :     }
    1723           0 :   }
    1724             : 
    1725             :   /* If there is nothing on the consumed, like in the case where we
    1726             :      publish to an orphan, or during catchup where all of our repair
    1727             :      consumed frontiers were < the new root. In that case we need to
    1728             :      continue repairing from the new root, so add it to the consumed
    1729             :      map. */
    1730             : 
    1731           0 :   if( FD_UNLIKELY( fd_forest_conslist_is_empty( fd_forest_conslist( forest ), conspool ) ) ) {
    1732           0 :     consumed_insert( forest, fd_forest_pool_idx( pool, new_root_ele ) );
    1733           0 :     requests_insert( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), fd_forest_pool_idx( pool, new_root_ele ) );
    1734             :     /* TODO: is there a chance when we actually need to repair the root
    1735             :        after snapshot expected slot goes in? in this case this is
    1736             :        invalid */
    1737           0 :     new_root_ele->complete_idx = 0;
    1738           0 :     new_root_ele->buffered_idx = 0;
    1739           0 :     advance_consumed_frontier( forest, new_root_ele->slot, 0 );
    1740           0 :   }
    1741             : 
    1742             :   /* Lastly, cleanup orphans if there orphan heads < new_root_slot.
    1743             :      First, add any relevant orphans to the prune queue. */
    1744             : 
    1745           0 :   for( fd_forest_subtlist_iter_t iter = fd_forest_subtlist_iter_fwd_init( subtlist, pool );
    1746           0 :                                        !fd_forest_subtlist_iter_done( iter, subtlist, pool );
    1747           0 :                                  iter = fd_forest_subtlist_iter_fwd_next( iter, subtlist, pool ) ) {
    1748           0 :     fd_forest_blk_t * ele = fd_forest_subtlist_iter_ele( iter, subtlist, pool );
    1749           0 :     if( FD_UNLIKELY( ele->slot < new_root_slot ) ) {
    1750           0 :       fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, ele ) );
    1751           0 :     }
    1752           0 :   }
    1753             : 
    1754             :   /* Now BFS and clean up children of these orphan heads */
    1755           0 :   while( FD_UNLIKELY( fd_forest_deque_cnt( queue ) ) ) {
    1756           0 :     head = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) );
    1757           0 :     fd_forest_blk_t * child = fd_forest_pool_ele( pool, head->child );
    1758           0 :     while( FD_LIKELY( child ) ) {
    1759           0 :       if( FD_LIKELY( child != new_root_ele ) ) {
    1760           0 :         fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) );
    1761           0 :       }
    1762           0 :       child = fd_forest_pool_ele( pool, child->sibling );
    1763           0 :     }
    1764           0 :     subtrees_orphaned_remove( forest, head->slot );
    1765             :     /* Remove from orphan requests if present */
    1766           0 :     requests_remove( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), &forest->orphiter, fd_forest_pool_idx( pool, head ) );
    1767           0 :     fd_forest_pool_ele_release( pool, head ); /* free head */
    1768           0 :   }
    1769             : 
    1770           0 :   new_root_ele->sibling = null; /* unlink new root from siblings, just in case */
    1771           0 :   return new_root_ele;
    1772           0 : }
    1773             : 
    1774             : 
    1775             : ulong
    1776           0 : fd_forest_highest_repaired_slot( fd_forest_t const * forest ) {
    1777           0 :   fd_forest_blk_t const * pool = fd_forest_pool_const( forest );
    1778           0 :   fd_forest_blk_t const * root = fd_forest_pool_ele_const( pool, forest->root );
    1779           0 :   fd_forest_conslist_t const * conslist = fd_forest_conslist_const( forest );
    1780           0 :   fd_forest_ref_t const *      conspool = fd_forest_conspool_const( forest );
    1781             : 
    1782           0 :   if( FD_UNLIKELY( !root ) ) return 0;
    1783             : 
    1784           0 :   ulong max_repaired_slot = root->slot;
    1785           0 :   for( fd_forest_conslist_iter_t iter = fd_forest_conslist_iter_fwd_init( conslist, conspool );
    1786           0 :        !fd_forest_conslist_iter_done( iter, conslist, conspool );
    1787           0 :        iter = fd_forest_conslist_iter_fwd_next( iter, conslist, conspool ) ) {
    1788           0 :     fd_forest_ref_t const * ele = fd_forest_conslist_iter_ele_const( iter, conslist, conspool );
    1789           0 :     fd_forest_blk_t const * ele_ = fd_forest_pool_ele_const( pool, ele->idx );
    1790           0 :     if( FD_LIKELY( ele_->slot > max_repaired_slot ) ) max_repaired_slot = ele_->slot;
    1791           0 :   }
    1792           0 :   return max_repaired_slot;
    1793           0 : }
    1794             : 
    1795             : 
    1796             : fd_forest_t *
    1797           0 : fd_forest_clear( fd_forest_t * forest ) {
    1798           0 :   return forest;
    1799           0 : }
    1800             : 
    1801             : fd_forest_iter_t *
    1802           0 : fd_forest_iter_next( fd_forest_iter_t * iter, fd_forest_t * forest ) {
    1803           0 :   fd_forest_blk_t const * pool     = fd_forest_pool_const( forest );
    1804           0 :   fd_forest_blk_t const * ele      = fd_forest_pool_ele_const( pool, iter->ele_idx );
    1805           0 :   fd_forest_reqslist_t  * reqslist = iter->list_gaddr == forest->reqslist_gaddr ? fd_forest_reqslist( forest ) : fd_forest_orphlist( forest );
    1806           0 :   fd_forest_requests_t  * reqsmap  = iter->list_gaddr == forest->reqslist_gaddr ? fd_forest_requests( forest ) : fd_forest_orphreqs( forest );
    1807           0 :   fd_forest_ref_t       * reqspool = fd_forest_reqspool( forest );
    1808             : 
    1809             :   /* forest->iter.ele_idx should always refer to the head of the
    1810             :      requests list, unless iter.ele_idx is null (initializing)*/
    1811           0 :   if( FD_UNLIKELY( iter->ele_idx != fd_forest_pool_idx_null( pool ) &&
    1812           0 :                    iter->ele_idx != fd_forest_reqslist_ele_peek_head( reqslist, reqspool )->idx ) ) {
    1813           0 :     FD_LOG_WARNING(("invariant violation: forest iterator ele_idx %lu != head of request list %lu", iter->ele_idx, fd_forest_reqslist_ele_peek_head( reqslist, reqspool )->idx));
    1814             :     /* check if the iterator ele_idx lives in the forest for debugging. */
    1815           0 :     fd_forest_blk_t const * ele_iter = fd_forest_pool_ele_const( pool, iter->ele_idx );
    1816           0 :     fd_forest_blk_t const * req_head = fd_forest_pool_ele_const( pool, fd_forest_reqslist_ele_peek_head( reqslist, reqspool )->idx );
    1817           0 :     ulong slot_iter     = ele_iter ? ele_iter->slot : 0;
    1818           0 :     ulong slot_req_head = req_head ? req_head->slot : 0;
    1819           0 :     FD_LOG_CRIT(( "Forest iterator slot %lu != head of request list slot %lu. Does forest have %lu? %p. Does forest have %lu? %p.", slot_iter, slot_req_head, slot_iter, (void *)fd_forest_query( forest, slot_iter ), req_head->slot, (void *)fd_forest_query( forest, slot_req_head ) ));
    1820           0 :   }
    1821             : 
    1822           0 :   uint next_shred_idx = iter->shred_idx;
    1823           0 :   for(;;) {
    1824           0 :     next_shred_idx++;
    1825             : 
    1826             :     /* Case 1: No more shreds in this slot to request, move to the
    1827             :        next one. Wraparound the shred_idx.
    1828             : 
    1829             :        Case 2: original iter.shred_idx == UINT_MAX (implies prev req
    1830             :        was a highest_window_idx request). Also requires moving to next
    1831             :        slot and wrapping the shred_idx. */
    1832             : 
    1833           0 :     if( FD_UNLIKELY( !ele || next_shred_idx > ele->complete_idx || iter->shred_idx == UINT_MAX ) ) {
    1834             : 
    1835             :       /* done requesting this slot.  peek the next slot from requests
    1836             :          deque. But first, add this slot's children to the requests
    1837             :          deque!  Debatable: should we add this slot's children to
    1838             :          the requests deque until we have actually sent reqs for every
    1839             :          shred of the slot? */
    1840             : 
    1841           0 :       if( FD_LIKELY( ele ) ) {
    1842           0 :         fd_forest_blk_t const * child = fd_forest_pool_ele_const( pool, ele->child );
    1843           0 :         while( FD_LIKELY( child ) ) {
    1844           0 :           requests_insert( forest, reqsmap, reqslist, fd_forest_pool_idx( pool, child ) );
    1845           0 :           child = fd_forest_pool_ele_const( pool, child->sibling );
    1846           0 :         }
    1847             :         /* so annoying. can't call requests_remove because itll invalidate the current iter->ele_idx,
    1848             :            so we explicitly pop the head and free the ele here. */
    1849           0 :         fd_forest_ref_t * head = fd_forest_reqslist_ele_pop_head( reqslist, reqspool );
    1850           0 :         fd_forest_requests_ele_remove ( reqsmap, &head->idx, NULL, reqspool );
    1851           0 :         fd_forest_reqspool_ele_release( reqspool, head );
    1852             : 
    1853           0 :         if( FD_UNLIKELY( iter->shred_idx == UINT_MAX && ( ele->buffered_idx == UINT_MAX || ele->buffered_idx < ele->complete_idx ) ) ) {
    1854             :           /* If we just made a highest_window_idx request, add this slot
    1855             :              back to the requests deque at the end.  Also condition on
    1856             :              whether or not this slot is still incomplete.  If the slot
    1857             :              is complete and we add it back to the loop, we will end up
    1858             :              infinite looping. */
    1859           0 :           requests_insert( forest, reqsmap, reqslist, iter->ele_idx );
    1860           0 :         }
    1861           0 :       }
    1862             : 
    1863             :       /* Move onto the next slot */
    1864           0 :       if( FD_UNLIKELY( fd_forest_reqslist_is_empty( reqslist, reqspool ) ) ) {
    1865           0 :         iter->ele_idx = fd_forest_pool_idx_null( pool );
    1866           0 :         iter->shred_idx = UINT_MAX;
    1867           0 :         return iter;
    1868           0 :       }
    1869             : 
    1870           0 :       iter->ele_idx = fd_forest_reqslist_ele_peek_head( reqslist, reqspool )->idx;
    1871           0 :       ele           = fd_forest_pool_ele_const( pool, iter->ele_idx );
    1872             : 
    1873           0 :       if( FD_UNLIKELY( !fd_forest_query( forest, ele->slot ) ) ) {
    1874             :         /* TODO: should never meet this condition if the iterator
    1875             :            invariants are maintained.  Can consider changing back to
    1876             :            LOG_CRIT after dynamic expected snapshot slot changes go in,
    1877             :            or removing this check entirely. */
    1878           0 :          FD_LOG_WARNING(( "[%s] slot %lu not found in forest. purging from requests list.", __func__, ele->slot ));
    1879           0 :          requests_remove( forest, reqsmap, reqslist, iter, iter->ele_idx );
    1880           0 :          return iter;
    1881           0 :       }
    1882           0 :       next_shred_idx = ele->buffered_idx + 1;
    1883           0 :     }
    1884             : 
    1885             :     /* Common case - valid shred to request. Note you can't know the
    1886             :        ele->complete_idx until you have actually received the slot
    1887             :        complete shred, but the last shred may have been evicted, so we
    1888             :        need leq. */
    1889             : 
    1890           0 :     if( ele->complete_idx != UINT_MAX && next_shred_idx <= ele->complete_idx ) {
    1891           0 :       fd_forest_blk_idxs_t const * idxs = fd_forest_blk_idxs( forest, ele );
    1892             : 
    1893             :       /* Jump a word at a time to the first missing shred, or to
    1894             :          complete_idx if none.  Not after a highest_window_idx request,
    1895             :          which tests one shred of the next slot and moves on. */
    1896             : 
    1897           0 :       if( FD_LIKELY( iter->shred_idx != UINT_MAX ) ) {
    1898           0 :         next_shred_idx = (uint)fd_ulong_min( fd_forest_blk_idxs_next_unset( idxs, next_shred_idx, ele->complete_idx ), ele->complete_idx );
    1899           0 :       }
    1900           0 :       if( !fd_forest_blk_idxs_test( idxs, next_shred_idx ) ) {
    1901           0 :         iter->shred_idx = next_shred_idx;
    1902           0 :         break;
    1903           0 :       }
    1904           0 :     }
    1905             : 
    1906             :     /* Current slot actually needs a highest_window_idx request */
    1907             : 
    1908           0 :     if( FD_UNLIKELY( ele->complete_idx == UINT_MAX ) ) {
    1909           0 :       iter->shred_idx = UINT_MAX;
    1910           0 :       break;
    1911           0 :     }
    1912           0 :   }
    1913           0 :   return iter;
    1914           0 : }
    1915             : 
    1916             : int
    1917           0 : fd_forest_iter_done( fd_forest_iter_t * iter, fd_forest_t * forest ) {
    1918           0 :   fd_forest_blk_t const * pool = fd_forest_pool_const( forest );
    1919           0 :   return iter->ele_idx == fd_forest_pool_idx_null( pool ); /* no more elements */
    1920           0 : }
    1921             : 
    1922             : #include <stdio.h>
    1923             : 
    1924             : #define FD_FOREST_ORPHANED_PRINT_MAX_DEPTH 500UL
    1925             : 
    1926             : static void
    1927             : orphaned_print( fd_forest_t const     * forest,
    1928             :                 fd_forest_blk_t const * ele,
    1929             :                 fd_forest_blk_t const * prev,
    1930             :                 ulong                   last_printed,
    1931             :                 int                     depth,
    1932             :                 const char *            prefix,
    1933           0 :                 ulong                   print_depth ) {
    1934             : 
    1935           0 :   if( FD_UNLIKELY( ele == NULL ) ) return;
    1936             : 
    1937             :   /* Prevent stack overflow from excessive recursion */
    1938           0 :   if( FD_UNLIKELY( print_depth >= FD_FOREST_ORPHANED_PRINT_MAX_DEPTH ) ) {
    1939           0 :     printf( "... (truncated: too many orphaned nodes, max depth %lu reached)\n", FD_FOREST_ORPHANED_PRINT_MAX_DEPTH );
    1940           0 :     return;
    1941           0 :   }
    1942             : 
    1943           0 :   fd_forest_blk_t const * pool = fd_forest_pool_const( forest );
    1944           0 :   int digits = (int)fd_ulong_base10_dig_cnt( ele->slot );
    1945             : 
    1946             :   /* If there is a prefix, this means we are on a fork,  and we need to
    1947             :      indent to the correct depth. We do depth - 1 for more satisfying
    1948             :      spacing. */
    1949           0 :   if( FD_UNLIKELY( strcmp( prefix, "" ) ) ) {
    1950           0 :     for( int i = 0; i < depth - 1; i++ ) printf( " " );
    1951           0 :     if( depth > 0 ) printf( "%s", prefix );
    1952           0 :   }
    1953             : 
    1954           0 :   if ( FD_UNLIKELY( !prev ) ) { // New interval
    1955           0 :     printf("[%lu" , ele->slot );
    1956           0 :     last_printed = ele->slot;
    1957           0 :     depth       += 1 + digits;
    1958           0 :   }
    1959             : 
    1960           0 :   fd_forest_blk_t const * curr = fd_forest_pool_ele_const( pool, ele->child );
    1961             : 
    1962             :   /* Cases in which we close the interval:
    1963             :      1. the slots are no longer consecutive. no eliding, close bracket
    1964             :      2. current ele has multiple children, want to print forks.
    1965             :      Maintain last_printed on this fork so that we don't print [a, a]
    1966             :      intervals. */
    1967             : 
    1968           0 :   fd_forest_blk_t const * new_prev = ele;
    1969             : 
    1970           0 :   if( prev && prev->slot != ele->slot - 1 ) { // non-consecutive, do not elide
    1971           0 :     if( last_printed == prev->slot ){
    1972           0 :       printf( "] ── [%lu", ele->slot );
    1973           0 :       depth += digits + 6;
    1974           0 :     } else {
    1975           0 :       printf( ", %lu] ── [%lu", prev->slot, ele->slot );
    1976           0 :       depth += digits + (int)fd_ulong_base10_dig_cnt( prev->slot ) + 8;
    1977           0 :     }
    1978           0 :     last_printed = ele->slot;
    1979           0 :   } else if( curr && curr->sibling != ULONG_MAX ) { // has multiple children, do not elide
    1980           0 :     if( last_printed == ele->slot ){
    1981           0 :       printf( "] ── " );
    1982           0 :       depth += 5;
    1983           0 :     } else {
    1984           0 :       printf( ", %lu] ── ", ele->slot );
    1985           0 :       depth += digits + 2;
    1986           0 :     }
    1987           0 :     last_printed = ele->slot;
    1988           0 :     new_prev = NULL;
    1989           0 :   }
    1990             : 
    1991           0 :   if( !curr ){ // no children, close bracket, end fork
    1992           0 :     if( last_printed == ele->slot ){
    1993           0 :       printf( "]\n" );
    1994           0 :     } else {
    1995           0 :       printf( ", %lu]\n", ele->slot );
    1996           0 :     }
    1997           0 :     return;
    1998           0 :   }
    1999             : 
    2000           0 :   char new_prefix[32];
    2001           0 :   new_prefix[0] = '\0'; /* first fork stays on the same line, no prefix */
    2002           0 :   while( curr ) {
    2003           0 :     orphaned_print( forest, curr, new_prev, last_printed, depth, new_prefix, print_depth + 1UL );
    2004           0 :     curr = fd_forest_pool_ele_const( pool, curr->sibling );
    2005             : 
    2006             :     /* Set up prefix for following iterations */
    2007           0 :     if( curr && curr->sibling != ULONG_MAX ) {
    2008           0 :       fd_cstr_printf( new_prefix, sizeof(new_prefix), NULL, "├── " ); /* any following forks start on new lines */
    2009           0 :     } else {
    2010           0 :       fd_cstr_printf( new_prefix, sizeof(new_prefix), NULL, "└── " ); /* any following forks start on new lines */
    2011           0 :     }
    2012           0 :   }
    2013             : 
    2014           0 : }
    2015             : 
    2016             : #define FD_FOREST_ANCESTRY_PRINT_MAX_DEPTH 500UL
    2017             : 
    2018             : static void
    2019           0 : ancestry_print( fd_forest_t const * forest, fd_forest_blk_t const * ele, int space, const char * prefix, fd_forest_blk_t const * prev, int elide, ulong print_depth ) {
    2020           0 :   fd_forest_blk_t const * pool = fd_forest_pool_const( forest );
    2021             : 
    2022           0 :   char new_prefix[32]; /* only ever holds a fixed 4-5 byte branch glyph */
    2023             : 
    2024           0 :   while( ele ) {
    2025             : 
    2026             :     /* excessive recursion */
    2027           0 :     if( FD_UNLIKELY( print_depth >= FD_FOREST_ANCESTRY_PRINT_MAX_DEPTH ) ) {
    2028           0 :       printf( "... (truncated: too many forks, max depth %lu reached)\n", FD_FOREST_ANCESTRY_PRINT_MAX_DEPTH );
    2029           0 :       return;
    2030           0 :     }
    2031             : 
    2032             :     /* print the slot itself. either we might need to start a new interval, or it may get elided */
    2033           0 :     fd_forest_blk_t const * child = fd_forest_pool_ele_const( pool, ele->child );
    2034             : 
    2035           0 :     if( !elide ) {
    2036           0 :       if( space > 0 ) printf( "\n" );
    2037           0 :       for( int i = 0; i < space; i++ ) printf( " " );
    2038           0 :       printf( "%s", prefix );
    2039           0 :       printf( "%lu", ele->slot );
    2040           0 :     }
    2041             : 
    2042           0 :     if( !child && !elide ) { /* double check these cases aren't the same...*/
    2043           0 :       printf( "]" );
    2044           0 :       return;
    2045           0 :     } /* no children, close bracket */
    2046             : 
    2047           0 :     if( !child && elide ) {
    2048           0 :       printf( ", %lu]", ele->slot );
    2049           0 :       return;
    2050           0 :     }
    2051             : 
    2052           0 :     prev = ele;
    2053           0 :     int one_child = child && child->sibling == ULONG_MAX;
    2054           0 :     if( one_child &&
    2055           0 :         child->slot != ele->slot + 1 ) { // if I have ONE CHILD and one child is non-consecutive
    2056             : 
    2057           0 :       if( elide ) {
    2058             :         /* current slot wasn't printed, but now that we are branching,
    2059             :            we will want to print the current slot and close the bracket */
    2060           0 :         printf( ", %lu]", ele->slot );
    2061           0 :         space += fd_int_max( (int)fd_ulong_base10_dig_cnt( ele->slot ) + 2, 0 );
    2062           0 :       } else {
    2063           0 :         printf( "]");
    2064           0 :       }
    2065             : 
    2066           0 :       fd_cstr_printf( new_prefix, sizeof(new_prefix), NULL, "└── [" ); /* end branch */
    2067           0 :       ele    = child;
    2068           0 :       space += 5;
    2069           0 :       prefix = new_prefix;
    2070           0 :       elide  = 0;
    2071           0 :     } else if ( one_child && child->slot == ele->slot + 1 ) {
    2072           0 :       ele   = child;
    2073           0 :       elide = 1;
    2074           0 :     } else { /* multiple children */
    2075           0 :       if( elide ) {
    2076             :         /* current slot wasn't printed, but now that we are branching,
    2077             :            we will want to print the current slot and close the bracket */
    2078           0 :         printf( ", %lu]", ele->slot );
    2079           0 :         space += fd_int_max( (int)fd_ulong_base10_dig_cnt( ele->slot ) + 2, 0 );
    2080           0 :       } else {
    2081           0 :         printf( "]");
    2082           0 :       }
    2083             : 
    2084           0 :       while( child ) {
    2085           0 :         if( fd_forest_pool_ele_const( pool, child->sibling ) ) {
    2086           0 :           fd_cstr_printf( new_prefix, sizeof(new_prefix), NULL, "├── [" ); /* branch indicating more siblings follow */
    2087           0 :           ancestry_print( forest, child, space + 5, new_prefix, prev, 0, print_depth + 1UL );
    2088           0 :         } else {
    2089           0 :           fd_cstr_printf( new_prefix, sizeof(new_prefix), NULL, "└── [" ); /* end branch */
    2090           0 :           ancestry_print( forest, child, space + 5, new_prefix, prev, 0, print_depth + 1UL );
    2091           0 :         }
    2092           0 :         child = fd_forest_pool_ele_const( pool, child->sibling );
    2093           0 :       }
    2094           0 :       return;
    2095           0 :     }
    2096           0 :   }
    2097           0 : }
    2098             : 
    2099             : void
    2100           0 : fd_forest_ancestry_print( fd_forest_t const * forest ) {
    2101           0 :   printf(("\n\n[Ancestry]\n" ) );
    2102           0 :   ancestry_print( forest, fd_forest_pool_ele_const( fd_forest_pool_const( forest ), forest->root ), 0, "[", NULL, 0, 0UL );
    2103           0 :   fflush(stdout); /* Ensure ancestry printf output is flushed */
    2104           0 : }
    2105             : 
    2106             : void
    2107           0 : fd_forest_frontier_print( fd_forest_t const * forest ) {
    2108           0 :   printf( "\n\n[Repairing Next]\n" );
    2109           0 :   fd_forest_conslist_t const * conslist = fd_forest_conslist_const( forest );
    2110           0 :   fd_forest_ref_t const *      conspool = fd_forest_conspool_const( forest );
    2111           0 :   fd_forest_blk_t const *      pool     = fd_forest_pool_const( forest );
    2112           0 :   for( fd_forest_conslist_iter_t iter = fd_forest_conslist_iter_fwd_init( conslist, conspool );
    2113           0 :        !fd_forest_conslist_iter_done( iter, conslist, conspool );
    2114           0 :        iter = fd_forest_conslist_iter_fwd_next( iter, conslist, conspool ) ) {
    2115           0 :     fd_forest_ref_t const * ele = fd_forest_conslist_iter_ele_const( iter, conslist, conspool );
    2116           0 :     fd_forest_blk_t const * ele_ = fd_forest_pool_ele_const( pool, ele->idx );
    2117           0 :     printf("%lu (%u/%u)\n", ele_->slot, ele_->buffered_idx + 1, ele_->complete_idx + 1 );
    2118           0 :   }
    2119           0 :   fflush(stdout);
    2120           0 : }
    2121             : 
    2122             : void
    2123           0 : fd_forest_orphaned_print( fd_forest_t const * forest ) {
    2124           0 :   printf( "\n[Orphaned]\n" );
    2125           0 :   fd_forest_subtlist_t const * subtlist = fd_forest_subtlist_const( forest );
    2126           0 :   fd_forest_blk_t const * pool = fd_forest_pool_const( forest );
    2127           0 :   for( fd_forest_subtlist_iter_t iter = fd_forest_subtlist_iter_fwd_init( subtlist, pool );
    2128           0 :                                        !fd_forest_subtlist_iter_done( iter, subtlist, pool );
    2129           0 :                                  iter = fd_forest_subtlist_iter_fwd_next( iter, subtlist, pool ) ) {
    2130           0 :     fd_forest_blk_t const * ele = fd_forest_subtlist_iter_ele_const( iter, subtlist, pool );
    2131           0 :     orphaned_print( forest, fd_forest_pool_ele_const( fd_forest_pool_const( forest ), fd_forest_pool_idx( pool, ele ) ), NULL, 0, 0, "", 0UL );
    2132           0 :   }
    2133           0 :   fflush(stdout);
    2134           0 : }
    2135             : 
    2136             : void
    2137           0 : fd_forest_print( fd_forest_t const * forest ) {
    2138           0 :   if( FD_UNLIKELY( forest->root == ULONG_MAX ) ) return;
    2139           0 :   FD_LOG_NOTICE(("\n\n[Forest]" ) );
    2140           0 :   fd_forest_ancestry_print( forest );
    2141           0 :   fd_forest_frontier_print( forest );
    2142           0 :   fd_forest_orphaned_print( forest );
    2143           0 :   printf("\n");
    2144             : 
    2145           0 :   fflush(stdout);
    2146           0 : }
    2147             : 
    2148             : #undef FD_FOREST_ANCESTRY_PRINT_MAX_DEPTH
    2149             : #undef FD_FOREST_ORPHANED_PRINT_MAX_DEPTH
    2150             : 
    2151             : #undef FD_FOREST_PRINT

Generated by: LCOV version 1.14