LCOV - code coverage report
Current view: top level - funk - fd_funk_txn.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 382 482 79.3 %
Date: 2025-07-01 05:00:49 Functions: 23 30 76.7 %

          Line data    Source code
       1             : #include "fd_funk.h"
       2             : 
       3             : /* Provide the actual transaction map implementation */
       4             : 
       5             : #define POOL_NAME          fd_funk_txn_pool
       6     2111415 : #define POOL_ELE_T         fd_funk_txn_t
       7             : #define POOL_IDX_T         uint
       8     5260749 : #define POOL_NEXT          map_next
       9             : #define POOL_IMPL_STYLE    2
      10             : #include "../util/tmpl/fd_pool_para.c"
      11             : 
      12             : #define MAP_NAME              fd_funk_txn_map
      13   387426621 : #define MAP_ELE_T             fd_funk_txn_t
      14           0 : #define MAP_KEY_T             fd_funk_txn_xid_t
      15     1055640 : #define MAP_KEY               xid
      16             : #define MAP_KEY_EQ(k0,k1)     fd_funk_txn_xid_eq((k0),(k1))
      17             : #define MAP_KEY_HASH(k0,seed) fd_funk_txn_xid_hash((k0),(seed))
      18   164936303 : #define MAP_NEXT              map_next
      19             : #define MAP_HASH              map_hash
      20          33 : #define MAP_MAGIC             (0xf173da2ce7172db0UL) /* Firedancer txn db version 0 */
      21             : #define MAP_IMPL_STYLE        2
      22             : #include "../util/tmpl/fd_map_chain_para.c"
      23             : 
      24             : /* TODO: remove this lock */
      25             : #include "../flamenco/fd_rwlock.h"
      26             : static fd_rwlock_t funk_txn_lock[ 1 ] = {0};
      27             : 
      28             : void
      29           0 : fd_funk_txn_start_read( fd_funk_t * funk FD_PARAM_UNUSED ) {
      30           0 :   fd_rwlock_read( funk_txn_lock );
      31           0 : }
      32             : 
      33             : void
      34           0 : fd_funk_txn_end_read( fd_funk_t * funk FD_PARAM_UNUSED ) {
      35           0 :   fd_rwlock_unread( funk_txn_lock );
      36           0 : }
      37             : 
      38             : void
      39          15 : fd_funk_txn_start_write( fd_funk_t * funk FD_PARAM_UNUSED ) {
      40          15 :   fd_rwlock_write( funk_txn_lock );
      41          15 : }
      42             : 
      43             : void
      44          15 : fd_funk_txn_end_write( fd_funk_t * funk FD_PARAM_UNUSED ) {
      45          15 :   fd_rwlock_unwrite( funk_txn_lock );
      46          15 : }
      47             : 
      48             : fd_funk_txn_t *
      49             : fd_funk_txn_prepare( fd_funk_t *               funk,
      50             :                      fd_funk_txn_t *           parent,
      51             :                      fd_funk_txn_xid_t const * xid,
      52     1317777 :                      int                       verbose ) {
      53             : 
      54             : #ifdef FD_FUNK_HANDHOLDING
      55             :   if( FD_UNLIKELY( !funk ) ) {
      56             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
      57             :     return NULL;
      58             :   }
      59             :   if( FD_UNLIKELY( !xid ) ) {
      60             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL xid" ));
      61             :     return NULL;
      62             :   }
      63             :   if( FD_UNLIKELY( parent && !fd_funk_txn_valid( funk, parent ) ) ) {
      64             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "bad txn" ));
      65             :     return NULL;
      66             :   }
      67             :   if( FD_UNLIKELY( fd_funk_txn_xid_eq_root( xid ) ) ) {
      68             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "xid is the root" ));
      69             :     return NULL;
      70             :   }
      71             :   if( FD_UNLIKELY( fd_funk_txn_xid_eq( xid, funk->shmem->last_publish ) ) ) {
      72             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "xid is the last published" ));
      73             :     return NULL;
      74             :   }
      75             : #endif
      76             : 
      77     1317777 :   fd_funk_txn_map_query_t query[1];
      78     1317777 :   if( FD_UNLIKELY( fd_funk_txn_map_query_try( funk->txn_map, xid, NULL, query, 0 ) != FD_MAP_ERR_KEY ) ) {
      79      262125 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "xid in use" ));
      80      262125 :     return NULL;
      81      262125 :   }
      82             : 
      83     1055652 :   ulong  parent_idx;
      84     1055652 :   uint * _child_head_cidx;
      85     1055652 :   uint * _child_tail_cidx;
      86             : 
      87     1055652 :   if( FD_LIKELY( !parent ) ) { /* opt for incr pub */
      88             : 
      89      445433 :     parent_idx = FD_FUNK_TXN_IDX_NULL;
      90             : 
      91      445433 :     _child_head_cidx = &funk->shmem->child_head_cidx;
      92      445433 :     _child_tail_cidx = &funk->shmem->child_tail_cidx;
      93             : 
      94      610219 :   } else {
      95             : 
      96      610219 :     parent_idx = (ulong)(parent - funk->txn_pool->ele);
      97             : 
      98      610219 :     _child_head_cidx = &parent->child_head_cidx;
      99      610219 :     _child_tail_cidx = &parent->child_tail_cidx;
     100             : 
     101      610219 :   }
     102             : 
     103             :   /* Get a new transaction from the map */
     104             : 
     105     1055652 :   fd_funk_txn_t * txn = fd_funk_txn_pool_acquire( funk->txn_pool, NULL, 1, NULL );
     106     1055652 :   if( txn == NULL ) {
     107          12 :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "transaction pool is exhuasted" ));
     108          12 :     return NULL;
     109          12 :   }
     110     1055640 :   fd_funk_txn_xid_copy( &txn->xid, xid );
     111     1055640 :   ulong txn_idx = (ulong)(txn - funk->txn_pool->ele);
     112             : 
     113             :   /* Join the family */
     114             : 
     115     1055640 :   ulong sibling_prev_idx = fd_funk_txn_idx( *_child_tail_cidx );
     116             : 
     117     1055640 :   int first_born = fd_funk_txn_idx_is_null( sibling_prev_idx );
     118             : 
     119     1055640 :   txn->parent_cidx       = fd_funk_txn_cidx( parent_idx           );
     120     1055640 :   txn->child_head_cidx   = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     121     1055640 :   txn->child_tail_cidx   = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     122     1055640 :   txn->sibling_prev_cidx = fd_funk_txn_cidx( sibling_prev_idx     );
     123     1055640 :   txn->sibling_next_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     124     1055640 :   txn->stack_cidx        = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     125     1055640 :   txn->tag               = 0UL;
     126             : 
     127     1055640 :   txn->lock = 0;
     128     1055640 :   txn->rec_head_idx = FD_FUNK_REC_IDX_NULL;
     129     1055640 :   txn->rec_tail_idx = FD_FUNK_REC_IDX_NULL;
     130             : 
     131             :   /* TODO: consider branchless impl */
     132     1055640 :   if( FD_LIKELY( first_born ) ) *_child_head_cidx                = fd_funk_txn_cidx( txn_idx ); /* opt for non-compete */
     133      537280 :   else funk->txn_pool->ele[ sibling_prev_idx ].sibling_next_cidx = fd_funk_txn_cidx( txn_idx );
     134             : 
     135     1055640 :   *_child_tail_cidx = fd_funk_txn_cidx( txn_idx );
     136             : 
     137     1055640 :   fd_funk_txn_map_insert( funk->txn_map, txn, FD_MAP_FLAG_BLOCKING );
     138             : 
     139     1055640 :   return txn;
     140     1055652 : }
     141             : 
     142             : /* fd_funk_txn_cancel_childless cancels a transaction that is known
     143             :    to be childless.  Callers have already validated our input arguments.
     144             :    Assumes that cancelling in the app can't fail but that could be
     145             :    straightforward to support by giving this an error and plumbing
     146             :    through to abort the overall cancel operation when it hits a error. */
     147             : 
     148             : static void
     149             : fd_funk_txn_cancel_childless( fd_funk_t * funk,
     150      837980 :                               ulong       txn_idx ) {
     151             : 
     152             :   /* Remove all records used by this transaction.  Note that we don't
     153             :      need to bother doing all the individual removal operations as we
     154             :      are removing the whole list.  We do reset the record transaction
     155             :      idx with NULL though we can detect cycles as soon as possible
     156             :      and abort. */
     157             : 
     158      837980 :   fd_wksp_t *          wksp     = funk->wksp;
     159      837980 :   fd_alloc_t *         alloc    = funk->alloc;
     160      837980 :   fd_funk_rec_map_t *  rec_map  = funk->rec_map;
     161      837980 :   fd_funk_rec_pool_t * rec_pool = funk->rec_pool;
     162      837980 :   ulong                rec_max  = fd_funk_rec_pool_ele_max( rec_pool );
     163      837980 :   fd_funk_txn_map_t *  txn_map  = funk->txn_map;
     164      837980 :   fd_funk_txn_pool_t * txn_pool = funk->txn_pool;
     165             : 
     166      837980 :   fd_funk_txn_t * txn = &txn_pool->ele[ txn_idx ];
     167      837980 :   uint rec_idx = txn->rec_head_idx;
     168    21420329 :   while( !fd_funk_rec_idx_is_null( rec_idx ) ) {
     169             : 
     170    20582349 :     if( FD_UNLIKELY( rec_idx>=rec_max ) ) FD_LOG_CRIT(( "memory corruption detected (bad idx)" ));
     171    20582349 :     if( FD_UNLIKELY( fd_funk_txn_idx( rec_pool->ele[ rec_idx ].txn_cidx )!=txn_idx ) )
     172           0 :       FD_LOG_CRIT(( "memory corruption detected (cycle or bad idx)" ));
     173             : 
     174    20582349 :     fd_funk_rec_t * rec = &rec_pool->ele[ rec_idx ];
     175    20582349 :     uint next_idx = rec->next_idx;
     176    20582349 :     rec->txn_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     177             : 
     178    20582349 :     for(;;) {
     179    20582349 :       fd_funk_rec_map_query_t rec_query[1];
     180    20582349 :       int err = fd_funk_rec_map_remove( rec_map, fd_funk_rec_pair( rec ), NULL, rec_query, FD_MAP_FLAG_BLOCKING );
     181    20582349 :       if( FD_UNLIKELY( err == FD_MAP_ERR_AGAIN ) ) continue;
     182    20582349 :       if( err == FD_MAP_ERR_KEY ) break;
     183    20582349 :       if( FD_UNLIKELY( err != FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "map corruption" ));
     184    20582349 :       if( rec != fd_funk_rec_map_query_ele( rec_query ) ) break;
     185    20582349 :       fd_funk_val_flush( rec, alloc, wksp );
     186    20582349 :       fd_funk_rec_pool_release( rec_pool, rec, 1 );
     187    20582349 :       break;
     188    20582349 :     }
     189             : 
     190    20582349 :     rec_idx = next_idx;
     191    20582349 :   }
     192             : 
     193             :   /* Leave the family */
     194             : 
     195      837980 :   ulong sibling_prev_idx = fd_funk_txn_idx( txn->sibling_prev_cidx );
     196      837980 :   ulong sibling_next_idx = fd_funk_txn_idx( txn->sibling_next_cidx );
     197             : 
     198             :   /* TODO: Consider branchless impl */
     199             : 
     200      837980 :   if( FD_LIKELY( fd_funk_txn_idx_is_null( sibling_prev_idx ) ) ) { /* opt for non-compete */
     201      407293 :     ulong parent_idx = fd_funk_txn_idx( txn_pool->ele[ txn_idx ].parent_cidx );
     202      407293 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) { /* No older sib and is a funk child, opt for incr pub */
     203      136757 :       funk->shmem->child_head_cidx = fd_funk_txn_cidx( sibling_next_idx );
     204      270536 :     } else { /* No older sib and has parent */
     205      270536 :       txn_pool->ele[ parent_idx ].child_head_cidx = fd_funk_txn_cidx( sibling_next_idx );
     206      270536 :     }
     207      430687 :   } else { /* Has older sib */
     208      430687 :     txn_pool->ele[ sibling_prev_idx ].sibling_next_cidx = fd_funk_txn_cidx( sibling_next_idx );
     209      430687 :   }
     210             : 
     211      837980 :   if( FD_LIKELY( fd_funk_txn_idx_is_null( sibling_next_idx ) ) ) { /* opt for non-compete */
     212      660528 :     ulong parent_idx = fd_funk_txn_idx( txn_pool->ele[ txn_idx ].parent_cidx );
     213      660528 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) { /* No younger sib and is a funk child, opt for incr pub */
     214      262877 :       funk->shmem->child_tail_cidx = fd_funk_txn_cidx( sibling_prev_idx );
     215      397651 :     } else { /* No younger sib and has parent */
     216      397651 :       txn_pool->ele[ parent_idx ].child_tail_cidx = fd_funk_txn_cidx( sibling_prev_idx );
     217      397651 :     }
     218      660528 :   } else { /* Has younger sib */
     219      177452 :     txn_pool->ele[ sibling_next_idx ].sibling_prev_cidx = fd_funk_txn_cidx( sibling_prev_idx );
     220      177452 :   }
     221             : 
     222      837980 :   fd_funk_txn_map_query_t query[1];
     223      837980 :   if( fd_funk_txn_map_remove( txn_map, fd_funk_txn_xid( txn ), NULL, query, FD_MAP_FLAG_BLOCKING ) == FD_MAP_SUCCESS ) {
     224      837980 :     fd_funk_txn_pool_release( txn_pool, txn, 1 );
     225      837980 :   }
     226      837980 : }
     227             : 
     228             : /* fd_funk_txn_cancel_family cancels a transaction and all its
     229             :    descendants in a tree-depth-first-ordered sense from youngest to
     230             :    oldest.  Callers have already validated our input arguments.  Returns
     231             :    the number of transactions canceled. */
     232             : 
     233             : static ulong
     234             : fd_funk_txn_cancel_family( fd_funk_t *  funk,
     235             :                            ulong        tag,
     236      475243 :                            ulong        txn_idx ) {
     237      475243 :   ulong cancel_cnt = 0UL;
     238             : 
     239      475243 :   ulong parent_stack_idx = FD_FUNK_TXN_IDX_NULL;
     240             : 
     241     1200717 :   for(;;) {
     242             : 
     243     1200717 :     fd_funk_txn_t * txn = &funk->txn_pool->ele[ txn_idx ];
     244     1200717 :     txn->tag = tag;
     245             : 
     246     1200717 :     ulong youngest_idx = fd_funk_txn_idx( txn->child_tail_cidx );
     247     1200717 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( youngest_idx ) ) ) { /* txn is is childless, opt for incr pub */
     248             : 
     249      837980 :       fd_funk_txn_cancel_childless( funk, txn_idx ); /* If this can fail, return cancel_cnt here on fail */
     250      837980 :       cancel_cnt++;
     251             : 
     252      837980 :       txn_idx = parent_stack_idx;                                  /* Pop the parent stack */
     253      837980 :       if( FD_LIKELY( fd_funk_txn_idx_is_null( txn_idx ) ) ) break; /* If stack is empty, we are done, opt for incr pub */
     254      362737 :       parent_stack_idx = fd_funk_txn_idx( funk->txn_pool->ele[ txn_idx ].stack_cidx );
     255      362737 :       continue;
     256      837980 :     }
     257             : 
     258             :     /* txn has at least one child and the youngest is youngest_idx.  Tag
     259             :        the youngest child, push txn onto the parent stack and recurse
     260             :        into the youngest child. */
     261             : 
     262      362737 :     txn->stack_cidx = fd_funk_txn_cidx( parent_stack_idx );
     263      362737 :     parent_stack_idx = txn_idx;
     264             : 
     265      362737 :     txn_idx = youngest_idx;
     266      362737 :   }
     267             : 
     268      475243 :   return cancel_cnt;
     269      475243 : }
     270             : 
     271             : ulong
     272             : fd_funk_txn_cancel( fd_funk_t *     funk,
     273             :                     fd_funk_txn_t * txn,
     274      147150 :                     int             verbose ) {
     275             : 
     276             : #ifdef FD_FUNK_HANDHOLDING
     277             :   if( FD_UNLIKELY( !funk ) ) {
     278             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     279             :     return 0UL;
     280             :   }
     281             :   if( FD_UNLIKELY( !fd_funk_txn_valid( funk, txn ) ) ) {
     282             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "bad txn" ));
     283             :     return 0UL;
     284             :   }
     285             : #else
     286      147150 :   (void)verbose;
     287      147150 : #endif
     288             : 
     289      147150 :   ulong txn_idx = (ulong)(txn - funk->txn_pool->ele);
     290      147150 :   return fd_funk_txn_cancel_family( funk, funk->shmem->cycle_tag++, txn_idx );
     291      147150 : }
     292             : 
     293             : /* fd_funk_txn_oldest_sibling returns the index of the oldest sibling
     294             :    in txn_idx's family.  Callers have already validated our input
     295             :    arguments.  The caller should validate the return index. */
     296             : 
     297             : static inline ulong
     298             : fd_funk_txn_oldest_sibling( fd_funk_t *  funk,
     299      215977 :                             ulong        txn_idx ) {
     300      215977 :   ulong parent_idx = fd_funk_txn_idx( funk->txn_pool->ele[ txn_idx ].parent_cidx );
     301             : 
     302      215977 :   if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) return fd_funk_txn_idx( funk->shmem->child_head_cidx ); /* opt for incr pub */
     303             : 
     304        9432 :   return fd_funk_txn_idx( funk->txn_pool->ele[ parent_idx ].child_head_cidx );
     305      215977 : }
     306             : 
     307             : /* fd_funk_txn_cancel_sibling_list cancels siblings from sibling_idx down
     308             :    to the youngest sibling inclusive in the order from youngest to
     309             :    sibling_idx.  Callers have already validated our input arguments
     310             :    except sibling_idx.  Returns the number of cancelled transactions
     311             :    (should be at least 1).  If any sibling is skip_idx, it will be not
     312             :    be cancelled but still tagged as visited.  Passing
     313             :    FD_FUNK_TXN_IDX_NULL for skip_idx will cancel all siblings from
     314             :    sibling_idx to the youngest inclusive. */
     315             : 
     316             : static ulong
     317             : fd_funk_txn_cancel_sibling_list( fd_funk_t * funk,
     318             :                                     ulong          tag,
     319             :                                     ulong          sibling_idx,
     320      215977 :                                     ulong          skip_idx ) {
     321             : 
     322      215977 :   ulong cancel_stack_idx = FD_FUNK_TXN_IDX_NULL;
     323             : 
     324             :   /* Push siblings_idx and its younger siblings inclusive (skipping
     325             :      sibling skip_idx if encounter) onto the cancel stack from oldest to
     326             :      youngest (such that we cancel youngest to oldest). */
     327             : 
     328      544070 :   for(;;) {
     329             : 
     330             :     /* At this point, sibling_idx is a sibling we might want to add to
     331             :        the sibling stack.  Validate and tag it. */
     332             : 
     333      544070 :     fd_funk_txn_t * sibling = &funk->txn_pool->ele[ sibling_idx ];
     334      544070 :     sibling->tag = tag;
     335             : 
     336      544070 :     if( FD_UNLIKELY( sibling_idx!=skip_idx ) ) { /* Not skip_idx so push onto the cancel stack, opt for non-compete */
     337      328093 :       sibling->stack_cidx = fd_funk_txn_cidx( cancel_stack_idx );
     338      328093 :       cancel_stack_idx = sibling_idx;
     339      328093 :     }
     340             : 
     341      544070 :     ulong younger_idx = fd_funk_txn_idx( sibling->sibling_next_cidx );
     342      544070 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( younger_idx ) ) ) break; /* opt for non-compete */
     343      328093 :     sibling_idx = younger_idx;
     344             : 
     345      328093 :   }
     346             : 
     347             :   /* Cancel all transactions and their descendants on the cancel stack */
     348             : 
     349      215977 :   ulong cancel_cnt = 0UL;
     350             : 
     351      544070 :   while( !fd_funk_txn_idx_is_null( cancel_stack_idx ) ) { /* TODO: peel first iter to make more predictable? */
     352      328093 :     ulong sibling_idx = cancel_stack_idx;
     353      328093 :     cancel_stack_idx  = fd_funk_txn_idx( funk->txn_pool->ele[ sibling_idx ].stack_cidx );
     354      328093 :     cancel_cnt += fd_funk_txn_cancel_family( funk, tag, sibling_idx );
     355      328093 :   }
     356             : 
     357      215977 :   return cancel_cnt;
     358      215977 : }
     359             : 
     360             : ulong
     361             : fd_funk_txn_cancel_siblings( fd_funk_t *     funk,
     362             :                              fd_funk_txn_t * txn,
     363           0 :                              int             verbose ) {
     364             : 
     365             : #ifdef FD_FUNK_HANDHOLDING
     366             :   if( FD_UNLIKELY( !funk ) ) {
     367             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     368             :     return 0UL;
     369             :   }
     370             :   if( FD_UNLIKELY( !fd_funk_txn_valid( funk, txn ) ) ) {
     371             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "bad txn" ));
     372             :     return 0UL;
     373             :   }
     374             : #else
     375           0 :   (void)verbose;
     376           0 : #endif
     377             : 
     378           0 :   ulong txn_idx = (ulong)(txn - funk->txn_pool->ele);
     379             : 
     380           0 :   ulong oldest_idx = fd_funk_txn_oldest_sibling( funk, txn_idx );
     381             : 
     382           0 :   return fd_funk_txn_cancel_sibling_list( funk, funk->shmem->cycle_tag++, oldest_idx, txn_idx );
     383           0 : }
     384             : 
     385             : ulong
     386             : fd_funk_txn_cancel_children( fd_funk_t *     funk,
     387             :                              fd_funk_txn_t * txn,
     388           0 :                              int             verbose ) {
     389             : 
     390             : #ifdef FD_FUNK_HANDHOLDING
     391             :   if( FD_UNLIKELY( !funk ) ) {
     392             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     393             :     return 0UL;
     394             :   }
     395             :   if( FD_UNLIKELY( !fd_funk_txn_valid( funk, txn ) ) ) {
     396             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "bad txn" ));
     397             :     return 0UL;
     398             :   }
     399             : #else
     400           0 :   (void)verbose;
     401           0 : #endif
     402             : 
     403           0 :   ulong oldest_idx;
     404             : 
     405           0 :   if( FD_LIKELY( txn == NULL ) ) {
     406           0 :     oldest_idx = fd_funk_txn_idx( funk->shmem->child_head_cidx ); /* opt for non-compete */
     407           0 :   } else {
     408           0 :     oldest_idx = fd_funk_txn_idx( txn->child_head_cidx );
     409           0 :   }
     410             : 
     411           0 :   if( fd_funk_txn_idx_is_null( oldest_idx ) ) {
     412           0 :     return 0UL;
     413           0 :   }
     414             : 
     415           0 :   return fd_funk_txn_cancel_sibling_list( funk, funk->shmem->cycle_tag++, oldest_idx, FD_FUNK_TXN_IDX_NULL );
     416           0 : }
     417             : 
     418             : /* Cancel all outstanding transactions */
     419             : 
     420             : ulong
     421             : fd_funk_txn_cancel_all( fd_funk_t *     funk,
     422           0 :                         int             verbose ) {
     423           0 :   return fd_funk_txn_cancel_children( funk, NULL, verbose );
     424           0 : }
     425             : 
     426             : /* fd_funk_txn_update applies the record updates in transaction txn_idx
     427             :    to another transaction or the parent transaction.  Callers have
     428             :    already validated our input arguments.
     429             : 
     430             :    On entry, the head/tail of the destination records are at
     431             :    *_dst_rec_head_idx / *_dst_rec_tail_idx.  All transactions on this
     432             :    list will have transaction id dst_xid and vice versa.  That is, this
     433             :    is the record list the last published transaction or txn_idx's
     434             :    in-prep parent transaction.
     435             : 
     436             :    On exit, the head/tail of the updated records is at
     437             :    *_dst_rec_head_idx / *_dst_rec_tail_idx.  As before, all transactions
     438             :    on this list will have transaction id dst_xid and vice versa.
     439             :    Transaction txn_idx will have an _empty_ record list.
     440             : 
     441             :    Updates in the transaction txn_idx are processed from oldest to
     442             :    youngest.  If an update erases an existing record in dest, the record
     443             :    to erase is removed from the destination records without perturbing
     444             :    the order of remaining destination records.  If an update is to
     445             :    update an existing record, the destination record value is updated
     446             :    and the order of the destination records is unchanged.  If an update
     447             :    is to create a new record, the record is appended to the list of
     448             :    existing values as youngest without changing the order of existing
     449             :    values.  If an update erases a record in an in-prep parent, the
     450             :    erasure will be moved into the parent as the youngest without
     451             :    changing the order of existing values. */
     452             : 
     453             : static void
     454             : fd_funk_txn_update( fd_funk_t *                  funk,
     455             :                        uint *                   _dst_rec_head_idx, /* Pointer to the dst list head */
     456             :                        uint *                   _dst_rec_tail_idx, /* Pointer to the dst list tail */
     457             :                        ulong                     dst_txn_idx,       /* Transaction index of the merge destination */
     458             :                        fd_funk_txn_xid_t const * dst_xid,        /* dst xid */
     459      215977 :                        ulong                     txn_idx ) {        /* Transaction index of the records to merge */
     460      215977 :   fd_wksp_t *          wksp     = funk->wksp;
     461      215977 :   fd_alloc_t *         alloc    = funk->alloc;
     462      215977 :   fd_funk_rec_map_t *  rec_map  = funk->rec_map;
     463      215977 :   fd_funk_rec_pool_t * rec_pool = funk->rec_pool;
     464      215977 :   fd_funk_txn_pool_t * txn_pool = funk->txn_pool;
     465             : 
     466      215977 :   fd_funk_txn_t * txn = &txn_pool->ele[ txn_idx ];
     467      215977 :   uint rec_idx = txn->rec_head_idx;
     468     2166170 :   while( !fd_funk_rec_idx_is_null( rec_idx ) ) {
     469     1950193 :     fd_funk_rec_t * rec = &rec_pool->ele[ rec_idx ];
     470     1950193 :     uint next_rec_idx = rec->next_idx;
     471             : 
     472             :     /* See if (dst_xid,key) already exists.  */
     473     1950193 :     fd_funk_xid_key_pair_t pair[1];
     474     1950193 :     fd_funk_xid_key_pair_init( pair, dst_xid, rec->pair.key );
     475     1950193 :     for(;;) {
     476     1950193 :       fd_funk_rec_map_query_t rec_query[1];
     477     1950193 :       int err = fd_funk_rec_map_remove( rec_map, pair, NULL, rec_query, FD_MAP_FLAG_BLOCKING );
     478     1950193 :       if( FD_UNLIKELY( err == FD_MAP_ERR_AGAIN ) ) continue;
     479     1950193 :       if( err == FD_MAP_ERR_KEY ) break;
     480      170229 :       if( FD_UNLIKELY( err != FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "map corruption" ));
     481             : 
     482             :       /* Remove from the transaction */
     483      170229 :       fd_funk_rec_t * rec2 = fd_funk_rec_map_query_ele( rec_query );
     484      170229 :       uint prev_idx = rec2->prev_idx;
     485      170229 :       uint next_idx = rec2->next_idx;
     486      170229 :       if( fd_funk_rec_idx_is_null( prev_idx ) ) {
     487        2454 :         *_dst_rec_head_idx = next_idx;
     488      167775 :       } else {
     489      167775 :         rec_pool->ele[ prev_idx ].next_idx = next_idx;
     490      167775 :       }
     491      170229 :       if( fd_funk_rec_idx_is_null( next_idx ) ) {
     492         411 :         *_dst_rec_tail_idx = prev_idx;
     493      169818 :       } else {
     494      169818 :         rec_pool->ele[ next_idx ].prev_idx = prev_idx;
     495      169818 :       }
     496             :       /* Clean up value */
     497      170229 :       fd_funk_val_flush( rec2, alloc, wksp );
     498      170229 :       rec2->txn_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     499      170229 :       fd_funk_rec_pool_release( rec_pool, rec2, 1 );
     500      170229 :       break;
     501      170229 :     }
     502             : 
     503             :     /* Add the new record to the transaction. We can update the xid in
     504             :        place because it is not used for hashing the element. We have
     505             :        to preserve the original element to preserve the
     506             :        newest-to-oldest ordering in the hash
     507             :        chain. fd_funk_rec_query_global relies on this subtle
     508             :        property. */
     509             : 
     510     1950193 :     rec->pair.xid[0] = *dst_xid;
     511     1950193 :     rec->txn_cidx = fd_funk_txn_cidx( dst_txn_idx );
     512             : 
     513     1950193 :     if( fd_funk_rec_idx_is_null( *_dst_rec_head_idx ) ) {
     514        4647 :       *_dst_rec_head_idx = rec_idx;
     515        4647 :       rec->prev_idx = FD_FUNK_REC_IDX_NULL;
     516     1945546 :     } else {
     517     1945546 :       rec_pool->ele[ *_dst_rec_tail_idx ].next_idx = rec_idx;
     518     1945546 :       rec->prev_idx = *_dst_rec_tail_idx;
     519     1945546 :     }
     520     1950193 :     *_dst_rec_tail_idx = rec_idx;
     521     1950193 :     rec->next_idx = FD_FUNK_REC_IDX_NULL;
     522             : 
     523     1950193 :     rec_idx = next_rec_idx;
     524     1950193 :   }
     525             : 
     526      215977 :   txn_pool->ele[ txn_idx ].rec_head_idx = FD_FUNK_REC_IDX_NULL;
     527      215977 :   txn_pool->ele[ txn_idx ].rec_tail_idx = FD_FUNK_REC_IDX_NULL;
     528      215977 : }
     529             : 
     530             : /* fd_funk_txn_publish_funk_child publishes a transaction that is known
     531             :    to be a child of funk.  Callers have already validated our input
     532             :    arguments.  Returns FD_FUNK_SUCCESS on success and an FD_FUNK_ERR_*
     533             :    code on failure.  (There are currently no failure cases but the
     534             :    plumbing is there if value handling requires it at some point.) */
     535             : 
     536             : static int
     537             : fd_funk_txn_publish_funk_child( fd_funk_t * const funk,
     538             :                                 ulong       const tag,
     539      203977 :                                 ulong       const txn_idx ) {
     540             : 
     541             :   /* Apply the updates in txn to the last published transactions */
     542             : 
     543      203977 :   fd_funk_txn_update( funk, &funk->shmem->rec_head_idx, &funk->shmem->rec_tail_idx, FD_FUNK_TXN_IDX_NULL, fd_funk_root( funk ), txn_idx );
     544             : 
     545             :   /* Cancel all competing transaction histories */
     546             : 
     547      203977 :   ulong oldest_idx = fd_funk_txn_oldest_sibling( funk, txn_idx );
     548      203977 :   fd_funk_txn_cancel_sibling_list( funk, tag, oldest_idx, txn_idx );
     549             : 
     550             :   /* Make all the children children of funk */
     551             : 
     552      203977 :   fd_funk_txn_t * txn = fd_funk_txn_pool_ele( funk->txn_pool, txn_idx );
     553      203977 :   ulong child_head_idx = fd_funk_txn_idx( txn->child_head_cidx );
     554      203977 :   ulong child_tail_idx = fd_funk_txn_idx( txn->child_tail_cidx );
     555             : 
     556      203977 :   ulong child_idx = child_head_idx;
     557      386202 :   while( FD_UNLIKELY( !fd_funk_txn_idx_is_null( child_idx ) ) ) { /* opt for incr pub */
     558      182225 :     fd_funk_txn_t * child_txn = fd_funk_txn_pool_ele( funk->txn_pool, child_idx );
     559      182225 :     child_txn->tag = tag;
     560      182225 :     child_txn->parent_cidx = fd_funk_txn_cidx( FD_FUNK_TXN_IDX_NULL );
     561      182225 :     child_idx = fd_funk_txn_idx( child_txn->sibling_next_cidx );
     562      182225 :   }
     563             : 
     564      203977 :   funk->shmem->child_head_cidx = fd_funk_txn_cidx( child_head_idx );
     565      203977 :   funk->shmem->child_tail_cidx = fd_funk_txn_cidx( child_tail_idx );
     566             : 
     567      203977 :   fd_funk_txn_xid_copy( funk->shmem->last_publish, fd_funk_txn_xid( txn ) );
     568             : 
     569             :   /* Remove the mapping */
     570             : 
     571      203977 :   fd_funk_txn_map_query_t query[1];
     572      203977 :   if( fd_funk_txn_map_remove( funk->txn_map, funk->shmem->last_publish, NULL, query, FD_MAP_FLAG_BLOCKING ) == FD_MAP_SUCCESS ) {
     573      203977 :     fd_funk_txn_pool_release( funk->txn_pool, txn, 1 );
     574      203977 :   }
     575             : 
     576      203977 :   return FD_FUNK_SUCCESS;
     577      203977 : }
     578             : 
     579             : ulong
     580             : fd_funk_txn_publish( fd_funk_t *     funk,
     581             :                      fd_funk_txn_t * txn,
     582      147637 :                      int             verbose ) {
     583             : 
     584             : #ifdef FD_FUNK_HANDHOLDING
     585             :   if( FD_UNLIKELY( !funk ) ) {
     586             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     587             :     return 0UL;
     588             :   }
     589             :   if( FD_UNLIKELY( !fd_funk_txn_valid( funk, txn ) ) ) {
     590             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "bad txn" ));
     591             :     return 0UL;
     592             :   }
     593             : #else
     594      147637 :   (void)verbose;
     595      147637 : #endif
     596             : 
     597      147637 :   ulong txn_idx = (ulong)(txn - funk->txn_pool->ele);
     598             : 
     599      147637 :   ulong tag = funk->shmem->cycle_tag++;
     600             : 
     601      147637 :   ulong publish_stack_idx = FD_FUNK_TXN_IDX_NULL;
     602             : 
     603      203977 :   for(;;) {
     604      203977 :     fd_funk_txn_t * txn2 = &funk->txn_pool->ele[ txn_idx ];
     605      203977 :     txn2->tag = tag;
     606             : 
     607             :     /* At this point, txn_idx is a transaction that needs to be
     608             :        published and has been tagged.  If txn is a child of funk, we are
     609             :        ready to publish txn and everything on the publish stack. */
     610             : 
     611      203977 :     ulong parent_idx = fd_funk_txn_idx( txn2->parent_cidx );
     612      203977 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( parent_idx ) ) ) break; /* opt for incr pub */
     613             : 
     614             :     /* txn_idx has a parent.  Validate and tag it.  Push txn to the
     615             :        publish stack and recurse into the parent. */
     616             : 
     617       56340 :     txn2->stack_cidx = fd_funk_txn_cidx( publish_stack_idx );
     618       56340 :     publish_stack_idx = txn_idx;
     619             : 
     620       56340 :     txn_idx = parent_idx;
     621       56340 :   }
     622             : 
     623      147637 :   ulong publish_cnt = 0UL;
     624             : 
     625      203977 :   for(;;) {
     626             : 
     627             :     /* At this point, all the transactions we need to publish are
     628             :        tagged, txn is the next up publish funk and publish_stack has the
     629             :        transactions to follow in order by pop.  We use a new tag for
     630             :        each publish as txn and its siblings we potentially visited in a
     631             :        previous iteration of this loop. */
     632             : 
     633      203977 :     if( FD_UNLIKELY( fd_funk_txn_publish_funk_child( funk, funk->shmem->cycle_tag++, txn_idx ) ) ) break;
     634      203977 :     publish_cnt++;
     635             : 
     636      203977 :     txn_idx = publish_stack_idx;
     637      203977 :     if( FD_LIKELY( fd_funk_txn_idx_is_null( txn_idx ) ) ) break; /* opt for incr pub */
     638       56340 :     publish_stack_idx = fd_funk_txn_idx( funk->txn_pool->ele[ txn_idx ].stack_cidx );
     639       56340 :   }
     640             : 
     641      147637 :   return publish_cnt;
     642      147637 : }
     643             : 
     644             : int
     645             : fd_funk_txn_publish_into_parent( fd_funk_t *     funk,
     646             :                                  fd_funk_txn_t * txn,
     647       12000 :                                  int             verbose ) {
     648             : #ifdef FD_FUNK_HANDHOLDING
     649             :   if( FD_UNLIKELY( !funk ) ) {
     650             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "NULL funk" ));
     651             :     return FD_FUNK_ERR_INVAL;
     652             :   }
     653             :   if( FD_UNLIKELY( !fd_funk_txn_valid( funk, txn ) ) ) {
     654             :     if( FD_UNLIKELY( verbose ) ) FD_LOG_WARNING(( "bad txn" ));
     655             :     return 0UL;
     656             :   }
     657             : #else
     658       12000 :   (void)verbose;
     659       12000 : #endif
     660             : 
     661       12000 :   fd_funk_txn_map_t *  txn_map  = funk->txn_map;
     662       12000 :   fd_funk_txn_pool_t * txn_pool = funk->txn_pool;
     663       12000 :   ulong txn_idx = (ulong)(txn - txn_pool->ele);
     664             : 
     665       12000 :   ulong oldest_idx = fd_funk_txn_oldest_sibling( funk, txn_idx );
     666       12000 :   fd_funk_txn_cancel_sibling_list( funk, funk->shmem->cycle_tag++, oldest_idx, txn_idx );
     667             : 
     668       12000 :   ulong parent_idx = fd_funk_txn_idx( txn->parent_cidx );
     669       12000 :   if( fd_funk_txn_idx_is_null( parent_idx ) ) {
     670             :     /* Publish to root */
     671        2568 :     fd_funk_txn_update( funk, &funk->shmem->rec_head_idx, &funk->shmem->rec_tail_idx, FD_FUNK_TXN_IDX_NULL, fd_funk_root( funk ), txn_idx );
     672             :     /* Inherit the children */
     673        2568 :     funk->shmem->child_head_cidx = txn->child_head_cidx;
     674        2568 :     funk->shmem->child_tail_cidx = txn->child_tail_cidx;
     675        9432 :   } else {
     676        9432 :     fd_funk_txn_t * parent_txn = &txn_pool->ele[ parent_idx ];
     677        9432 :     fd_funk_txn_update( funk, &parent_txn->rec_head_idx, &parent_txn->rec_tail_idx, parent_idx, &parent_txn->xid, txn_idx );
     678             :     /* Inherit the children */
     679        9432 :     parent_txn->child_head_cidx = txn->child_head_cidx;
     680        9432 :     parent_txn->child_tail_cidx = txn->child_tail_cidx;
     681        9432 :   }
     682             : 
     683             :   /* Adjust the parent pointers of the children to point to their grandparent */
     684       12000 :   ulong child_idx = fd_funk_txn_idx( txn->child_head_cidx );
     685       21123 :   while( FD_UNLIKELY( !fd_funk_txn_idx_is_null( child_idx ) ) ) {
     686        9123 :     txn_pool->ele[ child_idx ].parent_cidx = fd_funk_txn_cidx( parent_idx );
     687        9123 :     child_idx = fd_funk_txn_idx( txn_pool->ele[ child_idx ].sibling_next_cidx );
     688        9123 :   }
     689             : 
     690       12000 :   fd_funk_txn_map_query_t query[1];
     691       12000 :   if( fd_funk_txn_map_remove( txn_map, fd_funk_txn_xid( txn ), NULL, query, FD_MAP_FLAG_BLOCKING ) == FD_MAP_SUCCESS ) {
     692       12000 :     fd_funk_txn_pool_release( txn_pool, txn, 1 );
     693       12000 :   }
     694             : 
     695       12000 :   return FD_FUNK_SUCCESS;
     696       12000 : }
     697             : 
     698             : /* Return the first record in a transaction. Returns NULL if the
     699             :    transaction has no records yet. */
     700             : 
     701             : fd_funk_rec_t const *
     702             : fd_funk_txn_first_rec( fd_funk_t *           funk,
     703    47622933 :                        fd_funk_txn_t const * txn ) {
     704    47622933 :   uint rec_idx;
     705    47622933 :   if( FD_UNLIKELY( NULL == txn ))
     706     3247731 :     rec_idx = funk->shmem->rec_head_idx;
     707    44375202 :   else
     708    44375202 :     rec_idx = txn->rec_head_idx;
     709    47622933 :   if( fd_funk_rec_idx_is_null( rec_idx ) ) return NULL;
     710    31761792 :   return funk->rec_pool->ele + rec_idx;
     711    47622933 : }
     712             : 
     713             : fd_funk_rec_t const *
     714             : fd_funk_txn_last_rec( fd_funk_t *           funk,
     715           0 :                       fd_funk_txn_t const * txn ) {
     716           0 :   uint rec_idx;
     717           0 :   if( FD_UNLIKELY( NULL == txn ))
     718           0 :     rec_idx = funk->shmem->rec_tail_idx;
     719           0 :   else
     720           0 :     rec_idx = txn->rec_tail_idx;
     721           0 :   if( fd_funk_rec_idx_is_null( rec_idx ) ) return NULL;
     722           0 :   return funk->rec_pool->ele + rec_idx;
     723           0 : }
     724             : 
     725             : /* Return the next record in a transaction. Returns NULL if the
     726             :    transaction has no more records. */
     727             : 
     728             : fd_funk_rec_t const *
     729             : fd_funk_txn_next_rec( fd_funk_t *           funk,
     730   656335248 :                       fd_funk_rec_t const * rec ) {
     731   656335248 :   uint rec_idx = rec->next_idx;
     732   656335248 :   if( fd_funk_rec_idx_is_null( rec_idx ) ) return NULL;
     733   594079938 :   return funk->rec_pool->ele + rec_idx;
     734   656335248 : }
     735             : 
     736             : fd_funk_rec_t const *
     737             : fd_funk_txn_prev_rec( fd_funk_t *           funk,
     738   318768039 :                       fd_funk_rec_t const * rec ) {
     739   318768039 :   uint rec_idx = rec->prev_idx;
     740   318768039 :   if( fd_funk_rec_idx_is_null( rec_idx ) ) return NULL;
     741   288274521 :   return funk->rec_pool->ele + rec_idx;
     742   318768039 : }
     743             : 
     744             : fd_funk_txn_xid_t
     745    18918876 : fd_funk_generate_xid(void) {
     746    18918876 :   fd_funk_txn_xid_t xid;
     747    18918876 :   static FD_TL ulong seq = 0;
     748    18918876 :   xid.ul[0] =
     749    18918876 :     (fd_log_cpu_id() + 1U)*3138831853UL +
     750    18918876 :     (fd_log_thread_id() + 1U)*9180195821UL +
     751    18918876 :     (++seq)*6208101967UL;
     752    18918876 :   xid.ul[1] = ((ulong)fd_tickcount())*2810745731UL;
     753    18918876 :   return xid;
     754    18918876 : }
     755             : 
     756             : static void
     757  3817717099 : fd_funk_txn_all_iter_skip_nulls( fd_funk_txn_all_iter_t * iter ) {
     758  3817717099 :   if( iter->chain_idx == iter->chain_cnt ) return;
     759  6465226736 :   while( fd_funk_txn_map_iter_done( iter->txn_map_iter ) ) {
     760  2826233943 :     if( ++(iter->chain_idx) == iter->chain_cnt ) break;
     761  2647509637 :     iter->txn_map_iter = fd_funk_txn_map_iter( &iter->txn_map, iter->chain_idx );
     762  2647509637 :   }
     763  3817717099 : }
     764             : 
     765             : void
     766             : fd_funk_txn_all_iter_new( fd_funk_t *              funk,
     767   178724306 :                           fd_funk_txn_all_iter_t * iter ) {
     768   178724306 :   iter->txn_map = *funk->txn_map;
     769   178724306 :   iter->chain_cnt = fd_funk_txn_map_chain_cnt( funk->txn_map );
     770   178724306 :   iter->chain_idx = 0;
     771   178724306 :   iter->txn_map_iter = fd_funk_txn_map_iter( funk->txn_map, 0 );
     772   178724306 :   fd_funk_txn_all_iter_skip_nulls( iter );
     773   178724306 : }
     774             : 
     775             : int
     776  3817717099 : fd_funk_txn_all_iter_done( fd_funk_txn_all_iter_t * iter ) {
     777  3817717099 :   return ( iter->chain_idx == iter->chain_cnt );
     778  3817717099 : }
     779             : 
     780             : void
     781  3638992793 : fd_funk_txn_all_iter_next( fd_funk_txn_all_iter_t * iter ) {
     782  3638992793 :   iter->txn_map_iter = fd_funk_txn_map_iter_next( iter->txn_map_iter );
     783  3638992793 :   fd_funk_txn_all_iter_skip_nulls( iter );
     784  3638992793 : }
     785             : 
     786             : fd_funk_txn_t const *
     787     1846548 : fd_funk_txn_all_iter_ele_const( fd_funk_txn_all_iter_t * iter ) {
     788     1846548 :   return fd_funk_txn_map_iter_ele_const( iter->txn_map_iter );
     789     1846548 : }
     790             : 
     791             : fd_funk_txn_t *
     792  3637145631 : fd_funk_txn_all_iter_ele( fd_funk_txn_all_iter_t * iter ) {
     793  3637145631 :   return fd_funk_txn_map_iter_ele( iter->txn_map_iter );
     794  3637145631 : }
     795             : 
     796             : int
     797          12 : fd_funk_txn_verify( fd_funk_t * funk ) {
     798          12 :   fd_funk_txn_map_t *  txn_map  = funk->txn_map;
     799          12 :   fd_funk_txn_pool_t * txn_pool = funk->txn_pool;
     800          12 :   ulong                txn_max  = fd_funk_txn_pool_ele_max( txn_pool );
     801             : 
     802          12 :   ulong funk_child_head_idx = fd_funk_txn_idx( funk->shmem->child_head_cidx ); /* Previously verified */
     803          12 :   ulong funk_child_tail_idx = fd_funk_txn_idx( funk->shmem->child_tail_cidx ); /* Previously verified */
     804             : 
     805          12 :   fd_funk_txn_xid_t const * last_publish = funk->shmem->last_publish; /* Previously verified */
     806             : 
     807          24 : # define TEST(c) do {                                                                           \
     808          24 :     if( FD_UNLIKELY( !(c) ) ) { FD_LOG_WARNING(( "FAIL: %s", #c )); return FD_FUNK_ERR_INVAL; } \
     809          24 :   } while(0)
     810             : 
     811          12 : # define IS_VALID( idx ) ( (idx==FD_FUNK_TXN_IDX_NULL) || \
     812          12 :                            ((idx<txn_max) && (!fd_funk_txn_xid_eq( fd_funk_txn_xid( &txn_pool->ele[idx] ), last_publish ))) )
     813             : 
     814          12 :   TEST( !fd_funk_txn_map_verify( txn_map ) );
     815          12 :   TEST( !fd_funk_txn_pool_verify( txn_pool ) );
     816             : 
     817             :   /* Tag all transactions as not visited yet */
     818             : 
     819     1572876 :   for( ulong txn_idx=0UL; txn_idx<txn_max; txn_idx++ ) txn_pool->ele[ txn_idx ].tag = 0UL;
     820             : 
     821             :   /* Visit all transactions in preparation, traversing from oldest to
     822             :      youngest. */
     823             : 
     824          12 :   do {
     825             : 
     826             :     /* Push all children of funk to the stack */
     827             : 
     828          12 :     ulong stack_idx = FD_FUNK_TXN_IDX_NULL;
     829          12 :     ulong child_idx = funk_child_head_idx;
     830          12 :     while( !fd_funk_txn_idx_is_null( child_idx ) ) {
     831             : 
     832             :       /* Make sure valid idx, not tagged (detects cycles) and child
     833             :          knows it is a child of funk.  Then tag as visited / in-prep,
     834             :          push to stack and update prep_cnt */
     835             : 
     836           0 :       TEST( IS_VALID( child_idx ) );
     837           0 :       TEST( !txn_pool->ele[ child_idx ].tag );
     838           0 :       TEST( fd_funk_txn_idx_is_null( fd_funk_txn_idx( txn_pool->ele[ child_idx ].parent_cidx ) ) );
     839           0 :       txn_pool->ele[ child_idx ].tag        = 1UL;
     840           0 :       txn_pool->ele[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
     841           0 :       stack_idx                   = child_idx;
     842             : 
     843           0 :       ulong next_idx = fd_funk_txn_idx( txn_pool->ele[ child_idx ].sibling_next_cidx );
     844           0 :       if( !fd_funk_txn_idx_is_null( next_idx ) ) TEST( fd_funk_txn_idx( txn_pool->ele[ next_idx ].sibling_prev_cidx )==child_idx );
     845           0 :       child_idx = next_idx;
     846           0 :     }
     847             : 
     848          12 :     while( !fd_funk_txn_idx_is_null( stack_idx ) ) {
     849             : 
     850             :       /* Pop the next transaction to traverse */
     851             : 
     852           0 :       ulong txn_idx = stack_idx;
     853           0 :       stack_idx = fd_funk_txn_idx( txn_pool->ele[ txn_idx ].stack_cidx );
     854             : 
     855             :       /* Push all children of txn to the stack */
     856             : 
     857           0 :       ulong child_idx = fd_funk_txn_idx( txn_pool->ele[ txn_idx ].child_head_cidx );
     858           0 :       while( !fd_funk_txn_idx_is_null( child_idx ) ) {
     859             : 
     860             :         /* Make sure valid idx, not tagged (detects cycles) and child
     861             :            knows it is a child of txn_idx.  Then tag as visited /
     862             :            in-prep, push to stack and update prep_cnt */
     863             : 
     864           0 :         TEST( IS_VALID( child_idx ) );
     865           0 :         TEST( !txn_pool->ele[ child_idx ].tag );
     866           0 :         TEST( fd_funk_txn_idx( txn_pool->ele[ child_idx ].parent_cidx )==txn_idx );
     867           0 :         txn_pool->ele[ child_idx ].tag        = 1UL;
     868           0 :         txn_pool->ele[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
     869           0 :         stack_idx                   = child_idx;
     870             : 
     871           0 :         ulong next_idx = fd_funk_txn_idx( txn_pool->ele[ child_idx ].sibling_next_cidx );
     872           0 :         if( !fd_funk_txn_idx_is_null( next_idx ) ) TEST( fd_funk_txn_idx( txn_pool->ele[ next_idx ].sibling_prev_cidx )==child_idx );
     873           0 :         child_idx = next_idx;
     874           0 :       }
     875           0 :     }
     876             : 
     877          12 :   } while(0);
     878             : 
     879             :   /* Do it again with a youngest to oldest traversal to test reverse
     880             :      link integrity */
     881             : 
     882          12 :   do {
     883             : 
     884             :     /* Push all children of funk to the stack */
     885             : 
     886          12 :     ulong stack_idx = FD_FUNK_TXN_IDX_NULL;
     887          12 :     ulong child_idx = funk_child_tail_idx;
     888          12 :     while( !fd_funk_txn_idx_is_null( child_idx ) ) {
     889             : 
     890             :       /* Make sure valid idx, tagged as visited above (detects cycles)
     891             :          and child knows it is a child of funk.  Then tag as visited /
     892             :          in-prep, push to stack and update prep_cnt */
     893             : 
     894           0 :       TEST( IS_VALID( child_idx ) );
     895           0 :       TEST( txn_pool->ele[ child_idx ].tag==1UL );
     896           0 :       TEST( fd_funk_txn_idx_is_null( fd_funk_txn_idx( txn_pool->ele[ child_idx ].parent_cidx ) ) );
     897           0 :       txn_pool->ele[ child_idx ].tag        = 2UL;
     898           0 :       txn_pool->ele[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
     899           0 :       stack_idx                             = child_idx;
     900             : 
     901           0 :       ulong prev_idx = fd_funk_txn_idx( txn_pool->ele[ child_idx ].sibling_prev_cidx );
     902           0 :       if( !fd_funk_txn_idx_is_null( prev_idx ) ) TEST( fd_funk_txn_idx( txn_pool->ele[ prev_idx ].sibling_next_cidx )==child_idx );
     903           0 :       child_idx = prev_idx;
     904           0 :     }
     905             : 
     906          12 :     while( !fd_funk_txn_idx_is_null( stack_idx ) ) {
     907             : 
     908             :       /* Pop the next transaction to traverse */
     909             : 
     910           0 :       ulong txn_idx = stack_idx;
     911           0 :       stack_idx = fd_funk_txn_idx( txn_pool->ele[ txn_idx ].stack_cidx );
     912             : 
     913             :       /* Push all children of txn to the stack */
     914             : 
     915           0 :       ulong child_idx = fd_funk_txn_idx( txn_pool->ele[ txn_idx ].child_tail_cidx );
     916           0 :       while( !fd_funk_txn_idx_is_null( child_idx ) ) {
     917             : 
     918             :         /* Make sure valid idx, tagged as visited above (detects cycles)
     919             :            and child knows it is a child of txn_idx.  Then, tag as
     920             :            visited / in-prep, push to stack and update prep_cnt */
     921             : 
     922           0 :         TEST( IS_VALID( child_idx ) );
     923           0 :         TEST( txn_pool->ele[ child_idx ].tag==1UL );
     924           0 :         TEST( fd_funk_txn_idx( txn_pool->ele[ child_idx ].parent_cidx )==txn_idx );
     925           0 :         txn_pool->ele[ child_idx ].tag        = 2UL;
     926           0 :         txn_pool->ele[ child_idx ].stack_cidx = fd_funk_txn_cidx( stack_idx );
     927           0 :         stack_idx                             = child_idx;
     928             : 
     929           0 :         ulong prev_idx = fd_funk_txn_idx( txn_pool->ele[ child_idx ].sibling_prev_cidx );
     930           0 :         if( !fd_funk_txn_idx_is_null( prev_idx ) ) TEST( fd_funk_txn_idx( txn_pool->ele[ prev_idx ].sibling_next_cidx )==child_idx );
     931           0 :         child_idx = prev_idx;
     932           0 :       }
     933           0 :     }
     934          12 :   } while(0);
     935             : 
     936          12 : # undef IS_VALID
     937          12 : # undef TEST
     938             : 
     939          12 :   return FD_FUNK_SUCCESS;
     940          12 : }
     941             : 
     942             : int
     943           0 : fd_funk_txn_valid( fd_funk_t const * funk, fd_funk_txn_t const * txn ) {
     944           0 :   ulong txn_idx = (ulong)(txn - funk->txn_pool->ele);
     945           0 :   ulong txn_max = fd_funk_txn_pool_ele_max( funk->txn_pool );
     946           0 :   if( txn_idx>=txn_max || txn != txn_idx + funk->txn_pool->ele ) return 0;
     947           0 :   fd_funk_txn_map_query_t query[1];
     948           0 :   if( FD_UNLIKELY( fd_funk_txn_map_query_try( funk->txn_map, &txn->xid, NULL, query, 0 ) ) ) return 0;
     949           0 :   if( fd_funk_txn_map_query_ele( query ) != txn ) return 0;
     950           0 :   return 1;
     951           0 : }

Generated by: LCOV version 1.14