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

          Line data    Source code
       1             : #include "fd_txncache_writer.h"
       2             : #include "../../flamenco/runtime/fd_txncache_private.h"
       3             : #include "../../flamenco/runtime/sysvar/fd_sysvar_slot_history.h"
       4             : #include "../../util/fd_util.h"
       5             : #include "../../util/racesan/fd_racesan_target.h"
       6             : 
       7           0 : #define STATE_HEADER  1
       8           0 : #define STATE_SLOT    2
       9           0 : #define STATE_GROUP   3
      10           0 : #define STATE_ENTRIES 4
      11           0 : #define STATE_DONE    5
      12             : 
      13             : /* Wire sizes */
      14             : 
      15           0 : #define HEADER_SZ     (8UL)                      /* slot_deltas_len */
      16           0 : #define SLOT_DELTA_SZ (8UL+1UL+8UL)              /* slot, is_root, status_len */
      17           0 : #define GROUP_HDR_SZ  (32UL+8UL+8UL)             /* blockhash, txnhash_offset, txn_cnt */
      18           0 : #define TXN_SZ        (sizeof(fd_txnhash_t)+4UL) /* txnhash, result */
      19             : 
      20           0 : #define FD_TXNCACHE_WRITER_ARENA_MIN (64UL<<20)
      21             : 
      22           0 : #define WALK_COUNT 0
      23           0 : #define WALK_FILL  1
      24             : 
      25           0 : #define WALK_ANOMALY_NONE                       0
      26           0 : #define WALK_ANOMALY_CHAIN_TXN_IDX_OOB          1
      27           0 : #define WALK_ANOMALY_CYCLE                      2
      28           0 : #define WALK_ANOMALY_CAPTURED_FORK_NO_EXEC_SLOT 3
      29           0 : #define WALK_ANOMALY_GROUP_COUNT_EXCEEDED       4
      30             : 
      31             : FD_STATIC_ASSERT( FD_TXNCACHE_WRITER_CHECK_INTERVAL, check_interval_nonzero );
      32             : 
      33             : FD_STATIC_ASSERT( FD_TXNCACHE_WRITER_MAX_SLOT_DELTAS<=USHORT_MAX, group_slot_i      );
      34             : FD_STATIC_ASSERT( FD_TXNCACHE_WRITER_MAX_BLOCKHASHES<=USHORT_MAX, group_blockhash_i );
      35             : 
      36             : static inline ulong
      37           0 : group_key( ulong slot_i, ulong blockhash_i ) {
      38           0 :   return slot_i*FD_TXNCACHE_WRITER_MAX_BLOCKHASHES+blockhash_i;
      39           0 : }
      40             : 
      41             : FD_FN_CONST ulong
      42           0 : fd_txncache_writer_arena_align( void ) {
      43           0 :   return 64UL;
      44           0 : }
      45             : 
      46             : FD_FN_CONST ulong
      47           0 : fd_txncache_writer_arena_sz( ulong max_txn_per_slot ) {
      48             :   /* Write at least 2 slots at once, and have at least 64 MiB. */
      49           0 :   return fd_ulong_max( FD_TXNCACHE_WRITER_ARENA_MIN, 2UL*2UL*max_txn_per_slot*sizeof(fd_txnhash_t) );
      50           0 : }
      51             : 
      52             : /* Returns the captured rooted descriptor of the fork the txn executed
      53             :    on, or NULL if that fork is not in the captured rooted set, either
      54             :    because it is unrooted or because it is a cancelled fork whose pool
      55             :    slot has since been reused. */
      56             : 
      57             : static inline fd_txncache_writer_blockhash_desc_t const *
      58             : writer_exec_desc( fd_txncache_writer_t const *     writer,
      59           0 :                   fd_txncache_single_txn_t const * txn ) {
      60           0 :   ulong desc_i = writer->fork_id_to_blockhash_i[ txn->fork_id.val ];
      61           0 :   if( FD_UNLIKELY( desc_i==USHORT_MAX ) ) return NULL;
      62           0 :   fd_txncache_writer_blockhash_desc_t const * desc = &writer->blockhash_descs[ desc_i ];
      63           0 :   return txn->generation==desc->generation ? desc : NULL;
      64           0 : }
      65             : 
      66             : static void
      67             : writer_slots_init( fd_txncache_writer_t * writer,
      68             :                    uchar const *          slot_history,
      69           0 :                    ulong                  slot_history_sz ) {
      70             :   /* Agave's verify_slot_history rejects a snapshot whose SlotHistory is
      71             :      not exactly MAX_ENTRIES bits ending at the bank's slot. */
      72             : 
      73           0 :   fd_slot_history_view_t view[1];
      74           0 :   if( FD_UNLIKELY( !fd_sysvar_slot_history_view( view, slot_history, slot_history_sz ) ) ) {
      75           0 :     FD_LOG_CRIT(( "malformed SlotHistory sysvar at slot %lu", writer->snapshot_slot ));
      76           0 :   }
      77           0 :   if( FD_UNLIKELY( view->bits_len!=FD_SLOT_HISTORY_MAX_ENTRIES            ||
      78           0 :                    view->blocks_len*64UL!=FD_SLOT_HISTORY_MAX_ENTRIES     ||
      79           0 :                    view->next_slot>writer->snapshot_slot+1UL ) ) {
      80           0 :     FD_LOG_CRIT(( "malformed SlotHistory sysvar at slot %lu (bits_len %lu blocks_len %lu next_slot %lu)",
      81           0 :                   writer->snapshot_slot, view->bits_len, view->blocks_len, view->next_slot ));
      82           0 :   }
      83             : 
      84             :   /* The SlotHistory in the bank's sysvar cache is one slot stale: it
      85             :      is missing the bit for the snapshot slot itself, so the loop below
      86             :      forces that slot in. */
      87             : 
      88           0 :   ulong scan_cnt = fd_ulong_min( writer->snapshot_slot+1UL, FD_SLOT_HISTORY_MAX_ENTRIES );
      89           0 :   ulong cnt      = 0UL;
      90           0 :   for( ulong i=0UL; i<scan_cnt && cnt<FD_TXNCACHE_WRITER_MAX_SLOT_DELTAS; i++ ) {
      91           0 :     ulong slot = writer->snapshot_slot-i;
      92           0 :     if( slot!=writer->snapshot_slot && fd_sysvar_slot_history_find_slot( view, slot )!=FD_SLOT_HISTORY_SLOT_FOUND ) continue;
      93           0 :     writer->slots[ FD_TXNCACHE_WRITER_MAX_SLOT_DELTAS-1UL-cnt ] = slot; /* fill from the back to get ascending order */
      94           0 :     cnt++;
      95           0 :   }
      96           0 :   FD_TEST( cnt ); /* i==0 always counts */
      97             : 
      98           0 :   memmove( writer->slots, writer->slots+FD_TXNCACHE_WRITER_MAX_SLOT_DELTAS-cnt, cnt*sizeof(ulong) );
      99           0 :   writer->slot_cnt = cnt;
     100           0 : }
     101             : 
     102             : /* Pairs rooted blockcache descriptors with execution slots by rank from
     103             :    the newest end.  Every rooted block registered exactly one blockhash
     104             :    and set exactly one SlotHistory bit, and both sequences end at the
     105             :    snapshot slot.  The initial genesis descriptor may precede the named
     106             :    execution slots; it remains usable as a referenced blockhash but has
     107             :    no execution slot mapping. */
     108             : static int
     109           0 : writer_map_execution_slots( fd_txncache_writer_t * writer ) {
     110           0 :   ulong blockhash_cnt = writer->blockhash_cnt;
     111           0 :   ulong slot_cnt      = writer->slot_cnt;
     112           0 :   if( FD_UNLIKELY( blockhash_cnt>slot_cnt+1UL ) ) {
     113           0 :     FD_LOG_WARNING(( "txncache has %lu rooted blockhash descriptors but SlotHistory names only %lu execution slots", blockhash_cnt, slot_cnt ));
     114           0 :     return 0;
     115           0 :   }
     116             : 
     117           0 :   memset( writer->fork_id_to_blockhash_i, 0xFF, sizeof(writer->fork_id_to_blockhash_i) );
     118           0 :   for( ulong blockhash_i=0UL; blockhash_i<blockhash_cnt; blockhash_i++ ) {
     119           0 :     fd_txncache_writer_blockhash_desc_t * desc = &writer->blockhash_descs[ blockhash_i ];
     120           0 :     desc->slot_i = USHORT_MAX;
     121           0 :     writer->fork_id_to_blockhash_i[ desc->blockcache_idx ] = (ushort)blockhash_i;
     122           0 :   }
     123             : 
     124           0 :   ulong mapped_cnt   = fd_ulong_min( blockhash_cnt, slot_cnt );
     125           0 :   ulong blockhash_i0 = blockhash_cnt-mapped_cnt;
     126           0 :   ulong slot_i0      = slot_cnt     -mapped_cnt;
     127           0 :   for( ulong pair_i=0UL; pair_i<mapped_cnt; pair_i++ ) {
     128           0 :     ulong blockhash_i = blockhash_i0+pair_i;
     129           0 :     ulong slot_i      = slot_i0     +pair_i;
     130           0 :     writer->blockhash_descs[ blockhash_i ].slot_i = (ushort)slot_i;
     131           0 :   }
     132           0 :   return 1;
     133           0 : }
     134             : 
     135             : /* Walks every hash chain of a rooted blockcache without taking the
     136             :    txncache lock.  For each transaction that matches a captured rooted
     137             :    descriptor, the function either counts the transaction into its
     138             :    (execution slot, blockhash) group, or copies its hash into the
     139             :    group's arena range. */
     140             : 
     141             : static void
     142             : writer_walk_blockhash( fd_txncache_writer_t * writer,
     143             :                        ulong                  blockhash_i,
     144           0 :                        int                    mode ) {
     145           0 :   fd_txncache_t const *                       tc             = writer->tc;
     146           0 :   fd_txncache_writer_blockhash_desc_t const * blockhash_desc = &writer->blockhash_descs[ blockhash_i ];
     147             : 
     148             :   /* Loop invariant variables are hoisted because the compiler fences
     149             :      would otherwise force the address chains to be reloaded every
     150             :      bucket. */
     151           0 :   fd_txncache_shmem_t const *   shmem         = tc->shmem;
     152           0 :   uint const *                  heads         = tc->blockcache_pool[ blockhash_desc->blockcache_idx ].heads;
     153           0 :   fd_txncache_txnpage_t const * txnpages      = tc->txnpages;
     154           0 :   ulong                         bucket_cnt    = shmem->bucket_cnt;
     155           0 :   ulong                         txn_cap       = shmem->max_txnpages*FD_TXNCACHE_TXNS_PER_PAGE;
     156           0 :   ulong                         root_gen_init = writer->root_gen;
     157           0 :   fd_txnhash_t *                arena         = writer->arena;
     158           0 :   ulong                         group_lo      = writer->group_i;
     159           0 :   ulong                         group_hi      = writer->batch_hi;
     160             : 
     161           0 :   uint   bucket_entry_cnt[ FD_TXNCACHE_WRITER_MAX_SLOT_DELTAS ] = {0};
     162           0 :   ushort touched_slot_i[ FD_TXNCACHE_WRITER_MAX_SLOT_DELTAS ];
     163             : 
     164           0 :   for( ulong bucket=0UL; bucket<bucket_cnt; bucket++ ) {
     165           0 :     for(;;) {
     166           0 :       ulong gen0 = __atomic_load_n( &shmem->mutation_gen, __ATOMIC_ACQUIRE );
     167           0 :       if( FD_UNLIKELY( gen0&1UL ) ) {
     168           0 :         fd_racesan_hook( "txncache_writer:mutation_in_progress" );
     169           0 :         FD_SPIN_PAUSE();
     170           0 :         continue;
     171           0 :       }
     172             : 
     173             :       /* root_gen only changes inside a mutation bracket, so we check it
     174             :          once up front.  If mutation_gen checks out in the end, that
     175             :          implies root_gen is also good. */
     176           0 :       if( FD_UNLIKELY( __atomic_load_n( &shmem->root_gen, __ATOMIC_RELAXED )!=root_gen_init ) ) {
     177           0 :         FD_LOG_CRIT(( "txncache root advanced while snapshot of slot %lu was in progress", writer->snapshot_slot ));
     178           0 :       }
     179             : 
     180           0 :       ulong  touched_cnt      = 0UL;
     181           0 :       ulong  steps            = 0UL;
     182           0 :       int    anomaly          = WALK_ANOMALY_NONE;
     183           0 :       uint   anomaly_txn_idx  = UINT_MAX;
     184           0 :       fd_txncache_writer_blockhash_desc_t const * anomaly_desc = NULL;
     185             : 
     186           0 :       fd_racesan_hook( "txncache_writer:bucket_started" );
     187             : 
     188           0 :       for( uint head=__atomic_load_n( &heads[ bucket ], __ATOMIC_ACQUIRE ); head!=UINT_MAX; ) {
     189           0 :         if( FD_UNLIKELY( (ulong)head>=txn_cap ) ) {
     190           0 :           anomaly         = WALK_ANOMALY_CHAIN_TXN_IDX_OOB;
     191           0 :           anomaly_txn_idx = head;
     192           0 :           break;
     193           0 :         }
     194           0 :         fd_txncache_single_txn_t const * txn = txnpages[ head/FD_TXNCACHE_TXNS_PER_PAGE ].txns[ head%FD_TXNCACHE_TXNS_PER_PAGE ];
     195             :         /* Pigeonhole principle.  If we visited more than the max number
     196             :            of entries, then at least one entry has been visited twice,
     197             :            meaning a potential cycle. */
     198           0 :         if( FD_UNLIKELY( ++steps>txn_cap ) ) {
     199             :           /* Record the anomaly rather than fatally crash immediately.
     200             :              A concurrent compaction for example could legitimately send
     201             :              us into a loop.  We should simply retry in that case. */
     202           0 :           anomaly = WALK_ANOMALY_CYCLE;
     203           0 :           break;
     204           0 :         }
     205             : 
     206           0 :         fd_txncache_writer_blockhash_desc_t const * exec_desc = writer_exec_desc( writer, txn );
     207           0 :         if( FD_LIKELY( exec_desc ) ) {
     208           0 :           ushort slot_i = exec_desc->slot_i;
     209           0 :           if( FD_UNLIKELY( slot_i==USHORT_MAX ) ) {
     210           0 :             anomaly      = WALK_ANOMALY_CAPTURED_FORK_NO_EXEC_SLOT;
     211           0 :             anomaly_desc = exec_desc;
     212           0 :             break;
     213           0 :           }
     214           0 :           ulong key = group_key( slot_i, blockhash_i );
     215           0 :           if( mode==WALK_COUNT ) {
     216           0 :             if( FD_UNLIKELY( !bucket_entry_cnt[ slot_i ] ) ) touched_slot_i[ touched_cnt++ ] = slot_i;
     217           0 :             bucket_entry_cnt[ slot_i ]++;
     218           0 :           } else {
     219           0 :             ulong group_i = writer->group_i_by_key[ key ];
     220           0 :             if( group_i>=group_lo && group_i<group_hi ) {
     221           0 :               fd_txncache_writer_group_t * group = &writer->groups[ group_i ];
     222           0 :               uint entry_i = group->filled_entry_cnt+bucket_entry_cnt[ slot_i ];
     223           0 :               if( FD_UNLIKELY( entry_i<group->filled_entry_cnt || entry_i>=group->entry_cnt ) ) {
     224           0 :                 anomaly = WALK_ANOMALY_GROUP_COUNT_EXCEEDED;
     225           0 :                 break;
     226           0 :               }
     227           0 :               if( FD_UNLIKELY( !bucket_entry_cnt[ slot_i ] ) ) touched_slot_i[ touched_cnt++ ] = slot_i;
     228             : 
     229             :               /* Speculative.  These txnhash values are considered
     230             :                  committed when entry_cnt is bumped from local
     231             :                  accumulators, after the final generation check. */
     232           0 :               memcpy( arena[ group->arena_entry_off+entry_i ], txn->txnhash, sizeof(arena[0]) );
     233           0 :               bucket_entry_cnt[ slot_i ]++;
     234           0 :               fd_racesan_hook( "txncache_writer:txnhash_staged" );
     235           0 :             }
     236           0 :           }
     237           0 :         }
     238             : 
     239           0 :         if( FD_UNLIKELY( !(steps%FD_TXNCACHE_WRITER_CHECK_INTERVAL) ) ) {
     240           0 :           FD_HW_MFENCE_LD();
     241           0 :           if( __atomic_load_n( &shmem->mutation_gen, __ATOMIC_RELAXED )!=gen0 ) { break; }
     242           0 :         }
     243             : 
     244             :         /* Volatile: head is bounds checked and then used to index the
     245             :            pool, so it must be loaded exactly once. */
     246           0 :         head = FD_VOLATILE_CONST( txn->blockcache_next );
     247           0 :       }
     248             : 
     249           0 :       if( FD_UNLIKELY( anomaly!=WALK_ANOMALY_NONE ) ) { fd_racesan_hook( "txncache_writer:anomaly_detected" ); }
     250             : 
     251             :       /* Drain reads before we check generation number. */
     252           0 :       FD_HW_MFENCE_LD();
     253           0 :       ulong gen1 = __atomic_load_n( &shmem->mutation_gen, __ATOMIC_RELAXED );
     254             : 
     255           0 :       if( FD_UNLIKELY( gen1!=gen0 ) ) {
     256           0 :         for( ulong i=0UL; i<touched_cnt; i++ ) bucket_entry_cnt[ touched_slot_i[ i ] ] = 0U;
     257           0 :         FD_SPIN_PAUSE();
     258           0 :         continue;
     259           0 :       }
     260             : 
     261             :       /* At this point, we have an anomaly without any generation number
     262             :          change that would explain the anomaly.  Fatal. */
     263           0 :       switch( anomaly ) {
     264           0 :       case WALK_ANOMALY_NONE:
     265           0 :         break;
     266           0 :       case WALK_ANOMALY_CHAIN_TXN_IDX_OOB:
     267           0 :         FD_LOG_CRIT(( "txncache chain of bucket %lu of blockcache %lu has out-of-bounds transaction index %u (capacity %lu)", bucket, blockhash_desc->blockcache_idx, anomaly_txn_idx, txn_cap ));
     268           0 :       case WALK_ANOMALY_CYCLE:
     269           0 :         FD_LOG_CRIT(( "txncache chain walk of bucket %lu of blockcache %lu exceeded the pool's %lu transactions: cycle", bucket, blockhash_desc->blockcache_idx, txn_cap ));
     270           0 :       case WALK_ANOMALY_CAPTURED_FORK_NO_EXEC_SLOT:
     271           0 :         FD_LOG_CRIT(( "txncache transaction on captured fork %lu (descriptor %ld) has no execution slot", anomaly_desc->blockcache_idx, (long)(anomaly_desc-writer->blockhash_descs) ));
     272           0 :       case WALK_ANOMALY_GROUP_COUNT_EXCEEDED:
     273           0 :         FD_LOG_CRIT(( "txncache group in blockcache %lu exceeds its counted transaction count while snapshot of slot %lu is in progress", blockhash_desc->blockcache_idx, writer->snapshot_slot ));
     274           0 :       default:
     275           0 :         FD_LOG_CRIT(( "invalid txncache walk anomaly %d", anomaly ));
     276           0 :       }
     277             : 
     278             :       /* Yay!  This was a successful bucket walk.  Aggregate local
     279             :          accumulators into writer.  Then break out of the retry loop to
     280             :          start the next bucket. */
     281           0 :       for( ulong i=0UL; i<touched_cnt; i++ ) {
     282           0 :         ulong slot_i = touched_slot_i[ i ];
     283           0 :         ulong key    = group_key( slot_i, blockhash_i );
     284           0 :         uint  delta  = bucket_entry_cnt[ slot_i ];
     285           0 :         if( mode==WALK_COUNT ) writer->entry_cnt_by_key[ key ] += delta;
     286           0 :         else                   writer->groups[ writer->group_i_by_key[ key ] ].filled_entry_cnt += delta;
     287           0 :         bucket_entry_cnt[ slot_i ] = 0U;
     288           0 :       }
     289           0 :       break;
     290           0 :     }
     291           0 :   }
     292           0 : }
     293             : 
     294             : /* Walks every rooted blockcache once to count the transactions of each
     295             :    group, then lays the non-empty groups out in wire order: slot delta
     296             :    ascending, then wire group in blockhash descriptor order (oldest
     297             :    rooted blockcache first). */
     298             : 
     299             : static void
     300           0 : writer_groups_init( fd_txncache_writer_t * writer ) {
     301           0 :   memset( writer->entry_cnt_by_key, 0, sizeof(writer->entry_cnt_by_key) );
     302           0 :   for( ulong blockhash_i=0UL; blockhash_i<writer->blockhash_cnt; blockhash_i++ ) {
     303           0 :     writer_walk_blockhash( writer, blockhash_i, WALK_COUNT );
     304           0 :   }
     305             : 
     306           0 :   ulong group_cnt = 0UL;
     307           0 :   ulong entry_cnt = 0UL;
     308           0 :   for( ulong slot_i=0UL; slot_i<writer->slot_cnt; slot_i++ ) {
     309           0 :     writer->group_cnt_by_slot_i[ slot_i ] = 0UL;
     310           0 :     for( ulong blockhash_i=0UL; blockhash_i<writer->blockhash_cnt; blockhash_i++ ) {
     311           0 :       ulong key             = group_key( slot_i, blockhash_i );
     312           0 :       uint  group_entry_cnt = writer->entry_cnt_by_key[ key ];
     313           0 :       if( !group_entry_cnt ) continue;
     314           0 :       fd_txncache_writer_group_t * group = &writer->groups[ group_cnt ];
     315           0 :       group->slot_i           = (ushort)slot_i;
     316           0 :       group->blockhash_i      = (ushort)blockhash_i;
     317           0 :       group->entry_cnt        = group_entry_cnt;
     318           0 :       group->arena_entry_off  = 0U;
     319           0 :       group->filled_entry_cnt = 0U;
     320           0 :       writer->group_i_by_key[ key ] = (uint)group_cnt;
     321           0 :       writer->group_cnt_by_slot_i[ slot_i ]++;
     322           0 :       group_cnt++;
     323           0 :       entry_cnt += group_entry_cnt;
     324           0 :     }
     325           0 :   }
     326           0 :   writer->group_cnt = group_cnt;
     327           0 :   writer->entry_cnt = entry_cnt;
     328           0 : }
     329             : 
     330             : /* Writes groups [group_i,batch_hi) into the arena, with batch_hi as far
     331             :    as the arena reaches.  Walks each rooted blockcache that has a group
     332             :    in the batch once. */
     333             : static void
     334           0 : writer_fill_batch( fd_txncache_writer_t * writer ) {
     335           0 :   ulong group_i0 = writer->group_i;
     336           0 :   fd_txncache_writer_group_t * first = &writer->groups[ group_i0 ];
     337           0 :   if( FD_UNLIKELY( (ulong)first->entry_cnt>writer->arena_entry_cnt_max ) ) {
     338           0 :     FD_BASE58_ENCODE_32_BYTES( writer->blockhash_descs[ first->blockhash_i ].blockhash, blockhash_b58 );
     339           0 :     FD_LOG_CRIT(( "status cache group of slot %lu under blockhash %s holds %u transactions, exceeding the %lu entry arena", writer->slots[ first->slot_i ], blockhash_b58, first->entry_cnt, writer->arena_entry_cnt_max ));
     340           0 :   }
     341             : 
     342           0 :   uchar blockhash_in_batch[ FD_TXNCACHE_WRITER_MAX_BLOCKHASHES ] = {0};
     343           0 :   ulong arena_entry_off = 0UL;
     344           0 :   ulong batch_hi        = group_i0;
     345           0 :   while( batch_hi<writer->group_cnt && arena_entry_off+(ulong)writer->groups[ batch_hi ].entry_cnt<=writer->arena_entry_cnt_max ) {
     346           0 :     fd_txncache_writer_group_t * group = &writer->groups[ batch_hi ];
     347           0 :     group->arena_entry_off  = (uint)arena_entry_off;
     348           0 :     group->filled_entry_cnt = 0U;
     349           0 :     blockhash_in_batch[ group->blockhash_i ] = 1;
     350           0 :     arena_entry_off += group->entry_cnt;
     351           0 :     batch_hi++;
     352           0 :   }
     353           0 :   writer->batch_hi = batch_hi;
     354             : 
     355           0 :   for( ulong blockhash_i=0UL; blockhash_i<writer->blockhash_cnt; blockhash_i++ ) {
     356           0 :     if( blockhash_in_batch[ blockhash_i ] ) writer_walk_blockhash( writer, blockhash_i, WALK_FILL );
     357           0 :   }
     358             : 
     359           0 :   for( ulong group_i=group_i0; group_i<batch_hi; group_i++ ) {
     360           0 :     fd_txncache_writer_group_t const * group = &writer->groups[ group_i ];
     361           0 :     if( FD_UNLIKELY( group->filled_entry_cnt!=group->entry_cnt ) ) {
     362           0 :       FD_LOG_CRIT(( "txncache group %u of slot %lu has %u transactions, expected %u while snapshot of slot %lu is in progress", group->blockhash_i, writer->slots[ group->slot_i ], group->filled_entry_cnt, group->entry_cnt, writer->snapshot_slot ));
     363           0 :     }
     364           0 :   }
     365           0 : }
     366             : 
     367             : fd_txncache_writer_t *
     368             : fd_txncache_writer_init( fd_txncache_writer_t * writer,
     369             :                          fd_txncache_t *        tc,
     370             :                          fd_txncache_fork_id_t  fork_id,
     371             :                          ulong                  snapshot_slot,
     372             :                          uchar const *          slot_history,
     373             :                          ulong                  slot_history_sz,
     374             :                          void *                 arena,
     375           0 :                          ulong                  arena_sz ) {
     376           0 :   fd_rwlock_t * lock = tc->shmem->lock;
     377             : 
     378           0 :   if( FD_UNLIKELY( !arena || !fd_ulong_is_aligned( (ulong)arena, fd_txncache_writer_arena_align() ) || arena_sz<2UL*2UL*sizeof(fd_txnhash_t) ) ) {
     379           0 :     FD_LOG_CRIT(( "invalid txncache writer arena (%p, %lu bytes)", arena, arena_sz ));
     380           0 :   }
     381             : 
     382           0 :   ulong arena_entry_cnt_max = arena_sz/sizeof(fd_txnhash_t);
     383           0 :   FD_CHECK_CRIT( arena_entry_cnt_max<=UINT_MAX, "txncache writer arena has too many entries" ); /* arena_entry_off is uint */
     384             : 
     385           0 :   writer->state               = STATE_HEADER;
     386           0 :   writer->tc                  = tc;
     387           0 :   writer->snapshot_slot       = snapshot_slot;
     388           0 :   writer->arena               = arena;
     389           0 :   writer->arena_entry_cnt_max = arena_entry_cnt_max;
     390           0 :   writer->batch_hi            = 0UL;
     391           0 :   writer->slot_i              = 0UL;
     392           0 :   writer->group_i             = 0UL;
     393           0 :   writer->entry_i             = 0UL;
     394             : 
     395           0 :   writer_slots_init( writer, slot_history, slot_history_sz );
     396             : 
     397             :   /* Snapshot the root list.  The snapshot root must be the newest root. */
     398             : 
     399           0 :   fd_rwlock_read( lock );
     400           0 :   if( FD_UNLIKELY( fork_id.val>=tc->shmem->active_slots_max                                               ||
     401           0 :                    root_slist_is_empty( tc->shmem->root_ll, tc->blockcache_shmem_pool )                   ||
     402           0 :                    root_slist_idx_peek_tail( tc->shmem->root_ll, tc->blockcache_shmem_pool )!=fork_id.val ||
     403           0 :                    tc->blockcache_shmem_pool[ fork_id.val ].frozen!=2 ) ) {
     404           0 :     fd_rwlock_unread( lock );
     405           0 :     return NULL;
     406           0 :   }
     407           0 :   writer->root_gen = __atomic_load_n( &tc->shmem->root_gen, __ATOMIC_RELAXED );
     408             : 
     409           0 :   ulong blockhash_cnt = 0UL;
     410           0 :   for( ulong it = root_slist_iter_init( tc->shmem->root_ll, tc->blockcache_shmem_pool );
     411           0 :        !root_slist_iter_done( it, tc->shmem->root_ll, tc->blockcache_shmem_pool );
     412           0 :        it = root_slist_iter_next( it, tc->shmem->root_ll, tc->blockcache_shmem_pool ) ) {
     413           0 :     ulong blockcache_idx = root_slist_iter_idx( it, tc->shmem->root_ll, tc->blockcache_shmem_pool );
     414           0 :     if( FD_UNLIKELY( blockhash_cnt>=FD_TXNCACHE_WRITER_MAX_BLOCKHASHES ) ) {
     415           0 :       FD_LOG_CRIT(( "txncache has more than %lu roots", FD_TXNCACHE_WRITER_MAX_BLOCKHASHES ));
     416           0 :     }
     417           0 :     fd_txncache_blockcache_shmem_t const * blockcache_shmem = &tc->blockcache_shmem_pool[ blockcache_idx ];
     418           0 :     fd_txncache_writer_blockhash_desc_t * blockhash_desc = &writer->blockhash_descs[ blockhash_cnt ];
     419           0 :     blockhash_desc->blockcache_idx = blockcache_idx;
     420           0 :     blockhash_desc->generation     = blockcache_shmem->generation;
     421           0 :     blockhash_desc->txnhash_offset = blockcache_shmem->txnhash_offset;
     422           0 :     memcpy( blockhash_desc->blockhash, blockcache_shmem->blockhash.uc, 32UL );
     423           0 :     blockhash_cnt++;
     424           0 :   }
     425           0 :   fd_rwlock_unread( lock );
     426           0 :   writer->blockhash_cnt = blockhash_cnt;
     427             : 
     428           0 :   if( FD_UNLIKELY( !writer_map_execution_slots( writer ) ) ) return NULL;
     429           0 :   writer_groups_init( writer );
     430             : 
     431           0 :   return writer;
     432           0 : }
     433             : 
     434             : FD_FN_PURE ulong
     435           0 : fd_txncache_writer_serialized_sz( fd_txncache_writer_t const * writer ) {
     436           0 :   return HEADER_SZ+writer->slot_cnt*SLOT_DELTA_SZ+writer->group_cnt*GROUP_HDR_SZ+writer->entry_cnt*TXN_SZ;
     437           0 : }
     438             : 
     439             : ulong
     440             : fd_txncache_writer_serialize( fd_txncache_writer_t * writer,
     441             :                               uchar *                out_buf,
     442           0 :                               ulong                  buf_sz ) {
     443           0 :   if( FD_UNLIKELY( buf_sz<FD_TXNCACHE_WRITER_BUF_MIN ) ) {
     444           0 :     FD_LOG_CRIT(( "buffer too small (%lu bytes, need at least %lu)", buf_sz, FD_TXNCACHE_WRITER_BUF_MIN ));
     445           0 :   }
     446           0 :   uchar * p  = out_buf;
     447           0 :   uchar * p1 = out_buf+buf_sz;
     448             : 
     449           0 :   for(;;) {
     450           0 :     switch( writer->state ) {
     451             : 
     452           0 :     case STATE_HEADER: {
     453           0 :       if( FD_UNLIKELY( p+HEADER_SZ>p1 ) ) goto done;
     454           0 :       FD_STORE( ulong, p, writer->slot_cnt ); p += 8UL; /* slot_deltas_len */
     455           0 :       writer->slot_i  = 0UL;
     456           0 :       writer->group_i = 0UL;
     457           0 :       writer->state   = writer->slot_cnt ? STATE_SLOT : STATE_DONE;
     458           0 :       break;
     459           0 :     }
     460             : 
     461           0 :     case STATE_SLOT: {
     462           0 :       if( FD_UNLIKELY( p+SLOT_DELTA_SZ>p1 ) ) goto done;
     463           0 :       ulong slot_i = writer->slot_i;
     464           0 :       FD_STORE( ulong, p, writer->slots[ slot_i ]               ); p += 8UL; /* slot       */
     465           0 :       FD_STORE( uchar, p, 1                                     ); p += 1UL; /* is_root    */
     466           0 :       FD_STORE( ulong, p, writer->group_cnt_by_slot_i[ slot_i ] ); p += 8UL; /* status_len */
     467           0 :       if( writer->group_cnt_by_slot_i[ slot_i ] ) {
     468           0 :         writer->state = STATE_GROUP;
     469           0 :       } else {
     470           0 :         writer->slot_i++;
     471           0 :         if( writer->slot_i>=writer->slot_cnt ) writer->state = STATE_DONE;
     472           0 :       }
     473           0 :       break;
     474           0 :     }
     475             : 
     476           0 :     case STATE_GROUP: {
     477           0 :       fd_txncache_writer_group_t const * group = &writer->groups[ writer->group_i ];
     478           0 :       FD_TEST( group->slot_i==writer->slot_i );
     479           0 :       if( FD_UNLIKELY( writer->group_i>=writer->batch_hi ) ) writer_fill_batch( writer );
     480           0 :       if( FD_UNLIKELY( p+GROUP_HDR_SZ>p1 ) ) goto done;
     481           0 :       fd_txncache_writer_blockhash_desc_t const * blockhash_desc = &writer->blockhash_descs[ group->blockhash_i ];
     482           0 :       memcpy( p, blockhash_desc->blockhash, 32UL );          p += 32UL;
     483           0 :       FD_STORE( ulong, p, blockhash_desc->txnhash_offset );  p +=  8UL;
     484           0 :       FD_STORE( ulong, p, (ulong)group->entry_cnt         ); p +=  8UL;
     485           0 :       writer->entry_i = 0UL;
     486           0 :       writer->state   = STATE_ENTRIES;
     487           0 :       break;
     488           0 :     }
     489             : 
     490           0 :     case STATE_ENTRIES: {
     491           0 :       fd_txncache_writer_group_t const * group = &writer->groups[ writer->group_i ];
     492           0 :       while( writer->entry_i<group->entry_cnt ) {
     493           0 :         if( FD_UNLIKELY( p+TXN_SZ>p1 ) ) goto done;
     494           0 :         memcpy( p, writer->arena[ group->arena_entry_off+writer->entry_i ], sizeof(writer->arena[0]) ); p += sizeof(writer->arena[0]);
     495           0 :         FD_STORE( uint, p, 0U );                                                                        p +=  4UL; /* result = Ok */
     496           0 :         writer->entry_i++;
     497           0 :       }
     498           0 :       writer->group_i++;
     499           0 :       if( writer->group_i<writer->group_cnt && writer->groups[ writer->group_i ].slot_i==writer->slot_i ) {
     500           0 :         writer->state = STATE_GROUP;
     501           0 :       } else {
     502           0 :         writer->slot_i++;
     503           0 :         writer->state = writer->slot_i<writer->slot_cnt ? STATE_SLOT : STATE_DONE;
     504           0 :       }
     505           0 :       break;
     506           0 :     }
     507             : 
     508           0 :     case STATE_DONE:
     509           0 :       goto done;
     510             : 
     511           0 :     default:
     512           0 :       FD_LOG_CRIT(( "invalid state reached (%u)", writer->state ));
     513           0 :     }
     514           0 :   }
     515             : 
     516           0 : done:
     517           0 :   return (ulong)( p-out_buf );
     518           0 : }

Generated by: LCOV version 1.14