LCOV - code coverage report
Current view: top level - funk - fd_funk_txn.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 470 522 90.0 %
Date: 2025-01-08 12:08:44 Functions: 14 17 82.4 %

          Line data    Source code
       1             : #include "fd_funk.h"
       2             : 
       3             : /* Provide the actual transaction map implementation */
       4             : 
       5             : #define MAP_NAME              fd_funk_txn_map
       6  1558699241 : #define MAP_T                 fd_funk_txn_t
       7             : #define MAP_KEY_T             fd_funk_txn_xid_t
       8     3758337 : #define MAP_KEY               xid
       9             : #define MAP_KEY_EQ(k0,k1)     fd_funk_txn_xid_eq((k0),(k1))
      10   829988157 : #define MAP_KEY_HASH(k0,seed) fd_funk_txn_xid_hash((k0),(seed))
      11             : #define MAP_KEY_COPY(kd,ks)   fd_funk_txn_xid_copy((kd),(ks))
      12  1175702674 : #define MAP_NEXT              map_next
      13  1635111307 : #define MAP_HASH              map_hash
      14      408483 : #define MAP_MAGIC             (0xf173da2ce7172db0UL) /* Firedancer trn db version 0 */
      15             : #define MAP_IMPL_STYLE        2
      16             : #define MAP_MEMOIZE           1
      17             : #include "../util/tmpl/fd_map_giant.c"
      18             : 
      19             : /* As described in fd_funk_txn.h, like the extensive tests in verify
      20             :    (which, by its very nature, is called on maps whose integrity is
      21             :    unclear to the caller), these have been fortified again memory
      22             :    corruption (accidental or deliberate) by doing out of bounds memory
      23             :    access detection and cycle (infinite loop) detection in all the
      24             :    various operations below.
      25             : 
      26             :    This is debatably overkill but, given this code is the pointy end of
      27             :    the stick for keeping transaction histories clean (i.e. cancelling a
      28             :    wrong transaction could lose information and publishing a wrong
      29             :    transaction is even worse), the overhead for the detection is
      30             :    minimal, and these operations aren't particularly performance
      31             :    critical anyway, seems a more-than-worthwhile safeguard.  Likewise,
      32             :    it also guarantees strict algo overheads of these in the face of
      33             :    corruption.
      34             : 
      35             :    When there is a corruption issue detected realtime, it is prima facie
      36             :    evidence of either software bug, hardware fault or compromised
      37             :    process.  In all cases, these functions will refuse to proceed
      38             :    further and abort with FD_LOG_CRIT to minimize the blast radius of
      39             :    possible corruption and give the user as much details (stack trace
      40             :    and core) to diagnose the source of the issue.  This handling is
      41             :    mostly isolated to the below macros and thus easy to modify or
      42             :    disable.
      43             : 
      44             :    The corruption detection works by ensuring all map indices are in
      45             :    bounds and then applying a unique tag during various map traversals
      46             :    such that operations can detected if a transaction has already been
      47             :    encountered earlier in the traversal (and thus will create a
      48             :    cycle/infinite loop) in the shortest possible algorithm overhead to
      49             :    detect such a cycle. */
      50             : 
      51     3758337 : #define ASSERT_IN_MAP( txn_idx ) do {                          \
      52     3758337 :     if( FD_UNLIKELY( txn_idx>=txn_max ) )                      \
      53     3758337 :       FD_LOG_CRIT(( "memory corruption detected (bad_idx)" )); \
      54     3758337 :   } while(0)
      55             : 
      56     7333491 : #define ASSERT_IN_PREP( txn_idx ) do {                                                           \
      57     7333491 :     if( FD_UNLIKELY( txn_idx>=txn_max ) )                                                        \
      58     7333491 :       FD_LOG_CRIT(( "memory corruption detected (bad_idx)" ));                                   \
      59     7333491 :     if( FD_UNLIKELY( !fd_funk_txn_map_query( map, fd_funk_txn_xid( &map[ txn_idx ] ), NULL ) ) ) \
      60     7333491 :       FD_LOG_CRIT(( "memory corruption detected (not in prep)" ));                               \
      61     7333491 :   } while(0)
      62             : 
      63     3132822 : #define ASSERT_UNTAGGED( txn_idx ) do {                      \
      64     3132822 :     if( FD_UNLIKELY( map[ txn_idx ].tag==tag ) )             \
      65     3132822 :       FD_LOG_CRIT(( "memory corruption detected (cycle)" )); \
      66     3132822 :   } while(0)
      67             : 
      68             : fd_funk_txn_t *
      69             : fd_funk_txn_prepare( fd_funk_t *               funk,
      70             :                      fd_funk_txn_t *           parent,
      71             :                      fd_funk_txn_xid_t const * xid,
      72     4994712 :                      int                       verbose ) {
      73             : 
      74     4994712 :   if( FD_UNLIKELY( !funk ) ) {
      75      196464 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
      76      196464 :     return NULL;
      77      196464 :   }
      78     4798248 :   fd_funk_check_write( funk );
      79             : 
      80     4798248 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) );
      81             : 
      82     4798248 :   if( FD_UNLIKELY( fd_funk_txn_map_is_full( map ) ) ) {
      83          24 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "too many transactions in preparation" ));
      84          24 :     return NULL;
      85          24 :   }
      86             : 
      87     4798224 :   ulong txn_max = funk->txn_max;
      88             : 
      89     4798224 :   ulong  parent_idx;
      90     4798224 :   uint * _child_head_cidx;
      91     4798224 :   uint * _child_tail_cidx;
      92             : 
      93     4798224 :   if( FD_LIKELY( !parent ) ) { /* opt for incr pub */
      94             : 
      95     2117388 :     parent_idx = FD_FUNK_TXN_IDX_NULL;
      96             : 
      97     2117388 :     _child_head_cidx = &funk->child_head_cidx;
      98     2117388 :     _child_tail_cidx = &funk->child_tail_cidx;
      99             : 
     100     2680836 :   } else {
     101             : 
     102     2680836 :     parent_idx = (ulong)(parent - map);
     103             : 
     104     2680836 :     if( FD_UNLIKELY( (parent_idx>=txn_max) /* Out of map */ | (parent!=(map+parent_idx)) /* Bad alignment */ ) ) {
     105      196461 :       if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "parent is not a funk transaction" ));
     106      196461 :       return NULL;
     107      196461 :     }
     108             : 
     109     2484375 :     if( FD_UNLIKELY( !fd_funk_txn_map_query( map, fd_funk_txn_xid( parent ), NULL ) ) ) {
     110      188382 :       if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "parent is not in preparation" ));
     111      188382 :       return NULL;
     112      188382 :     }
     113             : 
     114     2295993 :     _child_head_cidx = &parent->child_head_cidx;
     115     2295993 :     _child_tail_cidx = &parent->child_tail_cidx;
     116             : 
     117     2295993 :   }
     118             : 
     119     4413381 :   if( FD_UNLIKELY( !xid ) ) {
     120      196461 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL xid" ));
     121      196461 :     return NULL;
     122      196461 :   }
     123             : 
     124     4216920 :   if( FD_UNLIKELY( fd_funk_txn_xid_eq_root( xid ) ) ) {
     125           6 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "xid is the root" ));
     126           6 :     return NULL;
     127           6 :   }
     128             : 
     129     4216914 :   if( FD_UNLIKELY( fd_funk_txn_xid_eq( xid, funk->last_publish ) ) ) {
     130      196455 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "xid is the last published" ));
     131      196455 :     return NULL;
     132      196455 :   }
     133             : 
     134     4020459 :   if( FD_UNLIKELY( fd_funk_txn_map_query( map, xid, NULL ) ) ) {
     135      262122 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "id already a transaction" ));
     136      262122 :     return NULL;
     137      262122 :   }
     138             : 
     139             :   /* Get a new transaction from the map */
     140             : 
     141     3758337 :   fd_funk_txn_t * txn     = fd_funk_txn_map_insert( map, xid );
     142     3758337 :   ulong           txn_idx = (ulong)(txn - map);
     143     3758337 :   ASSERT_IN_MAP( txn_idx );
     144             : 
     145             :   /* Join the family */
     146             : 
     147     3758337 :   ulong sibling_prev_idx = fd_funk_txn_idx( *_child_tail_cidx );
     148             : 
     149     3758337 :   int first_born = fd_funk_txn_idx_is_null( sibling_prev_idx );
     150     3758337 :   if( FD_UNLIKELY( !first_born ) ) ASSERT_IN_PREP( sibling_prev_idx ); /* opt for non-compete */
     151             : 
     152     3758337 :   txn->parent_cidx       = fd_funk_txn_cidx( parent_idx           );
     153     3758337 :   txn->child_head_cidx   = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     154     3758337 :   txn->child_tail_cidx   = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     155     3758337 :   txn->sibling_prev_cidx = fd_funk_txn_cidx( sibling_prev_idx     );
     156     3758337 :   txn->sibling_next_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     157     3758337 :   txn->stack_cidx        = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     158     3758337 :   txn->tag               = 0UL;
     159             : 
     160     3758337 :   txn->rec_head_idx = FD_FUNK_REC_IDX_NULL;
     161     3758337 :   txn->rec_tail_idx = FD_FUNK_REC_IDX_NULL;
     162             : 
     163             :   /* TODO: consider branchless impl */
     164     3758337 :   if( FD_LIKELY( first_born ) ) *_child_head_cidx                         = fd_funk_txn_cidx( txn_idx ); /* opt for non-compete */
     165      796092 :   else                          map[ sibling_prev_idx ].sibling_next_cidx = fd_funk_txn_cidx( txn_idx );
     166             : 
     167     3758337 :   *_child_tail_cidx = fd_funk_txn_cidx( txn_idx );
     168             : 
     169     3758337 :   return txn;
     170     3758337 : }
     171             : 
     172             : /* fd_funk_txn_cancel_childless cancels a transaction that is known
     173             :    to be childless.  Callers have already validated our input arguments.
     174             :    Assumes that cancelling in the app can't fail but that could be
     175             :    straightforward to support by giving this an error and plumbing
     176             :    through to abort the overall cancel operation when it hits a error. */
     177             : 
     178             : static void
     179             : fd_funk_txn_cancel_childless( fd_funk_t *     funk,
     180             :                               fd_funk_txn_t * map,
     181             :                               ulong           txn_max,
     182     2596914 :                               ulong           txn_idx ) {
     183             : 
     184     2596914 :   fd_funk_check_write( funk );
     185             : 
     186             :   /* Remove all records used by this transaction.  Note that we don't
     187             :      need to bother doing all the individual removal operations as we
     188             :      are removing the whole list.  We do reset the record transaction
     189             :      idx with NULL though we can detect cycles as soon as possible
     190             :      and abort. */
     191             : 
     192     2596914 :   fd_wksp_t *     wksp    = fd_funk_wksp   ( funk );
     193     2596914 :   fd_alloc_t *    alloc   = fd_funk_alloc  ( funk, wksp );
     194     2596914 :   fd_funk_rec_t * rec_map = fd_funk_rec_map( funk, wksp );
     195     2596914 :   fd_funk_partvec_t * partvec = fd_funk_get_partvec( funk, wksp );
     196     2596914 :   ulong           rec_max = funk->rec_max;
     197             : 
     198     2596914 :   ulong rec_idx = map[ txn_idx ].rec_head_idx;
     199     9009222 :   while( !fd_funk_rec_idx_is_null( rec_idx ) ) {
     200             : 
     201     6412308 :     if( FD_UNLIKELY( rec_idx>=rec_max ) ) FD_LOG_CRIT(( "memory corruption detected (bad idx)" ));
     202     6412308 :     if( FD_UNLIKELY( fd_funk_txn_idx( rec_map[ rec_idx ].txn_cidx )!=txn_idx ) )
     203           0 :       FD_LOG_CRIT(( "memory corruption detected (cycle or bad idx)" ));
     204             : 
     205     6412308 :     ulong next_idx = rec_map[ rec_idx ].next_idx;
     206     6412308 :     rec_map[ rec_idx ].txn_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     207             : 
     208     6412308 :     fd_funk_val_flush( &rec_map[ rec_idx ], alloc, wksp );
     209     6412308 :     fd_funk_part_set_intern( partvec, rec_map, &rec_map[ rec_idx ], FD_FUNK_PART_NULL );
     210             : 
     211     6412308 :     fd_funk_rec_map_remove( rec_map, fd_funk_rec_pair( &rec_map[ rec_idx ] ) );
     212             : 
     213     6412308 :     rec_idx = next_idx;
     214     6412308 :   }
     215             : 
     216             :   /* Leave the family */
     217             : 
     218     2596914 :   ulong sibling_prev_idx = fd_funk_txn_idx( map[ txn_idx ].sibling_prev_cidx );
     219     2596914 :   ulong sibling_next_idx = fd_funk_txn_idx( map[ txn_idx ].sibling_next_cidx );
     220             : 
     221             :   /* TODO: Consider branchless impl */
     222             : 
     223     2596914 :   if( FD_LIKELY( fd_funk_txn_idx_is_null( sibling_prev_idx ) ) ) { /* opt for non-compete */
     224     1944147 :     ulong parent_idx = fd_funk_txn_idx( map[ txn_idx ].parent_cidx );
     225     1944147 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) { /* No older sib and is a funk child, opt for incr pub */
     226     1023474 :       funk->child_head_cidx = fd_funk_txn_cidx( sibling_next_idx );
     227     1023474 :     } else { /* No older sib and has parent */
     228      920673 :       ASSERT_IN_PREP( parent_idx );
     229      920673 :       map[ parent_idx ].child_head_cidx = fd_funk_txn_cidx( sibling_next_idx );
     230      920673 :     }
     231     1944147 :   } else { /* Has older sib */
     232      652767 :     ASSERT_IN_PREP( sibling_prev_idx );
     233      652767 :     map[ sibling_prev_idx ].sibling_next_cidx = fd_funk_txn_cidx( sibling_next_idx );
     234      652767 :   }
     235             : 
     236     2596914 :   if( FD_LIKELY( fd_funk_txn_idx_is_null( sibling_next_idx ) ) ) { /* opt for non-compete */
     237     2360598 :     ulong parent_idx = fd_funk_txn_idx( map[ txn_idx ].parent_cidx );
     238     2360598 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) { /* No younger sib and is a funk child, opt for incr pub */
     239     1197228 :       funk->child_tail_cidx = fd_funk_txn_cidx( sibling_prev_idx );
     240     1197228 :     } else { /* No younger sib and has parent */
     241     1163370 :       ASSERT_IN_PREP( parent_idx );
     242     1163370 :       map[ parent_idx ].child_tail_cidx = fd_funk_txn_cidx( sibling_prev_idx );
     243     1163370 :     }
     244     2360598 :   } else { /* Has younger sib */
     245      236316 :     ASSERT_IN_PREP( sibling_next_idx );
     246      236316 :     map[ sibling_next_idx ].sibling_prev_cidx = fd_funk_txn_cidx( sibling_prev_idx );
     247      236316 :   }
     248             : 
     249     2596914 :   fd_funk_txn_map_remove( map, fd_funk_txn_xid( &map[ txn_idx ] ) );
     250     2596914 : }
     251             : 
     252             : /* fd_funk_txn_cancel_family cancels a transaction and all its
     253             :    descendants in a tree-depth-first-ordered sense from youngest to
     254             :    oldest.  Callers have already validated our input arguments.  Returns
     255             :    the number of transactions canceled. */
     256             : 
     257             : static ulong
     258             : fd_funk_txn_cancel_family( fd_funk_t *     funk,
     259             :                            fd_funk_txn_t * map,
     260             :                            ulong           txn_max,
     261             :                            ulong           tag,
     262     1932246 :                            ulong           txn_idx ) {
     263     1932246 :   ulong cancel_cnt = 0UL;
     264             : 
     265     1932246 :   map[ txn_idx ].tag = tag;
     266             : 
     267     1932246 :   ulong parent_stack_idx = FD_FUNK_TXN_IDX_NULL;
     268             : 
     269     3261582 :   for(;;) {
     270             : 
     271             :     /* At this point, txn_idx appears to be valid and has been tagged. */
     272             : 
     273     3261582 :     ulong youngest_idx = fd_funk_txn_idx( map[ txn_idx ].child_tail_cidx );
     274     3261582 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( youngest_idx ) ) ) { /* txn is is childless, opt for incr pub */
     275             : 
     276     2596914 :       fd_funk_txn_cancel_childless( funk, map, txn_max, txn_idx ); /* If this can fail, return cancel_cnt here on fail */
     277     2596914 :       cancel_cnt++;
     278             : 
     279     2596914 :       txn_idx = parent_stack_idx;                                  /* Pop the parent stack */
     280     2596914 :       if( FD_LIKELY( fd_funk_txn_idx_is_null( txn_idx ) ) ) break; /* If stack is empty, we are done, opt for incr pub */
     281      664668 :       parent_stack_idx = fd_funk_txn_idx( map[ txn_idx ].stack_cidx );
     282      664668 :       continue;
     283     2596914 :     }
     284             : 
     285             :     /* txn has at least one child and the youngest is youngest_idx.  Tag
     286             :        the youngest child, push txn onto the parent stack and recurse
     287             :        into the youngest child. */
     288             : 
     289      664668 :     ASSERT_IN_PREP ( youngest_idx );
     290      664668 :     ASSERT_UNTAGGED( youngest_idx );
     291      664668 :     map[ youngest_idx ].tag = tag;
     292             : 
     293      664668 :     map[ txn_idx ].stack_cidx = fd_funk_txn_cidx( parent_stack_idx );
     294      664668 :     parent_stack_idx          = txn_idx;
     295             : 
     296      664668 :     txn_idx = youngest_idx;
     297      664668 :   }
     298             : 
     299     1932246 :   return cancel_cnt;
     300     1932246 : }
     301             : 
     302             : ulong
     303             : fd_funk_txn_cancel( fd_funk_t *     funk,
     304             :                     fd_funk_txn_t * txn,
     305     2634879 :                     int             verbose ) {
     306             : 
     307     2634879 :   if( FD_UNLIKELY( !funk ) ) {
     308      196464 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     309      196464 :     return 0UL;
     310      196464 :   }
     311             : 
     312     2438415 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) );
     313             : 
     314     2438415 :   ulong txn_max = funk->txn_max;
     315             : 
     316     2438415 :   ulong txn_idx = (ulong)(txn - map);
     317             : 
     318     2438415 :   if( FD_UNLIKELY( (txn_idx>=txn_max) /* Out of map (incl NULL) */ | (txn!=(map+txn_idx)) /* Bad alignment */ ) ) {
     319      786258 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not a funk transaction" ));
     320      786258 :     return 0UL;
     321      786258 :   }
     322             : 
     323     1652157 :   if( FD_UNLIKELY( !fd_funk_txn_map_query( map, fd_funk_txn_xid( txn ), NULL ) ) ) {
     324      188382 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not in preparation" ));
     325      188382 :     return 0UL;
     326      188382 :   }
     327             : 
     328     1463775 :   return fd_funk_txn_cancel_family( funk, map, txn_max, funk->cycle_tag++, txn_idx );
     329     1652157 : }
     330             : 
     331             : /* fd_funk_txn_oldest_sibling returns the index of the oldest sibling
     332             :    in txn_idx's family.  Callers have already validated our input
     333             :    arguments.  The caller should validate the return index. */
     334             : 
     335             : static inline ulong
     336             : fd_funk_txn_oldest_sibling( fd_funk_t *     funk,
     337             :                             fd_funk_txn_t * map,
     338             :                             ulong           txn_max,
     339     1161381 :                             ulong           txn_idx ) {
     340             : 
     341     1161381 :   ulong parent_idx = fd_funk_txn_idx( map[ txn_idx ].parent_cidx );
     342             : 
     343     1161381 :   if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) return fd_funk_txn_idx( funk->child_head_cidx ); /* opt for incr pub */
     344             : 
     345      431451 :   ASSERT_IN_PREP( parent_idx );
     346             : 
     347      431451 :   return fd_funk_txn_idx( map[ parent_idx ].child_head_cidx );
     348      431451 : }
     349             : 
     350             : /* fd_funk_txn_cancel_sibling_list cancels siblings from sibling_idx down
     351             :    to the youngest sibling inclusive in the order from youngest to
     352             :    sibling_idx.  Callers have already validated our input arguments
     353             :    except sibling_idx.  Returns the number of cancelled transactions
     354             :    (should be at least 1).  If any sibling is skip_idx, it will be not
     355             :    be cancelled but still tagged as visited.  Passing
     356             :    FD_FUNK_TXN_IDX_NULL for skip_idx will cancel all siblings from
     357             :    sibling_idx to the youngest inclusive. */
     358             : 
     359             : static ulong
     360             : fd_funk_txn_cancel_sibling_list( fd_funk_t *     funk,
     361             :                                  fd_funk_txn_t * map,
     362             :                                  ulong           txn_max,
     363             :                                  ulong           tag,
     364             :                                  ulong           sibling_idx,
     365     1161381 :                                  ulong           skip_idx ) {
     366             : 
     367     1161381 :   ulong cancel_stack_idx = FD_FUNK_TXN_IDX_NULL;
     368             : 
     369             :   /* Push siblings_idx and its younger siblings inclusive (skipping
     370             :      sibling skip_idx if encounter) onto the cancel stack from oldest to
     371             :      youngest (such that we cancel youngest to oldest). */
     372             : 
     373     1629852 :   for(;;) {
     374             : 
     375             :     /* At this point, sibling_idx is a sibling we might want to add to
     376             :        the sibling stack.  Validate and tag it. */
     377             : 
     378     1629852 :     ASSERT_IN_PREP ( sibling_idx );
     379     1629852 :     ASSERT_UNTAGGED( sibling_idx );
     380     1629852 :     map[ sibling_idx ].tag = tag;
     381             : 
     382     1629852 :     if( FD_UNLIKELY( sibling_idx!=skip_idx ) ) { /* Not skip_idx so push onto the cancel stack, opt for non-compete */
     383      468471 :       map[ sibling_idx ].stack_cidx = fd_funk_txn_cidx( cancel_stack_idx );
     384      468471 :       cancel_stack_idx = sibling_idx;
     385      468471 :     }
     386             : 
     387     1629852 :     ulong younger_idx = fd_funk_txn_idx( map[ sibling_idx ].sibling_next_cidx );
     388     1629852 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( younger_idx ) ) ) break; /* opt for non-compete */
     389      468471 :     sibling_idx = younger_idx;
     390             : 
     391      468471 :   }
     392             : 
     393             :   /* Cancel all transactions and their descendants on the cancel stack */
     394             : 
     395     1161381 :   ulong cancel_cnt = 0UL;
     396             : 
     397     1629852 :   while( !fd_funk_txn_idx_is_null( cancel_stack_idx ) ) { /* TODO: peel first iter to make more predictable? */
     398      468471 :     ulong sibling_idx = cancel_stack_idx;
     399      468471 :     cancel_stack_idx  = fd_funk_txn_idx( map[ sibling_idx ].stack_cidx );
     400             : 
     401      468471 :     cancel_cnt += fd_funk_txn_cancel_family( funk, map, txn_max, tag, sibling_idx );
     402      468471 :   }
     403             : 
     404     1161381 :   return cancel_cnt;
     405     1161381 : }
     406             : 
     407             : ulong
     408             : fd_funk_txn_cancel_siblings( fd_funk_t *     funk,
     409             :                              fd_funk_txn_t * txn,
     410           0 :                              int             verbose ) {
     411             : 
     412           0 :   if( FD_UNLIKELY( !funk ) ) {
     413           0 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     414           0 :     return 0UL;
     415           0 :   }
     416             : 
     417           0 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) );
     418             : 
     419           0 :   ulong txn_max = funk->txn_max;
     420             : 
     421           0 :   ulong txn_idx = (ulong)(txn - map);
     422             : 
     423           0 :   if( FD_UNLIKELY( (txn_idx>=txn_max) /* Out of map (incl NULL) */ | (txn!=(map+txn_idx)) /* Bad alignment */ ) ) {
     424           0 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not a funk transaction" ));
     425           0 :     return 0UL;
     426           0 :   }
     427             : 
     428           0 :   ulong oldest_idx = fd_funk_txn_oldest_sibling( funk, map, txn_max, txn_idx );
     429             : 
     430           0 :   return fd_funk_txn_cancel_sibling_list( funk, map, txn_max, funk->cycle_tag++, oldest_idx, txn_idx );
     431           0 : }
     432             : 
     433             : ulong
     434             : fd_funk_txn_cancel_children( fd_funk_t *     funk,
     435             :                              fd_funk_txn_t * txn,
     436           0 :                              int             verbose ) {
     437             : 
     438           0 :   if( FD_UNLIKELY( !funk ) ) {
     439           0 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     440           0 :     return 0UL;
     441           0 :   }
     442             : 
     443           0 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) );
     444             : 
     445           0 :   ulong txn_max = funk->txn_max;
     446             : 
     447           0 :   ulong oldest_idx;
     448             : 
     449           0 :   if( FD_LIKELY( txn == NULL ) ) {
     450           0 :     oldest_idx = fd_funk_txn_idx( funk->child_head_cidx ); /* opt for non-compete */
     451           0 :   } else {
     452           0 :     ulong txn_idx = (ulong)(txn - map);
     453           0 :     if( FD_UNLIKELY( (txn_idx>=txn_max) /* Out of map */ | (txn!=(map+txn_idx)) /* Bad alignment */ ) ) {
     454           0 :       if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not a funk transaction" ));
     455           0 :       return 0UL;
     456           0 :     }
     457             : 
     458           0 :     if( FD_UNLIKELY( !fd_funk_txn_map_query( map, fd_funk_txn_xid( txn ), NULL ) ) ) {
     459           0 :       if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not in preparation" ));
     460           0 :       return 0UL;
     461           0 :     }
     462             : 
     463           0 :     oldest_idx = fd_funk_txn_idx( txn->child_head_cidx );
     464           0 :   }
     465             : 
     466           0 :   if( fd_funk_txn_idx_is_null( oldest_idx ) ) {
     467           0 :     return 0UL;
     468           0 :   }
     469             : 
     470           0 :   return fd_funk_txn_cancel_sibling_list( funk, map, txn_max, funk->cycle_tag++, oldest_idx, FD_FUNK_TXN_IDX_NULL );
     471           0 : }
     472             : 
     473             : /* Cancel all outstanding transactions */
     474             : 
     475             : ulong
     476             : fd_funk_txn_cancel_all( fd_funk_t *     funk,
     477           0 :                         int             verbose ) {
     478           0 :   return fd_funk_txn_cancel_children( funk, NULL, verbose );
     479           0 : }
     480             : 
     481             : /* fd_funk_txn_update applies the record updates in transaction txn_idx
     482             :    to another transaction or the parent transaction.  Callers have
     483             :    already validated our input arguments.
     484             : 
     485             :    On entry, the head/tail of the destination records are at
     486             :    *_dst_rec_head_idx / *_dst_rec_tail_idx.  All transactions on this
     487             :    list will have transaction id dst_xid and vice versa.  That is, this
     488             :    is the record list the last published transaction or txn_idx's
     489             :    in-prep parent transaction.
     490             : 
     491             :    On exit, the head/tail of the updated records is at
     492             :    *_dst_rec_head_idx / *_dst_rec_tail_idx.  As before, all transactions
     493             :    on this list will have transaction id dst_xid and vice versa.
     494             :    Transaction txn_idx will have an _empty_ record list.
     495             : 
     496             :    Updates in the transaction txn_idx are processed from oldest to
     497             :    youngest.  If an update erases an existing record in dest, the record
     498             :    to erase is removed from the destination records without perturbing
     499             :    the order of remaining destination records.  If an update is to
     500             :    update an existing record, the destination record value is updated
     501             :    and the order of the destination records is unchanged.  If an update
     502             :    is to create a new record, the record is appended to the list of
     503             :    existing values as youngest without changing the order of existing
     504             :    values.  If an update erases a record in an in-prep parent, the
     505             :    erasure will be moved into the parent as the youngest without
     506             :    changing the order of existing values. */
     507             : 
     508             : static void
     509             : fd_funk_txn_update( ulong *                   _dst_rec_head_idx, /* Pointer to the dst list head */
     510             :                     ulong *                   _dst_rec_tail_idx, /* Pointer to the dst list tail */
     511             :                     ulong                     dst_txn_idx,       /* Transaction index of the merge destination */
     512             :                     fd_funk_txn_xid_t const * dst_xid,           /* dst xid */
     513             :                     ulong                     txn_idx,           /* Transaction index of the records to merge */
     514             :                     ulong                     rec_max,           /* ==funk->rec_max */
     515             :                     fd_funk_txn_t *           txn_map,           /* ==fd_funk_rec_map( funk, wksp ) */
     516             :                     fd_funk_rec_t *           rec_map,           /* ==fd_funk_rec_map( funk, wksp ) */
     517             :                     fd_funk_partvec_t *       partvec,           /* ==fd_funk_get_partvec( funk, wksp ) */
     518             :                     fd_alloc_t *              alloc,             /* ==fd_funk_alloc( funk, wksp ) */
     519     1161381 :                     fd_wksp_t *               wksp ) {           /* ==fd_funk_wksp( funk ) */
     520             :   /* We don't need to do all the individual removal pointer updates
     521             :      as we are removing the whole list from txn_idx.  */
     522             : 
     523     1161381 :   ulong rec_idx = txn_map[ txn_idx ].rec_head_idx;
     524     2724474 :   while( !fd_funk_rec_idx_is_null( rec_idx ) ) {
     525             :     /* Validate rec_idx */
     526             : 
     527     1563093 :     if( FD_UNLIKELY( rec_idx>=rec_max ) ) FD_LOG_CRIT(( "memory corruption detected (bad idx)" ));
     528     1563093 :     if( FD_UNLIKELY( fd_funk_txn_idx( rec_map[ rec_idx ].txn_cidx )!=txn_idx ) )
     529           0 :       FD_LOG_CRIT(( "memory corruption detected (cycle or bad idx)" ));
     530             : 
     531     1563093 :     fd_funk_rec_t * rec = &rec_map[ rec_idx ];
     532     1563093 :     ulong next_rec_idx = rec->next_idx;
     533             : 
     534             :     /* See if (dst_xid,key) already exists. Remove it if it does, and then clean up the corpse.
     535             :        We can take advantage of the ordering property that children
     536             :        come before parents in the hash chain, and all elements with
     537             :        the same record key have the same hash. */
     538     1563093 :     ulong * next = &rec->map_next;
     539     2058695 :     for(;;) {
     540     2058695 :       ulong ele_idx = fd_funk_rec_map_private_unbox_idx( *next );
     541     2058695 :       if( fd_funk_rec_map_private_is_null( ele_idx ) ) break;
     542     1938752 :       fd_funk_rec_t * ele = rec_map + ele_idx;
     543             : 
     544     1938752 :       if( FD_LIKELY( rec->map_hash == ele->map_hash ) &&
     545     1938752 :           FD_LIKELY( fd_funk_rec_key_eq( rec->pair.key, ele->pair.key ) ) &&
     546     1938752 :           FD_LIKELY( fd_funk_txn_xid_eq( dst_xid, ele->pair.xid ) ) ) {
     547             :         /* Remove from the transaction */
     548     1443150 :         ulong prev_idx = ele->prev_idx;
     549     1443150 :         ulong next_idx = ele->next_idx;
     550     1443150 :         if( fd_funk_rec_idx_is_null( prev_idx ) ) {
     551       72420 :           *_dst_rec_head_idx = next_idx;
     552     1370730 :         } else {
     553     1370730 :           rec_map[ prev_idx ].next_idx = next_idx;
     554     1370730 :         }
     555     1443150 :         if( fd_funk_rec_idx_is_null( next_idx ) ) {
     556       23253 :           *_dst_rec_tail_idx = prev_idx;
     557     1419897 :         } else {
     558     1419897 :           rec_map[ next_idx ].prev_idx = prev_idx;
     559     1419897 :         }
     560             :         /* Clean up value */
     561     1443150 :         fd_funk_val_flush( ele, alloc, wksp );
     562     1443150 :         ele->txn_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     563     1443150 :         fd_funk_part_set_intern( partvec, rec_map, ele, FD_FUNK_PART_NULL );
     564             :         /* Remove from record map */
     565     1443150 :         fd_funk_rec_map_private_t * priv = fd_funk_rec_map_private( rec_map );
     566     1443150 :         *next = ele->map_next;
     567     1443150 :         ele->map_next = priv->free_stack;
     568     1443150 :         priv->free_stack = (ele_idx | (1UL<<63));
     569     1443150 :         priv->key_cnt--;
     570     1443150 :         break;
     571     1443150 :       }
     572             : 
     573      495602 :       next = &ele->map_next;
     574      495602 :     }
     575             : 
     576             :     /* Add the new record to the transaction. We can update the xid in
     577             :        place because it is not used for hashing the element. We have
     578             :        to preserve the original element to preserve the
     579             :        newest-to-oldest ordering in the hash
     580             :        chain. fd_funk_rec_query_global relies on this subtle
     581             :        property. */
     582             : 
     583     1563093 :     rec->pair.xid[0] = *dst_xid;
     584     1563093 :     rec->txn_cidx = fd_funk_txn_cidx( dst_txn_idx );
     585             : 
     586     1563093 :     if( fd_funk_rec_idx_is_null( *_dst_rec_head_idx ) ) {
     587       11805 :       *_dst_rec_head_idx = rec_idx;
     588       11805 :       rec->prev_idx = FD_FUNK_REC_IDX_NULL;
     589     1551288 :     } else {
     590     1551288 :       rec_map[ *_dst_rec_tail_idx ].next_idx = rec_idx;
     591     1551288 :       rec->prev_idx = *_dst_rec_tail_idx;
     592     1551288 :     }
     593     1563093 :     *_dst_rec_tail_idx = rec_idx;
     594     1563093 :     rec->next_idx = FD_FUNK_REC_IDX_NULL;
     595             : 
     596     1563093 :     rec_idx = next_rec_idx;
     597     1563093 :   }
     598             : 
     599     1161381 :   txn_map[ txn_idx ].rec_head_idx = FD_FUNK_REC_IDX_NULL;
     600     1161381 :   txn_map[ txn_idx ].rec_tail_idx = FD_FUNK_REC_IDX_NULL;
     601     1161381 : }
     602             : 
     603             : /* fd_funk_txn_publish_funk_child publishes a transaction that is known
     604             :    to be a child of funk.  Callers have already validated our input
     605             :    arguments.  Returns FD_FUNK_SUCCESS on success and an FD_FUNK_ERR_*
     606             :    code on failure.  (There are currently no failure cases but the
     607             :    plumbing is there if value handling requires it at some point.) */
     608             : 
     609             : static int
     610             : fd_funk_txn_publish_funk_child( fd_funk_t *     funk,
     611             :                                 fd_funk_txn_t * map,
     612             :                                 ulong           txn_max,
     613             :                                 ulong           tag,
     614      723522 :                                 ulong           txn_idx ) {
     615             : 
     616      723522 :   fd_funk_check_write( funk );
     617             : 
     618             :   /* Apply the updates in txn to the last published transactions */
     619             : 
     620      723522 :   fd_wksp_t * wksp = fd_funk_wksp( funk );
     621      723522 :   fd_funk_txn_update( &funk->rec_head_idx, &funk->rec_tail_idx, FD_FUNK_TXN_IDX_NULL, fd_funk_root( funk ),
     622      723522 :                       txn_idx, funk->rec_max, map, fd_funk_rec_map( funk, wksp ), fd_funk_get_partvec( funk, wksp ),
     623      723522 :                       fd_funk_alloc( funk, wksp ), wksp );
     624             : 
     625             :   /* Cancel all competing transaction histories */
     626             : 
     627      723522 :   ulong oldest_idx = fd_funk_txn_oldest_sibling( funk, map, txn_max, txn_idx );
     628      723522 :   fd_funk_txn_cancel_sibling_list( funk, map, txn_max, tag, oldest_idx, txn_idx );
     629             : 
     630             :   /* Make all the children children of funk */
     631             : 
     632      723522 :   ulong child_head_idx = fd_funk_txn_idx( map[ txn_idx ].child_head_cidx );
     633      723522 :   ulong child_tail_idx = fd_funk_txn_idx( map[ txn_idx ].child_tail_cidx );
     634             : 
     635      723522 :   ulong child_idx = child_head_idx;
     636     1241010 :   while( FD_UNLIKELY( !fd_funk_txn_idx_is_null( child_idx ) ) ) { /* opt for incr pub */
     637             : 
     638      517488 :     ASSERT_IN_PREP ( child_idx );
     639      517488 :     ASSERT_UNTAGGED( child_idx );
     640      517488 :     map[ child_idx ].tag = tag;
     641             : 
     642      517488 :     map[ child_idx ].parent_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     643             : 
     644      517488 :     child_idx = fd_funk_txn_idx( map[ child_idx ].sibling_next_cidx );
     645      517488 :   }
     646             : 
     647      723522 :   funk->child_head_cidx = fd_funk_txn_cidx( child_head_idx );
     648      723522 :   funk->child_tail_cidx = fd_funk_txn_cidx( child_tail_idx );
     649             : 
     650             :   /* Remove the mapping */
     651             : 
     652      723522 :   fd_funk_txn_xid_copy( funk->last_publish, fd_funk_txn_xid( &map[ txn_idx ] ) );
     653             : 
     654      723522 :   fd_funk_txn_map_remove( map, fd_funk_txn_xid( &map[ txn_idx ] ) );
     655             : 
     656      723522 :   return FD_FUNK_SUCCESS;
     657      723522 : }
     658             : 
     659             : ulong
     660             : fd_funk_txn_publish( fd_funk_t *     funk,
     661             :                      fd_funk_txn_t * txn,
     662     1573926 :                      int             verbose ) {
     663             : 
     664     1573926 :   if( FD_UNLIKELY( !funk ) ) {
     665      196464 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     666      196464 :     return 0UL;
     667      196464 :   }
     668             : 
     669     1377462 :   fd_wksp_t * wksp = fd_funk_wksp( funk );
     670             : 
     671     1377462 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, wksp );
     672             : 
     673     1377462 :   ulong txn_max = funk->txn_max;
     674             : 
     675     1377462 :   ulong txn_idx = (ulong)(txn - map);
     676             : 
     677     1377462 :   if( FD_UNLIKELY( (txn_idx>=txn_max) /* Out of map (incl NULL) */ | (txn!=(map+txn_idx)) /* Bad alignment */ ) ) {
     678      786372 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not a funk transaction" ));
     679      786372 :     return 0UL;
     680      786372 :   }
     681             : 
     682      591090 :   if( FD_UNLIKELY( !fd_funk_txn_map_query( map, fd_funk_txn_xid( txn ), NULL ) ) ) {
     683      188382 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not in preparation" ));
     684      188382 :     return 0UL;
     685      188382 :   }
     686             : 
     687      402708 :   ulong tag = funk->cycle_tag++;
     688             : 
     689      402708 :   ulong publish_stack_idx = FD_FUNK_TXN_IDX_NULL;
     690             : 
     691      723522 :   for(;;) {
     692      723522 :     map[ txn_idx ].tag = tag;
     693             : 
     694             :     /* At this point, txn_idx is a transaction that needs to be
     695             :        published and has been tagged.  If txn is a child of funk, we are
     696             :        ready to publish txn and everything on the publish stack. */
     697             : 
     698      723522 :     ulong parent_idx = fd_funk_txn_idx( map[ txn_idx ].parent_cidx );
     699      723522 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) break; /* opt for incr pub */
     700             : 
     701             :     /* txn_idx has a parent.  Validate and tag it.  Push txn to the
     702             :        publish stack and recurse into the parent. */
     703             : 
     704      320814 :     ASSERT_IN_PREP ( parent_idx );
     705      320814 :     ASSERT_UNTAGGED( parent_idx );
     706             : 
     707      320814 :     map[ txn_idx ].stack_cidx = fd_funk_txn_cidx( publish_stack_idx );
     708      320814 :     publish_stack_idx         = txn_idx;
     709             : 
     710      320814 :     txn_idx = parent_idx;
     711      320814 :   }
     712             : 
     713      402708 :   ulong publish_cnt = 0UL;
     714             : 
     715      723522 :   for(;;) {
     716             : 
     717             :     /* At this point, all the transactions we need to publish are
     718             :        tagged, txn is the next up publish funk and publish_stack has the
     719             :        transactions to follow in order by pop.  We use a new tag for
     720             :        each publish as txn and its siblings we potentially visited in a
     721             :        previous iteration of this loop. */
     722             : 
     723      723522 :     if( FD_UNLIKELY( fd_funk_txn_publish_funk_child( funk, map, txn_max, funk->cycle_tag++, txn_idx ) ) ) break;
     724      723522 :     publish_cnt++;
     725             : 
     726      723522 :     txn_idx = publish_stack_idx;
     727      723522 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( txn_idx ) ) ) break; /* opt for incr pub */
     728      320814 :     publish_stack_idx = fd_funk_txn_idx( map[ txn_idx ].stack_cidx );
     729      320814 :   }
     730             : 
     731      402708 :   return publish_cnt;
     732      402708 : }
     733             : 
     734             : int
     735             : fd_funk_txn_publish_into_parent( fd_funk_t *     funk,
     736             :                                  fd_funk_txn_t * txn,
     737      437859 :                                  int             verbose ) {
     738      437859 :   if( FD_UNLIKELY( !funk ) ) {
     739           0 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     740           0 :     return FD_FUNK_ERR_INVAL;
     741           0 :   }
     742      437859 :   fd_funk_check_write( funk );
     743             : 
     744      437859 :   fd_wksp_t * wksp = fd_funk_wksp( funk );
     745             : 
     746      437859 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, wksp );
     747             : 
     748      437859 :   ulong txn_idx = (ulong)(txn - map);
     749             : 
     750      437859 :   ulong oldest_idx = fd_funk_txn_oldest_sibling( funk, map, funk->txn_max, txn_idx );
     751      437859 :   fd_funk_txn_cancel_sibling_list( funk, map, funk->txn_max, funk->cycle_tag++, oldest_idx, txn_idx );
     752             : 
     753      437859 :   ulong parent_idx = fd_funk_txn_idx( txn->parent_cidx );
     754      437859 :   if( fd_funk_txn_idx_is_null( parent_idx ) ) {
     755             :     /* Publish to root */
     756        6408 :     if( fd_funk_txn_idx( funk->child_head_cidx ) != txn_idx || fd_funk_txn_idx( funk->child_tail_cidx ) != txn_idx )
     757           0 :       FD_LOG_CRIT(( "memory corruption detected (cycle or bad idx)" ));
     758        6408 :     fd_funk_txn_update( &funk->rec_head_idx, &funk->rec_tail_idx, FD_FUNK_TXN_IDX_NULL, fd_funk_root( funk ),
     759        6408 :                         txn_idx, funk->rec_max, map, fd_funk_rec_map( funk, wksp ), fd_funk_get_partvec( funk, wksp ),
     760        6408 :                         fd_funk_alloc( funk, wksp ), wksp );
     761             :     /* Inherit the children */
     762        6408 :     funk->child_head_cidx = txn->child_head_cidx;
     763        6408 :     funk->child_tail_cidx = txn->child_tail_cidx;
     764      431451 :   } else {
     765      431451 :     fd_funk_txn_t * parent_txn = map + parent_idx;
     766      431451 :     if( fd_funk_txn_idx( parent_txn->child_head_cidx ) != txn_idx || fd_funk_txn_idx( parent_txn->child_tail_cidx ) != txn_idx )
     767           0 :       FD_LOG_CRIT(( "memory corruption detected (cycle or bad idx)" ));
     768      431451 :     fd_funk_txn_update( &parent_txn->rec_head_idx, &parent_txn->rec_tail_idx, parent_idx, &parent_txn->xid,
     769      431451 :                         txn_idx, funk->rec_max, map, fd_funk_rec_map( funk, wksp ), fd_funk_get_partvec( funk, wksp ),
     770      431451 :                         fd_funk_alloc( funk, wksp ), wksp );
     771             :     /* Inherit the children */
     772      431451 :     parent_txn->child_head_cidx = txn->child_head_cidx;
     773      431451 :     parent_txn->child_tail_cidx = txn->child_tail_cidx;
     774      431451 :   }
     775             : 
     776             :   /* Adjust the parent pointers of the children to point to their grandparent */
     777      437859 :   ulong child_idx = fd_funk_txn_idx( txn->child_head_cidx );
     778      461478 :   while( FD_UNLIKELY( !fd_funk_txn_idx_is_null( child_idx ) ) ) {
     779       23619 :     map[ child_idx ].parent_cidx = fd_funk_txn_cidx( parent_idx );
     780       23619 :     child_idx = fd_funk_txn_idx( map[ child_idx ].sibling_next_cidx );
     781       23619 :   }
     782             : 
     783      437859 :   fd_funk_txn_map_remove( map, fd_funk_txn_xid( txn ) );
     784             : 
     785      437859 :   return FD_FUNK_SUCCESS;
     786      437859 : }
     787             : 
     788             : /* Unfortunately, the semantics of how to deal with the same record
     789             :    key appearing in multiple children is not defined. So, for now,
     790             :    this API is commented out. */
     791             : #if 0
     792             : int
     793             : fd_funk_txn_merge_all_children( fd_funk_t *     funk,
     794             :                                 fd_funk_txn_t * parent_txn,
     795             :                                 int             verbose ) {
     796             :   if( FD_UNLIKELY( !funk ) ) {
     797             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     798             :     return FD_FUNK_ERR_INVAL;
     799             :   }
     800             :   fd_funk_check_write( funk );
     801             : 
     802             :   fd_wksp_t * wksp = fd_funk_wksp( funk );
     803             : 
     804             :   fd_funk_txn_t * map = fd_funk_txn_map( funk, wksp );
     805             :   ulong           txn_max = funk->txn_max;                 /* Previously verified */
     806             : 
     807             :   ulong parent_idx;
     808             :   fd_funk_txn_xid_t * parent_xid;
     809             :   uint * child_head_cidx;
     810             :   uint * child_tail_cidx;
     811             :   ulong * rec_head_idx;
     812             :   ulong * rec_tail_idx;
     813             :   if( parent_txn == NULL ) { /* Root */
     814             :     parent_idx = FD_FUNK_TXN_IDX_NULL;
     815             :     parent_xid = funk->root;
     816             :     child_head_cidx = &funk->child_head_cidx;
     817             :     child_tail_cidx = &funk->child_tail_cidx;
     818             :     rec_head_idx = &funk->rec_head_idx;
     819             :     rec_tail_idx = &funk->rec_tail_idx;
     820             :   } else {
     821             :     parent_idx = (ulong)(parent_txn - map);
     822             :     ASSERT_IN_PREP( parent_idx );
     823             :     parent_xid = &parent_txn->xid;
     824             :     child_head_cidx = &parent_txn->child_head_cidx;
     825             :     child_tail_cidx = &parent_txn->child_tail_cidx;
     826             :     rec_head_idx = &parent_txn->rec_head_idx;
     827             :     rec_tail_idx = &parent_txn->rec_tail_idx;
     828             :   }
     829             : 
     830             :   ulong child_head_idx = fd_funk_txn_idx( *child_head_cidx );
     831             :   ulong child_idx = child_head_idx;
     832             :   while( FD_UNLIKELY( !fd_funk_txn_idx_is_null( child_idx ) ) ) { /* opt for incr pub */
     833             :     /* Merge records from child into parent */
     834             : 
     835             :     fd_funk_txn_t * txn = &map[ child_idx ];
     836             :     if( FD_UNLIKELY( !fd_funk_txn_idx_is_null( fd_funk_txn_idx( txn->child_head_cidx ) ) ) ) {
     837             :       FD_LOG_WARNING(("cannot call fd_funk_txn_merge_all_children if parent_txn has grandchildren"));
     838             :       return FD_FUNK_ERR_TXN;
     839             :     }
     840             : 
     841             :     fd_funk_txn_update( rec_head_idx, rec_tail_idx, parent_idx, parent_xid,
     842             :                         child_idx, funk->rec_max, map, fd_funk_rec_map( funk, wksp ), fd_funk_get_partvec( funk, wksp ),
     843             :                         fd_funk_alloc( funk, wksp ), wksp );
     844             : 
     845             :     child_idx = fd_funk_txn_idx( txn->sibling_next_cidx );
     846             :     fd_funk_txn_map_remove( map, fd_funk_txn_xid( txn ) );
     847             : 
     848             :     /* Update the pointers as we go in case we stop in the middle
     849             :      */
     850             :     *child_head_cidx = fd_funk_txn_cidx( child_idx );
     851             :     if( FD_UNLIKELY( !fd_funk_txn_idx_is_null( child_idx ) ) ) {
     852             :       map[ child_idx ].sibling_prev_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     853             :     }
     854             :   }
     855             : 
     856             :   *child_tail_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     857             : 
     858             :   return FD_FUNK_SUCCESS;
     859             : }
     860             : #endif
     861             : 
     862             : /* Return the first record in a transaction. Returns NULL if the
     863             :    transaction has no records yet. */
     864             : 
     865             : FD_FN_PURE fd_funk_rec_t const *
     866             : fd_funk_txn_first_rec( fd_funk_t *           funk,
     867     5268909 :                        fd_funk_txn_t const * txn ) {
     868     5268909 :   ulong rec_idx;
     869     5268909 :   if( FD_UNLIKELY( NULL == txn ))
     870      255000 :     rec_idx = funk->rec_head_idx;
     871     5013909 :   else
     872     5013909 :     rec_idx = txn->rec_head_idx;
     873     5268909 :   if( fd_funk_rec_idx_is_null( rec_idx ) ) return NULL;
     874     3231006 :   fd_funk_rec_t const * rec_map = fd_funk_rec_map( funk, fd_funk_wksp( funk ) );
     875     3231006 :   return rec_map + rec_idx;
     876     5268909 : }
     877             : 
     878             : /* Return the next record in a transaction. Returns NULL if the
     879             :    transaction has no more records. */
     880             : 
     881             : FD_FN_PURE fd_funk_rec_t const *
     882             : fd_funk_txn_next_rec( fd_funk_t *           funk,
     883    46743843 :                       fd_funk_rec_t const * rec ) {
     884    46743843 :   ulong rec_idx = rec->next_idx;
     885    46743843 :   if( fd_funk_rec_idx_is_null( rec_idx ) ) return NULL;
     886    43512837 :   fd_funk_rec_t const * rec_map = fd_funk_rec_map( funk, fd_funk_wksp( funk ) );
     887    43512837 :   return rec_map + rec_idx;
     888    46743843 : }
     889             : 
     890             : fd_funk_txn_xid_t
     891    21280944 : fd_funk_generate_xid(void) {
     892    21280944 :   fd_funk_txn_xid_t xid;
     893    21280944 :   static FD_TL ulong seq = 0;
     894    21280944 :   xid.ul[0] =
     895    21280944 :     (fd_log_cpu_id() + 1U)*3138831853UL +
     896    21280944 :     (fd_log_thread_id() + 1U)*9180195821UL +
     897    21280944 :     (++seq)*6208101967UL;
     898    21280944 :   xid.ul[1] = ((ulong)fd_tickcount())*2810745731UL;
     899    21280944 :   return xid;
     900    21280944 : }
     901             : 
     902             : int
     903    22275108 : fd_funk_txn_verify( fd_funk_t * funk ) {
     904    22275108 :   fd_wksp_t *     wksp    = fd_funk_wksp( funk );          /* Previously verified */
     905    22275108 :   fd_funk_txn_t * map     = fd_funk_txn_map( funk, wksp ); /* Previously verified */
     906    22275108 :   ulong           txn_max = funk->txn_max;                 /* Previously verified */
     907             : 
     908    22275108 :   ulong funk_child_head_idx = fd_funk_txn_idx( funk->child_head_cidx ); /* Previously verified */
     909    22275108 :   ulong funk_child_tail_idx = fd_funk_txn_idx( funk->child_tail_cidx ); /* Previously verified */
     910             : 
     911    22275108 :   fd_funk_txn_xid_t const * last_publish = funk->last_publish; /* Previously verified */
     912             : 
     913   745948386 : # define TEST(c) do {                                                                           \
     914   745948386 :     if( FD_UNLIKELY( !(c) ) ) { FD_LOG_WARNING(( "FAIL: %s", #c )); return FD_FUNK_ERR_INVAL; } \
     915   745948386 :   } while(0)
     916             : 
     917    22275108 : # define IS_VALID( idx ) ( (idx==FD_FUNK_TXN_IDX_NULL) || \
     918    22275108 :                            ((idx<txn_max) && (!fd_funk_txn_xid_eq( fd_funk_txn_xid( &map[idx] ), last_publish ))) )
     919             : 
     920    22275108 :   TEST( !fd_funk_txn_map_verify( map ) );
     921             : 
     922    22275108 :   ulong free_cnt = txn_max - fd_funk_txn_map_key_cnt( map );
     923             : 
     924             :   /* Tag all transactions as not visited yet */
     925             : 
     926   761131044 :   for( ulong txn_idx=0UL; txn_idx<txn_max; txn_idx++ ) map[ txn_idx ].tag = 0UL;
     927             : 
     928             :   /* Visit all transactions in preparation, traversing from oldest to
     929             :      youngest. */
     930             : 
     931    22275108 :   ulong prep_cnt = 0UL;
     932    22275108 :   do {
     933             : 
     934             :     /* Push all children of funk to the stack */
     935             : 
     936    22275108 :     ulong stack_idx = FD_FUNK_TXN_IDX_NULL;
     937    22275108 :     ulong child_idx = funk_child_head_idx;
     938    52164405 :     while( !fd_funk_txn_idx_is_null( child_idx ) ) {
     939             : 
     940             :       /* Make sure valid idx, not tagged (detects cycles) and child
     941             :          knows it is a child of funk.  Then tag as visited / in-prep,
     942             :          push to stack and update prep_cnt */
     943             : 
     944    29889297 :       TEST( IS_VALID( child_idx ) );
     945    29889297 :       TEST( !map[ child_idx ].tag );
     946    29889297 :       TEST( fd_funk_txn_idx_is_null( fd_funk_txn_idx( map[ child_idx ].parent_cidx ) ) );
     947    29889297 :       map[ child_idx ].tag        = 1UL;
     948    29889297 :       map[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
     949    29889297 :       stack_idx                   = child_idx;
     950    29889297 :       prep_cnt++;
     951             : 
     952    29889297 :       ulong next_idx = fd_funk_txn_idx( map[ child_idx ].sibling_next_cidx );
     953    29889297 :       if( !fd_funk_txn_idx_is_null( next_idx ) ) TEST( fd_funk_txn_idx( map[ next_idx ].sibling_prev_cidx )==child_idx );
     954    29889297 :       child_idx = next_idx;
     955    29889297 :     }
     956             : 
     957   119006091 :     while( !fd_funk_txn_idx_is_null( stack_idx ) ) {
     958             : 
     959             :       /* Pop the next transaction to traverse */
     960             : 
     961    96730983 :       ulong txn_idx = stack_idx;
     962    96730983 :       stack_idx = fd_funk_txn_idx( map[ txn_idx ].stack_cidx );
     963             : 
     964             :       /* Push all children of txn to the stack */
     965             : 
     966    96730983 :       ulong child_idx = fd_funk_txn_idx( map[ txn_idx ].child_head_cidx );
     967   163572669 :       while( !fd_funk_txn_idx_is_null( child_idx ) ) {
     968             : 
     969             :         /* Make sure valid idx, not tagged (detects cycles) and child
     970             :            knows it is a child of txn_idx.  Then tag as visited /
     971             :            in-prep, push to stack and update prep_cnt */
     972             : 
     973    66841686 :         TEST( IS_VALID( child_idx ) );
     974    66841686 :         TEST( !map[ child_idx ].tag );
     975    66841686 :         TEST( fd_funk_txn_idx( map[ child_idx ].parent_cidx )==txn_idx );
     976    66841686 :         map[ child_idx ].tag        = 1UL;
     977    66841686 :         map[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
     978    66841686 :         stack_idx                   = child_idx;
     979    66841686 :         prep_cnt++;
     980             : 
     981    66841686 :         ulong next_idx = fd_funk_txn_idx( map[ child_idx ].sibling_next_cidx );
     982    66841686 :         if( !fd_funk_txn_idx_is_null( next_idx ) ) TEST( fd_funk_txn_idx( map[ next_idx ].sibling_prev_cidx )==child_idx );
     983    66841686 :         child_idx = next_idx;
     984    66841686 :       }
     985    96730983 :     }
     986             : 
     987    22275108 :   } while(0);
     988             : 
     989    22275108 :   TEST( (free_cnt+prep_cnt)==txn_max );
     990             : 
     991             :   /* Do it again with a youngest to oldest traversal to test reverse
     992             :      link integrity */
     993             : 
     994    22275108 :   prep_cnt = 0UL;
     995    22275108 :   do {
     996             : 
     997             :     /* Push all children of funk to the stack */
     998             : 
     999    22275108 :     ulong stack_idx = FD_FUNK_TXN_IDX_NULL;
    1000    22275108 :     ulong child_idx = funk_child_tail_idx;
    1001    52164405 :     while( !fd_funk_txn_idx_is_null( child_idx ) ) {
    1002             : 
    1003             :       /* Make sure valid idx, tagged as visited above (detects cycles)
    1004             :          and child knows it is a child of funk.  Then tag as visited /
    1005             :          in-prep, push to stack and update prep_cnt */
    1006             : 
    1007    29889297 :       TEST( IS_VALID( child_idx ) );
    1008    29889297 :       TEST( map[ child_idx ].tag==1UL );
    1009    29889297 :       TEST( fd_funk_txn_idx_is_null( fd_funk_txn_idx( map[ child_idx ].parent_cidx ) ) );
    1010    29889297 :       map[ child_idx ].tag        = 2UL;
    1011    29889297 :       map[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
    1012    29889297 :       stack_idx                   = child_idx;
    1013    29889297 :       prep_cnt++;
    1014             : 
    1015    29889297 :       ulong prev_idx = fd_funk_txn_idx( map[ child_idx ].sibling_prev_cidx );
    1016    29889297 :       if( !fd_funk_txn_idx_is_null( prev_idx ) ) TEST( fd_funk_txn_idx( map[ prev_idx ].sibling_next_cidx )==child_idx );
    1017    29889297 :       child_idx = prev_idx;
    1018    29889297 :     }
    1019             : 
    1020   119006091 :     while( !fd_funk_txn_idx_is_null( stack_idx ) ) {
    1021             : 
    1022             :       /* Pop the next transaction to traverse */
    1023             : 
    1024    96730983 :       ulong txn_idx = stack_idx;
    1025    96730983 :       stack_idx = fd_funk_txn_idx( map[ txn_idx ].stack_cidx );
    1026             : 
    1027             :       /* Push all children of txn to the stack */
    1028             : 
    1029    96730983 :       ulong child_idx = fd_funk_txn_idx( map[ txn_idx ].child_tail_cidx );
    1030   163572669 :       while( !fd_funk_txn_idx_is_null( child_idx ) ) {
    1031             : 
    1032             :         /* Make sure valid idx, tagged as visited above (detects cycles)
    1033             :            and child knows it is a child of txn_idx.  Then, tag as
    1034             :            visited / in-prep, push to stack and update prep_cnt */
    1035             : 
    1036    66841686 :         TEST( IS_VALID( child_idx ) );
    1037    66841686 :         TEST( map[ child_idx ].tag==1UL );
    1038    66841686 :         TEST( fd_funk_txn_idx( map[ child_idx ].parent_cidx )==txn_idx );
    1039    66841686 :         map[ child_idx ].tag        = 2UL;
    1040    66841686 :         map[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
    1041    66841686 :         stack_idx                   = child_idx;
    1042    66841686 :         prep_cnt++;
    1043             : 
    1044    66841686 :         ulong prev_idx = fd_funk_txn_idx( map[ child_idx ].sibling_prev_cidx );
    1045    66841686 :         if( !fd_funk_txn_idx_is_null( prev_idx ) ) TEST( fd_funk_txn_idx( map[ prev_idx ].sibling_next_cidx )==child_idx );
    1046    66841686 :         child_idx = prev_idx;
    1047    66841686 :       }
    1048    96730983 :     }
    1049    22275108 :   } while(0);
    1050             : 
    1051    22275108 :   TEST( (free_cnt+prep_cnt)==txn_max );
    1052             : 
    1053    22275108 :   TEST( fd_funk_txn_cnt( map )==prep_cnt );
    1054             : 
    1055    22275108 : # undef IS_VALID
    1056    22275108 : # undef TEST
    1057             : 
    1058    22275108 :   return FD_FUNK_SUCCESS;
    1059    22275108 : }
    1060             : 
    1061             : #undef ASSERT_UNTAGGED
    1062             : #undef ASSERT_IN_PREP
    1063             : #undef ASSERT_IN_MAP

Generated by: LCOV version 1.14