LCOV - code coverage report
Current view: top level - funk - fd_funk_txn.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 522 583 89.5 %
Date: 2024-11-13 11:58:15 Functions: 15 18 83.3 %

          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  1720807782 : #define MAP_T                 fd_funk_txn_t
       7             : #define MAP_KEY_T             fd_funk_txn_xid_t
       8     2547906 : #define MAP_KEY               xid
       9             : #define MAP_KEY_EQ(k0,k1)     fd_funk_txn_xid_eq((k0),(k1))
      10   858799176 : #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  1376335325 : #define MAP_NEXT              map_next
      13  2024801844 : #define MAP_HASH              map_hash
      14      110571 : #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             :    a 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     2547906 : #define ASSERT_IN_MAP( txn_idx ) do {                          \
      52     2547906 :     if( FD_UNLIKELY( txn_idx>=txn_max ) )                      \
      53     2547906 :       FD_LOG_CRIT(( "memory corruption detected (bad_idx)" )); \
      54     2547906 :   } while(0)
      55             : 
      56     4737072 : #define ASSERT_IN_PREP( txn_idx ) do {                                                           \
      57     4737072 :     if( FD_UNLIKELY( txn_idx>=txn_max ) )                                                        \
      58     4737072 :       FD_LOG_CRIT(( "memory corruption detected (bad_idx)" ));                                   \
      59     4737072 :     if( FD_UNLIKELY( !fd_funk_txn_map_query( map, fd_funk_txn_xid( &map[ txn_idx ] ), NULL ) ) ) \
      60     4737072 :       FD_LOG_CRIT(( "memory corruption detected (not in prep)" ));                               \
      61     4737072 :   } while(0)
      62             : 
      63     2097456 : #define ASSERT_UNTAGGED( txn_idx ) do {                      \
      64     2097456 :     if( FD_UNLIKELY( map[ txn_idx ].tag==tag ) )             \
      65     2097456 :       FD_LOG_CRIT(( "memory corruption detected (cycle)" )); \
      66     2097456 :   } 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     3784281 :                      int                       verbose ) {
      73             : 
      74     3784281 :   if( FD_UNLIKELY( !funk ) ) {
      75      196464 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
      76      196464 :     return NULL;
      77      196464 :   }
      78     3587817 :   fd_funk_check_write( funk );
      79             : 
      80     3587817 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) );
      81             : 
      82     3587817 :   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     3587793 :   ulong txn_max = funk->txn_max;
      88             : 
      89     3587793 :   ulong  parent_idx;
      90     3587793 :   uint * _child_head_cidx;
      91     3587793 :   uint * _child_tail_cidx;
      92             : 
      93     3587793 :   if( FD_LIKELY( !parent ) ) { /* opt for incr pub */
      94             : 
      95     1735311 :     parent_idx = FD_FUNK_TXN_IDX_NULL;
      96             : 
      97     1735311 :     _child_head_cidx = &funk->child_head_cidx;
      98     1735311 :     _child_tail_cidx = &funk->child_tail_cidx;
      99             : 
     100     1852482 :   } else {
     101             : 
     102     1852482 :     parent_idx = (ulong)(parent - map);
     103             : 
     104     1852482 :     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     1656021 :     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     1467639 :     _child_head_cidx = &parent->child_head_cidx;
     115     1467639 :     _child_tail_cidx = &parent->child_tail_cidx;
     116             : 
     117     1467639 :   }
     118             : 
     119     3202950 :   if( FD_UNLIKELY( !xid ) ) {
     120      196461 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL xid" ));
     121      196461 :     return NULL;
     122      196461 :   }
     123             : 
     124     3006489 :   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     3006483 :   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     2810028 :   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     2547906 :   fd_funk_txn_t * txn     = fd_funk_txn_map_insert( map, xid );
     142     2547906 :   ulong           txn_idx = (ulong)(txn - map);
     143     2547906 :   ASSERT_IN_MAP( txn_idx );
     144             : 
     145             :   /* Join the family */
     146             : 
     147     2547906 :   ulong sibling_prev_idx = fd_funk_txn_idx( *_child_tail_cidx );
     148             : 
     149     2547906 :   int first_born = fd_funk_txn_idx_is_null( sibling_prev_idx );
     150     2547906 :   if( FD_UNLIKELY( !first_born ) ) ASSERT_IN_PREP( sibling_prev_idx ); /* opt for non-compete */
     151             : 
     152     2547906 :   txn->parent_cidx       = fd_funk_txn_cidx( parent_idx           );
     153     2547906 :   txn->child_head_cidx   = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     154     2547906 :   txn->child_tail_cidx   = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     155     2547906 :   txn->sibling_prev_cidx = fd_funk_txn_cidx( sibling_prev_idx     );
     156     2547906 :   txn->sibling_next_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     157     2547906 :   txn->stack_cidx        = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     158     2547906 :   txn->tag               = 0UL;
     159             : 
     160     2547906 :   txn->rec_head_idx = FD_FUNK_REC_IDX_NULL;
     161     2547906 :   txn->rec_tail_idx = FD_FUNK_REC_IDX_NULL;
     162             : 
     163             :   /* TODO: consider branchless impl */
     164     2547906 :   if( FD_LIKELY( first_born ) ) *_child_head_cidx                         = fd_funk_txn_cidx( txn_idx ); /* opt for non-compete */
     165      499149 :   else                          map[ sibling_prev_idx ].sibling_next_cidx = fd_funk_txn_cidx( txn_idx );
     166             : 
     167     2547906 :   *_child_tail_cidx = fd_funk_txn_cidx( txn_idx );
     168             : 
     169     2547906 :   return txn;
     170     2547906 : }
     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     1772601 :                               ulong           txn_idx ) {
     183             : 
     184     1772601 :   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     1772601 :   fd_wksp_t *     wksp    = fd_funk_wksp   ( funk );
     193     1772601 :   fd_alloc_t *    alloc   = fd_funk_alloc  ( funk, wksp );
     194     1772601 :   fd_funk_rec_t * rec_map = fd_funk_rec_map( funk, wksp );
     195     1772601 :   fd_funk_partvec_t * partvec = fd_funk_get_partvec( funk, wksp );
     196     1772601 :   ulong           rec_max = funk->rec_max;
     197             : 
     198     1772601 :   ulong rec_idx = map[ txn_idx ].rec_head_idx;
     199     5789493 :   while( !fd_funk_rec_idx_is_null( rec_idx ) ) {
     200             : 
     201     4016892 :     if( FD_UNLIKELY( rec_idx>=rec_max ) ) FD_LOG_CRIT(( "memory corruption detected (bad idx)" ));
     202     4016892 :     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     4016892 :     ulong next_idx = rec_map[ rec_idx ].next_idx;
     206     4016892 :     rec_map[ rec_idx ].txn_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     207             : 
     208     4016892 :     fd_funk_val_flush( &rec_map[ rec_idx ], alloc, wksp );
     209     4016892 :     fd_funk_part_set_intern( partvec, rec_map, &rec_map[ rec_idx ], FD_FUNK_PART_NULL );
     210             : 
     211     4016892 :     fd_funk_rec_map_remove( rec_map, fd_funk_rec_pair( &rec_map[ rec_idx ] ) );
     212             : 
     213     4016892 :     rec_idx = next_idx;
     214     4016892 :   }
     215             : 
     216             :   /* Leave the family */
     217             : 
     218     1772601 :   ulong sibling_prev_idx = fd_funk_txn_idx( map[ txn_idx ].sibling_prev_cidx );
     219     1772601 :   ulong sibling_next_idx = fd_funk_txn_idx( map[ txn_idx ].sibling_next_cidx );
     220             : 
     221             :   /* TODO: Consider branchless impl */
     222             : 
     223     1772601 :   if( FD_LIKELY( fd_funk_txn_idx_is_null( sibling_prev_idx ) ) ) { /* opt for non-compete */
     224     1375761 :     ulong parent_idx = fd_funk_txn_idx( map[ txn_idx ].parent_cidx );
     225     1375761 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) { /* No older sib and is a funk child, opt for incr pub */
     226      695607 :       funk->child_head_cidx = fd_funk_txn_cidx( sibling_next_idx );
     227      695607 :     } else { /* No older sib and has parent */
     228      680154 :       ASSERT_IN_PREP( parent_idx );
     229      680154 :       map[ parent_idx ].child_head_cidx = fd_funk_txn_cidx( sibling_next_idx );
     230      680154 :     }
     231     1375761 :   } else { /* Has older sib */
     232      396840 :     ASSERT_IN_PREP( sibling_prev_idx );
     233      396840 :     map[ sibling_prev_idx ].sibling_next_cidx = fd_funk_txn_cidx( sibling_next_idx );
     234      396840 :   }
     235             : 
     236     1772601 :   if( FD_LIKELY( fd_funk_txn_idx_is_null( sibling_next_idx ) ) ) { /* opt for non-compete */
     237     1604259 :     ulong parent_idx = fd_funk_txn_idx( map[ txn_idx ].parent_cidx );
     238     1604259 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) { /* No younger sib and is a funk child, opt for incr pub */
     239      820449 :       funk->child_tail_cidx = fd_funk_txn_cidx( sibling_prev_idx );
     240      820449 :     } else { /* No younger sib and has parent */
     241      783810 :       ASSERT_IN_PREP( parent_idx );
     242      783810 :       map[ parent_idx ].child_tail_cidx = fd_funk_txn_cidx( sibling_prev_idx );
     243      783810 :     }
     244     1604259 :   } else { /* Has younger sib */
     245      168342 :     ASSERT_IN_PREP( sibling_next_idx );
     246      168342 :     map[ sibling_next_idx ].sibling_prev_cidx = fd_funk_txn_cidx( sibling_prev_idx );
     247      168342 :   }
     248             : 
     249     1772601 :   fd_funk_txn_map_remove( map, fd_funk_txn_xid( &map[ txn_idx ] ) );
     250     1772601 : }
     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     1466112 :                            ulong           txn_idx ) {
     263     1466112 :   ulong cancel_cnt = 0UL;
     264             : 
     265     1466112 :   map[ txn_idx ].tag = tag;
     266             : 
     267     1466112 :   ulong parent_stack_idx = FD_FUNK_TXN_IDX_NULL;
     268             : 
     269     2079090 :   for(;;) {
     270             : 
     271             :     /* At this point, txn_idx appears to be valid and has been tagged. */
     272             : 
     273     2079090 :     ulong youngest_idx = fd_funk_txn_idx( map[ txn_idx ].child_tail_cidx );
     274     2079090 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( youngest_idx ) ) ) { /* txn is is childless, opt for incr pub */
     275             : 
     276     1772601 :       fd_funk_txn_cancel_childless( funk, map, txn_max, txn_idx ); /* If this can fail, return cancel_cnt here on fail */
     277     1772601 :       cancel_cnt++;
     278             : 
     279     1772601 :       txn_idx = parent_stack_idx;                                  /* Pop the parent stack */
     280     1772601 :       if( FD_LIKELY( fd_funk_txn_idx_is_null( txn_idx ) ) ) break; /* If stack is empty, we are done, opt for incr pub */
     281      306489 :       parent_stack_idx = fd_funk_txn_idx( map[ txn_idx ].stack_cidx );
     282      306489 :       continue;
     283     1772601 :     }
     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      306489 :     ASSERT_IN_PREP ( youngest_idx );
     290      306489 :     ASSERT_UNTAGGED( youngest_idx );
     291      306489 :     map[ youngest_idx ].tag = tag;
     292             : 
     293      306489 :     map[ txn_idx ].stack_cidx = fd_funk_txn_cidx( parent_stack_idx );
     294      306489 :     parent_stack_idx          = txn_idx;
     295             : 
     296      306489 :     txn_idx = youngest_idx;
     297      306489 :   }
     298             : 
     299     1466112 :   return cancel_cnt;
     300     1466112 : }
     301             : 
     302             : ulong
     303             : fd_funk_txn_cancel( fd_funk_t *     funk,
     304             :                     fd_funk_txn_t * txn,
     305     2321292 :                     int             verbose ) {
     306             : 
     307     2321292 :   if( FD_UNLIKELY( !funk ) ) {
     308      196464 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     309      196464 :     return 0UL;
     310      196464 :   }
     311             : 
     312     2124828 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) );
     313             : 
     314     2124828 :   ulong txn_max = funk->txn_max;
     315             : 
     316     2124828 :   ulong txn_idx = (ulong)(txn - map);
     317             : 
     318     2124828 :   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     1338570 :   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     1150188 :   return fd_funk_txn_cancel_family( funk, map, txn_max, funk->cycle_tag++, txn_idx );
     329     1338570 : }
     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      772650 :                             ulong           txn_idx ) {
     340             : 
     341      772650 :   ulong parent_idx = fd_funk_txn_idx( map[ txn_idx ].parent_cidx );
     342             : 
     343      772650 :   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      109020 :   ASSERT_IN_PREP( parent_idx );
     346             : 
     347      109020 :   return fd_funk_txn_idx( map[ parent_idx ].child_head_cidx );
     348      109020 : }
     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      772650 :                                  ulong           skip_idx ) {
     366             : 
     367      772650 :   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     1088574 :   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     1088574 :     ASSERT_IN_PREP ( sibling_idx );
     379     1088574 :     ASSERT_UNTAGGED( sibling_idx );
     380     1088574 :     map[ sibling_idx ].tag = tag;
     381             : 
     382     1088574 :     if( FD_UNLIKELY( sibling_idx!=skip_idx ) ) { /* Not skip_idx so push onto the cancel stack, opt for non-compete */
     383      315924 :       map[ sibling_idx ].stack_cidx = fd_funk_txn_cidx( cancel_stack_idx );
     384      315924 :       cancel_stack_idx = sibling_idx;
     385      315924 :     }
     386             : 
     387     1088574 :     ulong younger_idx = fd_funk_txn_idx( map[ sibling_idx ].sibling_next_cidx );
     388     1088574 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( younger_idx ) ) ) break; /* opt for non-compete */
     389      315924 :     sibling_idx = younger_idx;
     390             : 
     391      315924 :   }
     392             : 
     393             :   /* Cancel all transactions and their descendants on the cancel stack */
     394             : 
     395      772650 :   ulong cancel_cnt = 0UL;
     396             : 
     397     1088574 :   while( !fd_funk_txn_idx_is_null( cancel_stack_idx ) ) { /* TODO: peel first iter to make more predictable? */
     398      315924 :     ulong sibling_idx = cancel_stack_idx;
     399      315924 :     cancel_stack_idx  = fd_funk_txn_idx( map[ sibling_idx ].stack_cidx );
     400             : 
     401      315924 :     cancel_cnt += fd_funk_txn_cancel_family( funk, map, txn_max, tag, sibling_idx );
     402      315924 :   }
     403             : 
     404      772650 :   return cancel_cnt;
     405      772650 : }
     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      775044 :                     fd_wksp_t *               wksp ) {           /* ==fd_funk_wksp( funk ) */
     520             :   /* We don't need to to do all the individual removal pointer updates
     521             :      as we are removing the whole list from txn_idx.  Likewise, we
     522             :      temporarily repurpose txn_cidx as a loop detector for additional
     523             :      corruption protection.  */
     524             : 
     525      775044 :   ulong rec_idx = txn_map[ txn_idx ].rec_head_idx;
     526     1928079 :   while( !fd_funk_rec_idx_is_null( rec_idx ) ) {
     527             : 
     528             :     /* Validate rec_idx */
     529             : 
     530     1153035 :     if( FD_UNLIKELY( rec_idx>=rec_max ) ) FD_LOG_CRIT(( "memory corruption detected (bad idx)" ));
     531     1153035 :     if( FD_UNLIKELY( fd_funk_txn_idx( rec_map[ rec_idx ].txn_cidx )!=txn_idx ) )
     532           0 :       FD_LOG_CRIT(( "memory corruption detected (cycle or bad idx)" ));
     533     1153035 :     if( FD_UNLIKELY( rec_map[ rec_idx ].val_no_free ) )
     534           0 :       FD_LOG_CRIT(( "new record was speed loaded" ));
     535     1153035 :     rec_map[ rec_idx ].txn_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     536             : 
     537     1153035 :     ulong next_idx = rec_map[ rec_idx ].next_idx;
     538             : 
     539             :     /* See if (dst_xid,key) already exists */
     540             : 
     541     1153035 :     fd_funk_xid_key_pair_t dst_pair[1];
     542     1153035 :     fd_funk_xid_key_pair_init( dst_pair, dst_xid, fd_funk_rec_key( &rec_map[ rec_idx ] ) );
     543             : 
     544     1153035 :     fd_funk_rec_t * dst_rec = fd_funk_rec_map_query( rec_map, dst_pair, NULL );
     545             : 
     546     1153035 :     if( FD_UNLIKELY( rec_map[ rec_idx ].flags & FD_FUNK_REC_FLAG_ERASE ) ) { /* Erase a published key */
     547             : 
     548             :       /* Remove from partition */
     549      190332 :       fd_funk_part_set_intern( partvec, rec_map, &rec_map[rec_idx], FD_FUNK_PART_NULL );
     550             : 
     551      190332 :       if( FD_UNLIKELY( !dst_rec ) ) {
     552             : 
     553             :         /* Note that we only set the erase flag if there is was ancestor
     554             :            to this transaction with the key in it.  So if are merging
     555             :            into the last published transaction and we didn't find a
     556             :            record there, we have a memory corruption problem. */
     557             : 
     558         882 :         if( FD_UNLIKELY( fd_funk_txn_idx_is_null( dst_txn_idx ) ) ) {
     559           0 :           FD_LOG_CRIT(( "memory corruption detected (bad ancestor)" ));
     560           0 :         }
     561             : 
     562             :         /* Otherwise, txn is an erase of this record from one of
     563             :            dst's ancestors.  So we move the erase from (src_xid,key) and
     564             :            to (dst_xid,key).  We need to do this by a map remove / map
     565             :            insert to keep map queries working correctly.  Note that
     566             :            value metadata was flushed when erase was first set on
     567             :            (src_xid,key). */
     568             : 
     569         882 :         fd_funk_rec_map_remove( rec_map, fd_funk_rec_pair( &rec_map[ rec_idx ] ) );
     570             : 
     571         882 :         if( !fd_funk_txn_xid_eq_root( dst_xid ) ) {
     572         882 :           dst_rec = fd_funk_rec_map_insert( rec_map, dst_pair ); /* Guaranteed to succeed at this point due to above remove */
     573             : 
     574         882 :           ulong dst_rec_idx  = (ulong)(dst_rec - rec_map);
     575             : 
     576         882 :           ulong dst_prev_idx = *_dst_rec_tail_idx;
     577             : 
     578         882 :           dst_rec->prev_idx         = dst_prev_idx;
     579         882 :           dst_rec->next_idx         = FD_FUNK_REC_IDX_NULL;
     580         882 :           dst_rec->txn_cidx         = fd_funk_txn_cidx( dst_txn_idx );
     581         882 :           dst_rec->tag              = 0U;
     582             : 
     583         882 :           if( fd_funk_rec_idx_is_null( dst_prev_idx ) ) *_dst_rec_head_idx               = dst_rec_idx;
     584         798 :           else                                          rec_map[ dst_prev_idx ].next_idx = dst_rec_idx;
     585             : 
     586         882 :           *_dst_rec_tail_idx = dst_rec_idx;
     587             : 
     588         882 :           fd_funk_val_init( dst_rec );
     589         882 :           fd_funk_part_init( dst_rec );
     590         882 :           dst_rec->flags |= FD_FUNK_REC_FLAG_ERASE;
     591         882 :         }
     592             : 
     593      189450 :       } else {
     594             : 
     595             :         /* The erase in rec_idx erases this transaction.  Unmap
     596             :            (src_xid,key) (note that value was flushed when erase was
     597             :            first set), flush dst xid's value, remove dst it from the dst
     598             :            sequence and unmap (dst_xid,key) */
     599             : 
     600      189450 :         fd_funk_rec_map_remove( rec_map, fd_funk_rec_pair( &rec_map[ rec_idx ] ) );
     601             : 
     602      189450 :         fd_funk_val_flush( dst_rec, alloc, wksp );
     603      189450 :         fd_funk_part_set_intern( partvec, rec_map, dst_rec, FD_FUNK_PART_NULL );
     604             : 
     605      189450 :         if( fd_funk_txn_xid_eq_root( dst_xid ) ) {
     606      189396 :           ulong prev_idx = dst_rec->prev_idx;
     607      189396 :           ulong next_idx = dst_rec->next_idx;
     608             : 
     609      189396 :           if( FD_UNLIKELY( fd_funk_rec_idx_is_null( prev_idx ) ) ) *_dst_rec_head_idx           = next_idx;
     610      171684 :           else                                                     rec_map[ prev_idx ].next_idx = next_idx;
     611             : 
     612      189396 :           if( FD_UNLIKELY( fd_funk_rec_idx_is_null( next_idx ) ) ) *_dst_rec_tail_idx           = prev_idx;
     613      176484 :           else                                                     rec_map[ next_idx ].prev_idx = prev_idx;
     614             : 
     615      189396 :           fd_funk_rec_map_remove( rec_map, dst_pair );
     616             : 
     617      189396 :         } else {
     618             :           /* Leave a new erase record */
     619          54 :           fd_funk_val_init( dst_rec );
     620          54 :           fd_funk_part_init( dst_rec );
     621          54 :           dst_rec->flags |= FD_FUNK_REC_FLAG_ERASE;
     622          54 :         }
     623      189450 :       }
     624             : 
     625      962703 :     } else {
     626             : 
     627             :       /* At this point, we are either creating a new record or updating
     628             :          an existing one.  In either case, we are going to be keeping
     629             :          around the src's value for later use and for speed, we do this
     630             :          zero-copy / in-place.  So we stash record value in stack
     631             :          temporaries and unmap (xid,key).  Note this strictly frees 1
     632             :          record from the rec_map, guaranteeing at least 1 record free in
     633             :          the record map below.  Note that we can't just reuse rec_idx in
     634             :          the update case because that could break map queries. */
     635             : 
     636      962703 :       ulong val_sz    = (ulong)rec_map[ rec_idx ].val_sz;
     637      962703 :       ulong val_max   = (ulong)rec_map[ rec_idx ].val_max;
     638      962703 :       ulong val_gaddr = rec_map[ rec_idx ].val_gaddr;
     639      962703 :       int val_no_free = rec_map[ rec_idx ].val_no_free;
     640      962703 :       uint part       = rec_map[ rec_idx ].part;
     641             : 
     642      962703 :       fd_funk_part_set_intern( partvec, rec_map, &rec_map[ rec_idx ], FD_FUNK_PART_NULL );
     643      962703 :       fd_funk_rec_map_remove( rec_map, fd_funk_rec_pair( &rec_map[ rec_idx ] ) );
     644             : 
     645      962703 :       if( FD_UNLIKELY( !dst_rec ) ) { /* Create a published key */
     646             : 
     647      246834 :         dst_rec = fd_funk_rec_map_insert( rec_map, dst_pair ); /* Guaranteed to succeed at this point due to above remove */
     648             : 
     649      246834 :         ulong dst_rec_idx  = (ulong)(dst_rec - rec_map);
     650      246834 :         ulong dst_prev_idx = *_dst_rec_tail_idx;
     651             : 
     652      246834 :         dst_rec->prev_idx         = dst_prev_idx;
     653      246834 :         dst_rec->next_idx         = FD_FUNK_REC_IDX_NULL;
     654      246834 :         dst_rec->txn_cidx         = fd_funk_txn_cidx( dst_txn_idx );
     655      246834 :         dst_rec->tag              = 0U;
     656             : 
     657      246834 :         fd_funk_part_init( dst_rec );
     658             : 
     659      246834 :         if( fd_funk_rec_idx_is_null( dst_prev_idx ) ) *_dst_rec_head_idx               = dst_rec_idx;
     660      246342 :         else                                          rec_map[ dst_prev_idx ].next_idx = dst_rec_idx;
     661             : 
     662      246834 :         *_dst_rec_tail_idx = dst_rec_idx;
     663             : 
     664      715869 :       } else { /* Update a published key */
     665             : 
     666      715869 :         fd_funk_val_flush( dst_rec, alloc, wksp ); /* Free up any preexisting value resources */
     667             : 
     668      715869 :       }
     669             : 
     670             :       /* Unstash value metadata from stack temporaries into dst_rec */
     671             : 
     672      962703 :       dst_rec->val_sz    = (uint)val_sz;
     673      962703 :       dst_rec->val_max   = (uint)val_max;
     674      962703 :       dst_rec->val_gaddr = val_gaddr;
     675      962703 :       dst_rec->val_no_free = val_no_free;
     676      962703 :       dst_rec->flags    &= ~FD_FUNK_REC_FLAG_ERASE;
     677             : 
     678             :       /* Use the new partition */
     679             : 
     680      962703 :       fd_funk_part_set_intern( partvec, rec_map, dst_rec, part );
     681      962703 :     }
     682             : 
     683             :     /* Advance to the next record */
     684             : 
     685     1153035 :     rec_idx = next_idx;
     686     1153035 :   }
     687             : 
     688      775044 :   txn_map[ txn_idx ].rec_head_idx = FD_FUNK_REC_IDX_NULL;
     689      775044 :   txn_map[ txn_idx ].rec_tail_idx = FD_FUNK_REC_IDX_NULL;
     690      775044 : }
     691             : 
     692             : /* fd_funk_txn_publish_funk_child publishes a transaction that is known
     693             :    to be a child of funk.  Callers have already validated our input
     694             :    arguments.  Returns FD_FUNK_SUCCESS on success and an FD_FUNK_ERR_*
     695             :    code on failure.  (There are currently no failure cases but the
     696             :    plumbing is there if value handling requires it at some point.) */
     697             : 
     698             : static int
     699             : fd_funk_txn_publish_funk_child( fd_funk_t *     funk,
     700             :                                 fd_funk_txn_t * map,
     701             :                                 ulong           txn_max,
     702             :                                 ulong           tag,
     703      663564 :                                 ulong           txn_idx ) {
     704             : 
     705      663564 :   fd_funk_check_write( funk );
     706             : 
     707             :   /* Apply the updates in txn to the last published transactions */
     708             : 
     709      663564 :   fd_wksp_t * wksp = fd_funk_wksp( funk );
     710      663564 :   fd_funk_txn_update( &funk->rec_head_idx, &funk->rec_tail_idx, FD_FUNK_TXN_IDX_NULL, fd_funk_root( funk ),
     711      663564 :                       txn_idx, funk->rec_max, map, fd_funk_rec_map( funk, wksp ), fd_funk_get_partvec( funk, wksp ),
     712      663564 :                       fd_funk_alloc( funk, wksp ), wksp );
     713             : 
     714             :   /* Cancel all competing transaction histories */
     715             : 
     716      663564 :   ulong oldest_idx = fd_funk_txn_oldest_sibling( funk, map, txn_max, txn_idx );
     717      663564 :   fd_funk_txn_cancel_sibling_list( funk, map, txn_max, tag, oldest_idx, txn_idx );
     718             : 
     719             :   /* Make all the children children of funk */
     720             : 
     721      663564 :   ulong child_head_idx = fd_funk_txn_idx( map[ txn_idx ].child_head_cidx );
     722      663564 :   ulong child_tail_idx = fd_funk_txn_idx( map[ txn_idx ].child_tail_cidx );
     723             : 
     724      663564 :   ulong child_idx = child_head_idx;
     725     1080093 :   while( FD_UNLIKELY( !fd_funk_txn_idx_is_null( child_idx ) ) ) { /* opt for incr pub */
     726             : 
     727      416529 :     ASSERT_IN_PREP ( child_idx );
     728      416529 :     ASSERT_UNTAGGED( child_idx );
     729      416529 :     map[ child_idx ].tag = tag;
     730             : 
     731      416529 :     map[ child_idx ].parent_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     732             : 
     733      416529 :     child_idx = fd_funk_txn_idx( map[ child_idx ].sibling_next_cidx );
     734      416529 :   }
     735             : 
     736      663564 :   funk->child_head_cidx = fd_funk_txn_cidx( child_head_idx );
     737      663564 :   funk->child_tail_cidx = fd_funk_txn_cidx( child_tail_idx );
     738             : 
     739             :   /* Remove the mapping */
     740             : 
     741      663564 :   fd_funk_txn_xid_copy( funk->last_publish, fd_funk_txn_xid( &map[ txn_idx ] ) );
     742             : 
     743      663564 :   fd_funk_txn_map_remove( map, fd_funk_txn_xid( &map[ txn_idx ] ) );
     744             : 
     745      663564 :   return FD_FUNK_SUCCESS;
     746      663564 : }
     747             : 
     748             : ulong
     749             : fd_funk_txn_publish( fd_funk_t *     funk,
     750             :                      fd_funk_txn_t * txn,
     751     1548918 :                      int             verbose ) {
     752             : 
     753     1548918 :   if( FD_UNLIKELY( !funk ) ) {
     754      196464 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     755      196464 :     return 0UL;
     756      196464 :   }
     757             : 
     758     1352454 :   fd_wksp_t * wksp = fd_funk_wksp( funk );
     759             : 
     760     1352454 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, wksp );
     761             : 
     762     1352454 :   ulong txn_max = funk->txn_max;
     763             : 
     764     1352454 :   ulong txn_idx = (ulong)(txn - map);
     765             : 
     766     1352454 :   if( FD_UNLIKELY( (txn_idx>=txn_max) /* Out of map (incl NULL) */ | (txn!=(map+txn_idx)) /* Bad alignment */ ) ) {
     767      786372 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not a funk transaction" ));
     768      786372 :     return 0UL;
     769      786372 :   }
     770             : 
     771      566082 :   if( FD_UNLIKELY( !fd_funk_txn_map_query( map, fd_funk_txn_xid( txn ), NULL ) ) ) {
     772      188382 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "txn is not in preparation" ));
     773      188382 :     return 0UL;
     774      188382 :   }
     775             : 
     776      377700 :   ulong tag = funk->cycle_tag++;
     777             : 
     778      377700 :   ulong publish_stack_idx = FD_FUNK_TXN_IDX_NULL;
     779             : 
     780      663564 :   for(;;) {
     781      663564 :     map[ txn_idx ].tag = tag;
     782             : 
     783             :     /* At this point, txn_idx is a transaction that needs to be
     784             :        published and has been tagged.  If txn is a child of funk, we are
     785             :        ready to publish txn and everything on the publish stack. */
     786             : 
     787      663564 :     ulong parent_idx = fd_funk_txn_idx( map[ txn_idx ].parent_cidx );
     788      663564 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) break; /* opt for incr pub */
     789             : 
     790             :     /* txn_idx has a parent.  Validate and tag it.  Push txn to the
     791             :        publish stack and recurse into the parent. */
     792             : 
     793      285864 :     ASSERT_IN_PREP ( parent_idx );
     794      285864 :     ASSERT_UNTAGGED( parent_idx );
     795             : 
     796      285864 :     map[ txn_idx ].stack_cidx = fd_funk_txn_cidx( publish_stack_idx );
     797      285864 :     publish_stack_idx         = txn_idx;
     798             : 
     799      285864 :     txn_idx = parent_idx;
     800      285864 :   }
     801             : 
     802      377700 :   ulong publish_cnt = 0UL;
     803             : 
     804      663564 :   for(;;) {
     805             : 
     806             :     /* At this point, all the transactions we need to publish are
     807             :        tagged, txn is the next up publish funk and publish_stack has the
     808             :        transactions to follow in order by pop.  We use a new tag for
     809             :        each publish as txn and its siblings we potentially visited in a
     810             :        previous iteration of this loop. */
     811             : 
     812      663564 :     if( FD_UNLIKELY( fd_funk_txn_publish_funk_child( funk, map, txn_max, funk->cycle_tag++, txn_idx ) ) ) break;
     813      663564 :     publish_cnt++;
     814             : 
     815      663564 :     txn_idx = publish_stack_idx;
     816      663564 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( txn_idx ) ) ) break; /* opt for incr pub */
     817      285864 :     publish_stack_idx = fd_funk_txn_idx( map[ txn_idx ].stack_cidx );
     818      285864 :   }
     819             : 
     820      377700 :   return publish_cnt;
     821      377700 : }
     822             : 
     823             : int
     824             : fd_funk_txn_publish_into_parent( fd_funk_t *     funk,
     825             :                                  fd_funk_txn_t * txn,
     826      109086 :                                  int             verbose ) {
     827      109086 :   if( FD_UNLIKELY( !funk ) ) {
     828           0 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     829           0 :     return FD_FUNK_ERR_INVAL;
     830           0 :   }
     831      109086 :   fd_funk_check_write( funk );
     832             : 
     833      109086 :   fd_wksp_t * wksp = fd_funk_wksp( funk );
     834             : 
     835      109086 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, wksp );
     836             : 
     837      109086 :   ulong txn_idx = (ulong)(txn - map);
     838             : 
     839      109086 :   ulong oldest_idx = fd_funk_txn_oldest_sibling( funk, map, funk->txn_max, txn_idx );
     840      109086 :   fd_funk_txn_cancel_sibling_list( funk, map, funk->txn_max, funk->cycle_tag++, oldest_idx, txn_idx );
     841             : 
     842      109086 :   ulong parent_idx = fd_funk_txn_idx( txn->parent_cidx );
     843      109086 :   if( fd_funk_txn_idx_is_null( parent_idx ) ) {
     844             :     /* Publish to root */
     845          66 :     if( fd_funk_txn_idx( funk->child_head_cidx ) != txn_idx || fd_funk_txn_idx( funk->child_tail_cidx ) != txn_idx )
     846           0 :       FD_LOG_CRIT(( "memory corruption detected (cycle or bad idx)" ));
     847          66 :     fd_funk_txn_update( &funk->rec_head_idx, &funk->rec_tail_idx, FD_FUNK_TXN_IDX_NULL, fd_funk_root( funk ),
     848          66 :                         txn_idx, funk->rec_max, map, fd_funk_rec_map( funk, wksp ), fd_funk_get_partvec( funk, wksp ),
     849          66 :                         fd_funk_alloc( funk, wksp ), wksp );
     850             :     /* Inherit the children */
     851          66 :     funk->child_head_cidx = txn->child_head_cidx;
     852          66 :     funk->child_tail_cidx = txn->child_tail_cidx;
     853      109020 :   } else {
     854      109020 :     fd_funk_txn_t * parent_txn = map + parent_idx;
     855      109020 :     if( fd_funk_txn_idx( parent_txn->child_head_cidx ) != txn_idx || fd_funk_txn_idx( parent_txn->child_tail_cidx ) != txn_idx )
     856           0 :       FD_LOG_CRIT(( "memory corruption detected (cycle or bad idx)" ));
     857      109020 :     fd_funk_txn_update( &parent_txn->rec_head_idx, &parent_txn->rec_tail_idx, parent_idx, &parent_txn->xid,
     858      109020 :                         txn_idx, funk->rec_max, map, fd_funk_rec_map( funk, wksp ), fd_funk_get_partvec( funk, wksp ),
     859      109020 :                         fd_funk_alloc( funk, wksp ), wksp );
     860             :     /* Inherit the children */
     861      109020 :     parent_txn->child_head_cidx = txn->child_head_cidx;
     862      109020 :     parent_txn->child_tail_cidx = txn->child_tail_cidx;
     863      109020 :   }
     864             : 
     865             :   /* Adjust the parent pointers of the children to point to their grandparent */
     866      109086 :   ulong child_idx = fd_funk_txn_idx( txn->child_head_cidx );
     867      109263 :   while( FD_UNLIKELY( !fd_funk_txn_idx_is_null( child_idx ) ) ) {
     868         177 :     map[ child_idx ].parent_cidx = fd_funk_txn_cidx( parent_idx );
     869         177 :     child_idx = fd_funk_txn_idx( map[ child_idx ].sibling_next_cidx );
     870         177 :   }
     871             : 
     872      109086 :   fd_funk_txn_map_remove( map, fd_funk_txn_xid( txn ) );
     873             : 
     874      109086 :   return FD_FUNK_SUCCESS;
     875      109086 : }
     876             : 
     877             : int
     878             : fd_funk_txn_merge_all_children( fd_funk_t *     funk,
     879             :                                 fd_funk_txn_t * parent_txn,
     880        2301 :                                 int             verbose ) {
     881        2301 :   if( FD_UNLIKELY( !funk ) ) {
     882           0 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     883           0 :     return FD_FUNK_ERR_INVAL;
     884           0 :   }
     885        2301 :   fd_funk_check_write( funk );
     886             : 
     887        2301 :   fd_wksp_t * wksp = fd_funk_wksp( funk );
     888             : 
     889        2301 :   fd_funk_txn_t * map = fd_funk_txn_map( funk, wksp );
     890             : 
     891        2301 :   ulong txn_max = fd_funk_txn_map_key_max( map );
     892             : 
     893        2301 :   ulong parent_idx = (ulong)(parent_txn - map);
     894             : 
     895        2301 :   ASSERT_IN_PREP( parent_idx );
     896             : 
     897        2301 :   ulong child_head_idx = fd_funk_txn_idx( map[ parent_idx ].child_head_cidx );
     898             : 
     899        2301 :   ulong child_idx = child_head_idx;
     900        4695 :   while( FD_UNLIKELY( !fd_funk_txn_idx_is_null( child_idx ) ) ) { /* opt for incr pub */
     901             :     /* Merge records from child into parent */
     902             : 
     903        2394 :     fd_funk_txn_t * txn = &map[ child_idx ];
     904        2394 :     if( FD_UNLIKELY( !fd_funk_txn_idx_is_null( fd_funk_txn_idx( txn->child_head_cidx ) ) ) ) {
     905           0 :       FD_LOG_WARNING(("cannot call fd_funk_txn_merge_all_children if parent_txn has grandchildren"));
     906           0 :       return FD_FUNK_ERR_TXN;
     907           0 :     }
     908             : 
     909        2394 :     fd_funk_txn_update( &parent_txn->rec_head_idx, &parent_txn->rec_tail_idx, parent_idx, &parent_txn->xid,
     910        2394 :                         child_idx, funk->rec_max, map, fd_funk_rec_map( funk, wksp ), fd_funk_get_partvec( funk, wksp ),
     911        2394 :                         fd_funk_alloc( funk, wksp ), wksp );
     912             : 
     913        2394 :     child_idx = fd_funk_txn_idx( txn->sibling_next_cidx );
     914        2394 :     fd_funk_txn_map_remove( map, fd_funk_txn_xid( txn ) );
     915        2394 :   }
     916             : 
     917        2301 :   parent_txn->child_head_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     918        2301 :   parent_txn->child_tail_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     919             : 
     920        2301 :   return FD_FUNK_SUCCESS;
     921        2301 : }
     922             : 
     923             : /* Return the first record in a transaction. Returns NULL if the
     924             :    transaction has no records yet. */
     925             : 
     926             : FD_FN_PURE fd_funk_rec_t const *
     927             : fd_funk_txn_first_rec( fd_funk_t *           funk,
     928      217374 :                        fd_funk_txn_t const * txn ) {
     929      217374 :   ulong rec_idx;
     930      217374 :   if( FD_UNLIKELY( NULL == txn ))
     931        6300 :     rec_idx = funk->rec_head_idx;
     932      211074 :   else
     933      211074 :     rec_idx = txn->rec_head_idx;
     934      217374 :   if( fd_funk_rec_idx_is_null( rec_idx ) ) return NULL;
     935      169761 :   fd_funk_rec_t const * rec_map = fd_funk_rec_map( funk, fd_funk_wksp( funk ) );
     936      169761 :   return rec_map + rec_idx;
     937      217374 : }
     938             : 
     939             : /* Return the next record in a transaction. Returns NULL if the
     940             :    transaction has no more records. */
     941             : 
     942             : FD_FN_PURE fd_funk_rec_t const *
     943             : fd_funk_txn_next_rec( fd_funk_t *           funk,
     944     2070993 :                       fd_funk_rec_t const * rec ) {
     945     2070993 :   ulong rec_idx = rec->next_idx;
     946     2070993 :   if( fd_funk_rec_idx_is_null( rec_idx ) ) return NULL;
     947     1901232 :   fd_funk_rec_t const * rec_map = fd_funk_rec_map( funk, fd_funk_wksp( funk ) );
     948     1901232 :   return rec_map + rec_idx;
     949     2070993 : }
     950             : 
     951             : fd_funk_txn_xid_t
     952    20682798 : fd_funk_generate_xid(void) {
     953    20682798 :   fd_funk_txn_xid_t xid;
     954    20682798 :   static FD_TL ulong seq = 0;
     955    20682798 :   xid.ul[0] =
     956    20682798 :     (fd_log_cpu_id() + 1U)*3138831853UL +
     957    20682798 :     (fd_log_thread_id() + 1U)*9180195821UL +
     958    20682798 :     (++seq)*6208101967UL;
     959    20682798 :   xid.ul[1] = ((ulong)fd_tickcount())*2810745731UL;
     960    20682798 :   return xid;
     961    20682798 : }
     962             : 
     963             : int
     964    22026408 : fd_funk_txn_verify( fd_funk_t * funk ) {
     965    22026408 :   fd_wksp_t *     wksp    = fd_funk_wksp( funk );          /* Previously verified */
     966    22026408 :   fd_funk_txn_t * map     = fd_funk_txn_map( funk, wksp ); /* Previously verified */
     967    22026408 :   ulong           txn_max = funk->txn_max;                 /* Previously verified */
     968             : 
     969    22026408 :   ulong funk_child_head_idx = fd_funk_txn_idx( funk->child_head_cidx ); /* Previously verified */
     970    22026408 :   ulong funk_child_tail_idx = fd_funk_txn_idx( funk->child_tail_cidx ); /* Previously verified */
     971             : 
     972    22026408 :   fd_funk_txn_xid_t const * last_publish = funk->last_publish; /* Previously verified */
     973             : 
     974   856024092 : # define TEST(c) do {                                                                           \
     975   856024092 :     if( FD_UNLIKELY( !(c) ) ) { FD_LOG_WARNING(( "FAIL: %s", #c )); return FD_FUNK_ERR_INVAL; } \
     976   856024092 :   } while(0)
     977             : 
     978    22026408 : # define IS_VALID( idx ) ( (idx==FD_FUNK_TXN_IDX_NULL) || \
     979    22026408 :                            ((idx<txn_max) && (!fd_funk_txn_xid_eq( fd_funk_txn_xid( &map[idx] ), last_publish ))) )
     980             : 
     981    22026408 :   TEST( !fd_funk_txn_map_verify( map ) );
     982             : 
     983    22026408 :   ulong free_cnt = txn_max - fd_funk_txn_map_key_cnt( map );
     984             : 
     985             :   /* Tag all transactions as not visited yet */
     986             : 
     987   729048744 :   for( ulong txn_idx=0UL; txn_idx<txn_max; txn_idx++ ) map[ txn_idx ].tag = 0UL;
     988             : 
     989             :   /* Visit all transactions in preparation, traversing from oldest to
     990             :      youngest. */
     991             : 
     992    22026408 :   ulong prep_cnt = 0UL;
     993    22026408 :   do {
     994             : 
     995             :     /* Push all children of funk to the stack */
     996             : 
     997    22026408 :     ulong stack_idx = FD_FUNK_TXN_IDX_NULL;
     998    22026408 :     ulong child_idx = funk_child_head_idx;
     999    52708581 :     while( !fd_funk_txn_idx_is_null( child_idx ) ) {
    1000             : 
    1001             :       /* Make sure valid idx, not tagged (detects cycles) and child
    1002             :          knows it is a child of funk.  Then tag as visited / in-prep,
    1003             :          push to stack and update prep_cnt */
    1004             : 
    1005    30682173 :       TEST( IS_VALID( child_idx ) );
    1006    30682173 :       TEST( !map[ child_idx ].tag );
    1007    30682173 :       TEST( fd_funk_txn_idx_is_null( fd_funk_txn_idx( map[ child_idx ].parent_cidx ) ) );
    1008    30682173 :       map[ child_idx ].tag        = 1UL;
    1009    30682173 :       map[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
    1010    30682173 :       stack_idx                   = child_idx;
    1011    30682173 :       prep_cnt++;
    1012             : 
    1013    30682173 :       ulong next_idx = fd_funk_txn_idx( map[ child_idx ].sibling_next_cidx );
    1014    30682173 :       if( !fd_funk_txn_idx_is_null( next_idx ) ) TEST( fd_funk_txn_idx( map[ next_idx ].sibling_prev_cidx )==child_idx );
    1015    30682173 :       child_idx = next_idx;
    1016    30682173 :     }
    1017             : 
    1018   134597451 :     while( !fd_funk_txn_idx_is_null( stack_idx ) ) {
    1019             : 
    1020             :       /* Pop the next transaction to traverse */
    1021             : 
    1022   112571043 :       ulong txn_idx = stack_idx;
    1023   112571043 :       stack_idx = fd_funk_txn_idx( map[ txn_idx ].stack_cidx );
    1024             : 
    1025             :       /* Push all children of txn to the stack */
    1026             : 
    1027   112571043 :       ulong child_idx = fd_funk_txn_idx( map[ txn_idx ].child_head_cidx );
    1028   194459913 :       while( !fd_funk_txn_idx_is_null( child_idx ) ) {
    1029             : 
    1030             :         /* Make sure valid idx, not tagged (detects cycles) and child
    1031             :            knows it is a child of txn_idx.  Then tag as visited /
    1032             :            in-prep, push to stack and update prep_cnt */
    1033             : 
    1034    81888870 :         TEST( IS_VALID( child_idx ) );
    1035    81888870 :         TEST( !map[ child_idx ].tag );
    1036    81888870 :         TEST( fd_funk_txn_idx( map[ child_idx ].parent_cidx )==txn_idx );
    1037    81888870 :         map[ child_idx ].tag        = 1UL;
    1038    81888870 :         map[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
    1039    81888870 :         stack_idx                   = child_idx;
    1040    81888870 :         prep_cnt++;
    1041             : 
    1042    81888870 :         ulong next_idx = fd_funk_txn_idx( map[ child_idx ].sibling_next_cidx );
    1043    81888870 :         if( !fd_funk_txn_idx_is_null( next_idx ) ) TEST( fd_funk_txn_idx( map[ next_idx ].sibling_prev_cidx )==child_idx );
    1044    81888870 :         child_idx = next_idx;
    1045    81888870 :       }
    1046   112571043 :     }
    1047             : 
    1048    22026408 :   } while(0);
    1049             : 
    1050    22026408 :   TEST( (free_cnt+prep_cnt)==txn_max );
    1051             : 
    1052             :   /* Do it again with a youngest to oldest traversal to test reverse
    1053             :      link integrity */
    1054             : 
    1055    22026408 :   prep_cnt = 0UL;
    1056    22026408 :   do {
    1057             : 
    1058             :     /* Push all children of funk to the stack */
    1059             : 
    1060    22026408 :     ulong stack_idx = FD_FUNK_TXN_IDX_NULL;
    1061    22026408 :     ulong child_idx = funk_child_tail_idx;
    1062    52708581 :     while( !fd_funk_txn_idx_is_null( child_idx ) ) {
    1063             : 
    1064             :       /* Make sure valid idx, tagged as visited above (detects cycles)
    1065             :          and child knows it is a child of funk.  Then tag as visited /
    1066             :          in-prep, push to stack and update prep_cnt */
    1067             : 
    1068    30682173 :       TEST( IS_VALID( child_idx ) );
    1069    30682173 :       TEST( map[ child_idx ].tag==1UL );
    1070    30682173 :       TEST( fd_funk_txn_idx_is_null( fd_funk_txn_idx( map[ child_idx ].parent_cidx ) ) );
    1071    30682173 :       map[ child_idx ].tag        = 2UL;
    1072    30682173 :       map[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
    1073    30682173 :       stack_idx                   = child_idx;
    1074    30682173 :       prep_cnt++;
    1075             : 
    1076    30682173 :       ulong prev_idx = fd_funk_txn_idx( map[ child_idx ].sibling_prev_cidx );
    1077    30682173 :       if( !fd_funk_txn_idx_is_null( prev_idx ) ) TEST( fd_funk_txn_idx( map[ prev_idx ].sibling_next_cidx )==child_idx );
    1078    30682173 :       child_idx = prev_idx;
    1079    30682173 :     }
    1080             : 
    1081   134597451 :     while( !fd_funk_txn_idx_is_null( stack_idx ) ) {
    1082             : 
    1083             :       /* Pop the next transaction to traverse */
    1084             : 
    1085   112571043 :       ulong txn_idx = stack_idx;
    1086   112571043 :       stack_idx = fd_funk_txn_idx( map[ txn_idx ].stack_cidx );
    1087             : 
    1088             :       /* Push all children of txn to the stack */
    1089             : 
    1090   112571043 :       ulong child_idx = fd_funk_txn_idx( map[ txn_idx ].child_tail_cidx );
    1091   194459913 :       while( !fd_funk_txn_idx_is_null( child_idx ) ) {
    1092             : 
    1093             :         /* Make sure valid idx, tagged as visited above (detects cycles)
    1094             :            and child knows it is a child of txn_idx.  Then, tag as
    1095             :            visited / in-prep, push to stack and update prep_cnt */
    1096             : 
    1097    81888870 :         TEST( IS_VALID( child_idx ) );
    1098    81888870 :         TEST( map[ child_idx ].tag==1UL );
    1099    81888870 :         TEST( fd_funk_txn_idx( map[ child_idx ].parent_cidx )==txn_idx );
    1100    81888870 :         map[ child_idx ].tag        = 2UL;
    1101    81888870 :         map[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
    1102    81888870 :         stack_idx                   = child_idx;
    1103    81888870 :         prep_cnt++;
    1104             : 
    1105    81888870 :         ulong prev_idx = fd_funk_txn_idx( map[ child_idx ].sibling_prev_cidx );
    1106    81888870 :         if( !fd_funk_txn_idx_is_null( prev_idx ) ) TEST( fd_funk_txn_idx( map[ prev_idx ].sibling_next_cidx )==child_idx );
    1107    81888870 :         child_idx = prev_idx;
    1108    81888870 :       }
    1109   112571043 :     }
    1110    22026408 :   } while(0);
    1111             : 
    1112    22026408 :   TEST( (free_cnt+prep_cnt)==txn_max );
    1113             : 
    1114    22026408 :   TEST( fd_funk_txn_cnt( map )==prep_cnt );
    1115             : 
    1116    22026408 : # undef IS_VALID
    1117    22026408 : # undef TEST
    1118             : 
    1119    22026408 :   return FD_FUNK_SUCCESS;
    1120    22026408 : }
    1121             : 
    1122             : #undef ASSERT_UNTAGGED
    1123             : #undef ASSERT_IN_PREP
    1124             : #undef ASSERT_IN_MAP

Generated by: LCOV version 1.14