LCOV - code coverage report
Current view: top level - flamenco/runtime - fd_txncache.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 245 404 60.6 %
Date: 2026-09-06 04:28:15 Functions: 17 22 77.3 %

          Line data    Source code
       1             : #include "fd_txncache.h"
       2             : #include "fd_txncache_private.h"
       3             : #include "../../util/log/fd_log.h"
       4             : 
       5             : struct blockcache {
       6             :   fd_txncache_blockcache_shmem_t * shmem;
       7             : 
       8             :   uint * heads;          /* The hash table for the blockhash.  Each entry is a pointer to the head of a linked list of
       9             :                             transactions that reference this blockhash.  As we add transactions to the bucket, the head
      10             :                             pointer is updated to the new item, and the new item is pointed to the previous head. */
      11             :   ushort * pages;        /* A list of the txnpages containing the transactions for this blockcache. */
      12             : 
      13             :   descends_set_t * descends; /* Each fork can descend from other forks in the txncache, and this bit vector contains one
      14             :                                 value for each fork in the txncache.  If this fork descends from some other fork F, then
      15             :                                 the bit at index F in descends[] is set. */
      16             : };
      17             : 
      18             : typedef struct blockcache blockcache_t;
      19             : 
      20             : struct fd_txncache_private {
      21             :   fd_txncache_shmem_t * shmem;
      22             : 
      23             :   fd_txncache_blockcache_shmem_t * blockcache_shmem_pool;
      24             :   blockcache_t * blockcache_pool;
      25             :   blockhash_map_t * blockhash_map;
      26             : 
      27             :   ushort * txnpages_free;           /* The index in the txnpages array that is free, for each of the free pages. */
      28             : 
      29             :   fd_txncache_txnpage_t * txnpages; /* The actual storage for the transactions.  The blockcache points to these
      30             :                                        pages when storing transactions.  Transaction are grouped into pages of
      31             :                                        size 16384 to make certain allocation and deallocation operations faster
      32             :                                        (just the pages are acquired/released, rather than each txn). */
      33             : 
      34             :   ushort * scratch_pages;
      35             :   uint * scratch_heads;
      36             :   fd_txncache_txnpage_t * scratch_txnpage;
      37             : };
      38             : 
      39             : FD_FN_CONST ulong
      40         279 : fd_txncache_align( void ) {
      41         279 :   return FD_TXNCACHE_ALIGN;
      42         279 : }
      43             : 
      44             : FD_FN_CONST ulong
      45         132 : fd_txncache_footprint( ulong max_live_slots ) {
      46         132 :   ulong max_active_slots = FD_TXNCACHE_MAX_BLOCKHASH_DISTANCE+max_live_slots;
      47             : 
      48         132 :   ulong l;
      49         132 :   l = FD_LAYOUT_INIT;
      50         132 :   l = FD_LAYOUT_APPEND( l, FD_TXNCACHE_SHMEM_ALIGN, sizeof(fd_txncache_t) );
      51         132 :   l = FD_LAYOUT_APPEND( l, alignof(blockcache_t),   max_active_slots*sizeof(blockcache_t) );
      52         132 :   return FD_LAYOUT_FINI( l, FD_TXNCACHE_ALIGN );
      53         132 : }
      54             : 
      55             : void *
      56             : fd_txncache_new( void *                ljoin,
      57          72 :                  fd_txncache_shmem_t * shmem ) {
      58          72 :   if( FD_UNLIKELY( !ljoin ) ) {
      59           0 :     FD_LOG_WARNING(( "NULL ljoin" ));
      60           0 :     return NULL;
      61           0 :   }
      62             : 
      63          72 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)ljoin, fd_txncache_align() ) ) ) {
      64           0 :     FD_LOG_WARNING(( "misaligned ljoin" ));
      65           0 :     return NULL;
      66           0 :   }
      67             : 
      68          72 :   ulong max_active_slots = shmem->active_slots_max;
      69          72 :   ulong blockhash_map_chains = fd_ulong_pow2_up( 2UL*shmem->active_slots_max );
      70          72 :   ulong bucket_cnt = shmem->bucket_cnt;
      71             : 
      72             :   /* Page counts come from the shmem header rather than being
      73             :      re-derived, so the layout walk below cannot desync from the one in
      74             :      fd_txncache_shmem_new. */
      75          72 :   ushort _max_txnpages               = shmem->max_txnpages;
      76          72 :   ushort _max_txnpages_per_blockhash = shmem->txnpages_per_blockhash_max;
      77             : 
      78          72 :   ulong _descends_footprint = descends_set_footprint( max_active_slots );
      79          72 :   if( FD_UNLIKELY( !_descends_footprint ) ) {
      80           0 :     FD_LOG_WARNING(( "invalid max_active_slots" ));
      81           0 :     return NULL;
      82           0 :   }
      83             : 
      84          72 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
      85          72 :   fd_txncache_shmem_t * tc    = FD_SCRATCH_ALLOC_APPEND( l, FD_TXNCACHE_SHMEM_ALIGN,         sizeof(fd_txncache_shmem_t)                                 );
      86          72 :   void * _blockhash_map       = FD_SCRATCH_ALLOC_APPEND( l, blockhash_map_align(),           blockhash_map_footprint( blockhash_map_chains )             );
      87          72 :   void * _blockcache_pool     = FD_SCRATCH_ALLOC_APPEND( l, blockcache_pool_align(),         blockcache_pool_footprint( max_active_slots )               );
      88          72 :   void * _blockcache_pages    = FD_SCRATCH_ALLOC_APPEND( l, alignof(ushort),                 max_active_slots*_max_txnpages_per_blockhash*sizeof(ushort) );
      89          72 :   void * _blockcache_heads    = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),                   max_active_slots*bucket_cnt*sizeof(uint)                    );
      90          72 :   void * _blockcache_descends = FD_SCRATCH_ALLOC_APPEND( l, descends_set_align(),            max_active_slots*_descends_footprint                        );
      91          72 :   void * _txnpages_free       = FD_SCRATCH_ALLOC_APPEND( l, alignof(ushort),                 _max_txnpages*sizeof(ushort)                                );
      92          72 :   void * _txnpages            = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txncache_txnpage_t),  _max_txnpages*sizeof(fd_txncache_txnpage_t)                 );
      93          72 :   void * _scratch_pages       = FD_SCRATCH_ALLOC_APPEND( l, alignof(ushort),                 _max_txnpages_per_blockhash*sizeof(ushort)                  );
      94          72 :   void * _scratch_heads       = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),                   bucket_cnt*sizeof(uint)                                     );
      95          72 :   void * _scratch_txnpage     = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txncache_txnpage_t),  sizeof(fd_txncache_txnpage_t)                               );
      96             : 
      97          72 :   FD_SCRATCH_ALLOC_INIT( l2, ljoin );
      98          72 :   fd_txncache_t * ltc           = FD_SCRATCH_ALLOC_APPEND( l2, FD_TXNCACHE_ALIGN,     sizeof(fd_txncache_t)                 );
      99          72 :   void * _local_blockcache_pool = FD_SCRATCH_ALLOC_APPEND( l2, alignof(blockcache_t), max_active_slots*sizeof(blockcache_t) );
     100             : 
     101          72 :   ltc->shmem = tc;
     102             : 
     103          72 :   ltc->blockcache_pool = (blockcache_t*)_local_blockcache_pool;
     104          72 :   ltc->blockcache_shmem_pool = blockcache_pool_join( _blockcache_pool );
     105             : 
     106       12060 :   for( ulong i=0UL; i<shmem->active_slots_max; i++ ) {
     107       11988 :     ltc->blockcache_pool[ i ].pages    = (ushort *)_blockcache_pages + i*_max_txnpages_per_blockhash;
     108       11988 :     ltc->blockcache_pool[ i ].heads    = (uint *)_blockcache_heads + i*bucket_cnt;
     109       11988 :     ltc->blockcache_pool[ i ].descends = descends_set_join( (uchar *)_blockcache_descends + i*_descends_footprint );
     110       11988 :     ltc->blockcache_pool[ i ].shmem    = ltc->blockcache_shmem_pool + i;
     111       11988 :     FD_TEST( ltc->blockcache_pool[ i ].shmem );
     112       11988 :   }
     113             : 
     114          72 :   FD_TEST( ltc->blockcache_shmem_pool );
     115             : 
     116          72 :   ltc->blockhash_map = blockhash_map_join( _blockhash_map );
     117          72 :   FD_TEST( ltc->blockhash_map );
     118             : 
     119          72 :   ltc->txnpages_free = (ushort *)_txnpages_free;
     120          72 :   ltc->txnpages      = (fd_txncache_txnpage_t *)_txnpages;
     121             : 
     122          72 :   ltc->scratch_pages   = _scratch_pages;
     123          72 :   ltc->scratch_heads   = _scratch_heads;
     124          72 :   ltc->scratch_txnpage = _scratch_txnpage;
     125             : 
     126          72 :   return (void *)ltc;
     127          72 : }
     128             : 
     129             : fd_txncache_t *
     130          72 : fd_txncache_join( void * ljoin ) {
     131          72 :   if( FD_UNLIKELY( !ljoin ) ) {
     132           0 :     FD_LOG_WARNING(( "NULL ljoin" ));
     133           0 :     return NULL;
     134           0 :   }
     135             : 
     136          72 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)ljoin, fd_txncache_align() ) ) ) {
     137           0 :     FD_LOG_WARNING(( "misaligned ljoin" ));
     138           0 :     return NULL;
     139           0 :   }
     140             : 
     141          72 :   fd_txncache_t * tc = (fd_txncache_t *)ljoin;
     142             : 
     143          72 :   return tc;
     144          72 : }
     145             : 
     146             : void
     147        3945 : fd_txncache_reset( fd_txncache_t * tc ) {
     148        3945 :   fd_rwlock_write( tc->shmem->lock );
     149             : 
     150        3945 :   tc->shmem->root_cnt = 0UL;
     151        3945 :   root_slist_remove_all( tc->shmem->root_ll, tc->blockcache_shmem_pool );
     152             : 
     153        3945 :   tc->shmem->txnpages_free_cnt = tc->shmem->max_txnpages;
     154      690804 :   for( ushort i=0; i<tc->shmem->max_txnpages; i++ ) tc->txnpages_free[ i ] = i;
     155             : 
     156        3945 :   blockcache_pool_reset( tc->blockcache_shmem_pool );
     157        3945 :   blockhash_map_reset( tc->blockhash_map );
     158             : 
     159        3945 :   fd_rwlock_unwrite( tc->shmem->lock );
     160        3945 : }
     161             : 
     162             : void *
     163             : fd_txncache_snapin_scratch( fd_txncache_t * tc,
     164           6 :                             ulong *         out_sz ) {
     165           6 :   FD_TEST_ERR( tc->shmem->txnpages_free_cnt==tc->shmem->max_txnpages );
     166           6 :   *out_sz = (ulong)tc->shmem->max_txnpages*sizeof(fd_txncache_txnpage_t);
     167           6 :   return tc->txnpages;
     168           6 : }
     169             : 
     170             : FD_FN_PURE static inline ulong
     171             : fd_txncache_bucket( fd_txncache_t const * tc,
     172         663 :                     uchar const *         txnhash ) {
     173         663 :   return fd_ulong_hash( FD_LOAD( ulong, txnhash )^tc->shmem->seed )%tc->shmem->bucket_cnt;
     174         663 : }
     175             : 
     176             : static fd_txncache_txnpage_t *
     177             : fd_txncache_ensure_txnpage( fd_txncache_t * tc,
     178         273 :                             blockcache_t *  blockcache ) {
     179         273 :   ushort page_cnt = blockcache->shmem->pages_cnt;
     180         273 :   if( FD_UNLIKELY( page_cnt>tc->shmem->txnpages_per_blockhash_max ) ) return NULL;
     181             : 
     182         273 :   if( FD_LIKELY( page_cnt ) ) {
     183         138 :     ushort txnpage_idx = blockcache->pages[ page_cnt-1 ];
     184         138 :     ushort txnpage_free = tc->txnpages[ txnpage_idx ].free;
     185         138 :     if( FD_LIKELY( txnpage_free ) ) return &tc->txnpages[ txnpage_idx ];
     186         138 :   }
     187             : 
     188         135 :   if( FD_UNLIKELY( page_cnt==tc->shmem->txnpages_per_blockhash_max ) ) return NULL;
     189         135 :   if( FD_LIKELY( FD_ATOMIC_CAS( &blockcache->pages[ page_cnt ], (ushort)USHORT_MAX, (ushort)(USHORT_MAX-1UL) )==(ushort)USHORT_MAX ) ) {
     190         135 :     ulong txnpages_free_cnt = tc->shmem->txnpages_free_cnt;
     191         135 :     for(;;) {
     192         135 :       if( FD_UNLIKELY( !txnpages_free_cnt ) ) {
     193           0 :         blockcache->pages[ page_cnt ] = (ushort)USHORT_MAX;
     194           0 :         FD_COMPILER_MFENCE();
     195           0 :         return NULL;
     196           0 :       }
     197         135 :       ulong old_txnpages_free_cnt = FD_ATOMIC_CAS( &tc->shmem->txnpages_free_cnt, (ushort)txnpages_free_cnt, (ushort)(txnpages_free_cnt-1UL) );
     198         135 :       if( FD_LIKELY( old_txnpages_free_cnt==txnpages_free_cnt ) ) break;
     199           0 :       txnpages_free_cnt = old_txnpages_free_cnt;
     200           0 :       FD_SPIN_PAUSE();
     201           0 :     }
     202             : 
     203         135 :     ushort txnpage_idx = tc->txnpages_free[ txnpages_free_cnt-1UL ];
     204         135 :     fd_txncache_txnpage_t * txnpage = &tc->txnpages[ txnpage_idx ];
     205         135 :     txnpage->free = FD_TXNCACHE_TXNS_PER_PAGE;
     206         135 :     FD_COMPILER_MFENCE();
     207         135 :     blockcache->pages[ page_cnt ] = txnpage_idx;
     208         135 :     FD_COMPILER_MFENCE();
     209         135 :     blockcache->shmem->pages_cnt = (ushort)(page_cnt+1);
     210         135 :     return txnpage;
     211         135 :   } else {
     212           0 :     ushort txnpage_idx = blockcache->pages[ page_cnt ];
     213           0 :     while( FD_UNLIKELY( txnpage_idx==(ushort)(USHORT_MAX-1UL) ) ) {
     214           0 :       txnpage_idx = blockcache->pages[ page_cnt ];
     215           0 :       FD_SPIN_PAUSE();
     216           0 :     }
     217           0 :     if( FD_UNLIKELY( txnpage_idx==(ushort)USHORT_MAX ) ) return NULL;
     218           0 :     return &tc->txnpages[ txnpage_idx ];
     219           0 :   }
     220         135 : }
     221             : 
     222             : static int
     223             : fd_txncache_insert_txn( fd_txncache_t *         tc,
     224             :                         blockcache_t *          blockcache,
     225             :                         fd_txncache_txnpage_t * txnpage,
     226             :                         fd_txncache_fork_id_t   fork_id,
     227         273 :                         uchar const *           txnhash ) {
     228         273 :   ulong txnpage_idx = (ulong)(txnpage - tc->txnpages);
     229             : 
     230         273 :   for(;;) {
     231         273 :     ushort txnpage_free = txnpage->free;
     232         273 :     if( FD_UNLIKELY( !txnpage_free ) ) return 0;
     233         273 :     if( FD_UNLIKELY( FD_ATOMIC_CAS( &txnpage->free, txnpage_free, txnpage_free-1UL )!=txnpage_free ) ) {
     234           0 :       FD_SPIN_PAUSE();
     235           0 :       continue;
     236           0 :     }
     237             : 
     238         273 :     ulong txn_idx = FD_TXNCACHE_TXNS_PER_PAGE-txnpage_free;
     239         273 :     ulong txnhash_offset = blockcache->shmem->txnhash_offset;
     240         273 :     memcpy( txnpage->txns[ txn_idx ]->txnhash, txnhash+txnhash_offset, 20UL );
     241         273 :     txnpage->txns[ txn_idx ]->fork_id = fork_id;
     242         273 :     txnpage->txns[ txn_idx ]->generation = tc->blockcache_pool[ fork_id.val ].shmem->generation;
     243         273 :     FD_COMPILER_MFENCE();
     244             : 
     245         273 :     ulong txn_bucket = fd_txncache_bucket( tc, txnhash+txnhash_offset );
     246         273 :     for(;;) {
     247         273 :       uint head = blockcache->heads[ txn_bucket ];
     248         273 :       txnpage->txns[ txn_idx ]->blockcache_next = head;
     249         273 :       FD_COMPILER_MFENCE();
     250         273 :       if( FD_LIKELY( FD_ATOMIC_CAS( &blockcache->heads[ txn_bucket ], head, (uint)(FD_TXNCACHE_TXNS_PER_PAGE*txnpage_idx+txn_idx) )==head ) ) break;
     251           0 :       FD_SPIN_PAUSE();
     252           0 :     }
     253             : 
     254         273 :     return 1;
     255         273 :   }
     256         273 : }
     257             : 
     258             : fd_txncache_fork_id_t
     259             : fd_txncache_attach_child( fd_txncache_t *       tc,
     260        8094 :                           fd_txncache_fork_id_t parent_fork_id ) {
     261        8094 :   fd_rwlock_write( tc->shmem->lock );
     262             : 
     263        8094 :   FD_TEST( blockcache_pool_free( tc->blockcache_shmem_pool ) );
     264        8094 :   ulong idx = blockcache_pool_idx_acquire( tc->blockcache_shmem_pool );
     265             : 
     266        8094 :   blockcache_t * fork = &tc->blockcache_pool[ idx ];
     267        8094 :   fd_txncache_fork_id_t fork_id = { .val = (ushort)idx };
     268             : 
     269        8094 :   fork->shmem->generation = tc->shmem->blockcache_generation++;
     270        8094 :   fork->shmem->child_id = (fd_txncache_fork_id_t){ .val = USHORT_MAX };
     271             : 
     272        8094 :   if( FD_LIKELY( parent_fork_id.val==USHORT_MAX ) ) {
     273        3951 :     FD_TEST( blockcache_pool_free( tc->blockcache_shmem_pool )==blockcache_pool_max( tc->blockcache_shmem_pool )-1UL );
     274        3951 :     fork->shmem->parent_id  = (fd_txncache_fork_id_t){ .val = USHORT_MAX };
     275        3951 :     fork->shmem->sibling_id = (fd_txncache_fork_id_t){ .val = USHORT_MAX };
     276             : 
     277        3951 :     descends_set_null( fork->descends );
     278        3951 :     root_slist_ele_push_tail( tc->shmem->root_ll, fork->shmem, tc->blockcache_shmem_pool );
     279        4143 :   } else {
     280        4143 :     blockcache_t * parent = &tc->blockcache_pool[ parent_fork_id.val ];
     281             :     /* We might be tempted to freeze the parent here, and it's valid to
     282             :        do this ordinarily, but not when loading from a snapshot, when
     283             :        we need to load many transactions into a root parent chain at
     284             :        once. */
     285        4143 :     fork->shmem->sibling_id = parent->shmem->child_id;
     286        4143 :     fork->shmem->parent_id  = parent_fork_id;
     287        4143 :     parent->shmem->child_id = fork_id;
     288             : 
     289        4143 :     descends_set_copy( fork->descends, parent->descends );
     290        4143 :     descends_set_insert( fork->descends, parent_fork_id.val );
     291        4143 :   }
     292             : 
     293        8094 :   fork->shmem->txnhash_offset = 0UL;
     294        8094 :   fork->shmem->frozen = 0;
     295        8094 :   memset( fork->heads, 0xFF, tc->shmem->bucket_cnt*sizeof(uint) );
     296        8094 :   fork->shmem->pages_cnt = 0;
     297        8094 :   memset( fork->pages, 0xFF, tc->shmem->txnpages_per_blockhash_max*sizeof(fork->pages[ 0 ]) );
     298             : 
     299        8094 :   fd_rwlock_unwrite( tc->shmem->lock );
     300        8094 :   return fork_id;
     301        8094 : }
     302             : 
     303             : void
     304             : fd_txncache_attach_blockhash( fd_txncache_t *       tc,
     305             :                               fd_txncache_fork_id_t fork_id,
     306           9 :                               uchar const *         blockhash ) {
     307           9 :   fd_rwlock_write( tc->shmem->lock );
     308             : 
     309           9 :   blockcache_t * fork = &tc->blockcache_pool[ fork_id.val ];
     310           9 :   FD_TEST( !fork->shmem->frozen );
     311           9 :   fork->shmem->frozen = 1;
     312             : 
     313           9 :   memcpy( fork->shmem->blockhash.uc, blockhash, 32UL );
     314             : 
     315           9 :   blockhash_map_ele_insert( tc->blockhash_map, fork->shmem, tc->blockcache_shmem_pool );
     316             : 
     317           9 :   fd_rwlock_unwrite( tc->shmem->lock );
     318           9 : }
     319             : 
     320             : void
     321             : fd_txncache_finalize_fork( fd_txncache_t *       tc,
     322             :                            fd_txncache_fork_id_t fork_id,
     323             :                            ulong                 txnhash_offset,
     324        4350 :                            uchar const *         blockhash ) {
     325        4350 :   fd_rwlock_write( tc->shmem->lock );
     326             : 
     327        4350 :   blockcache_t * fork = &tc->blockcache_pool[ fork_id.val ];
     328        4350 :   FD_TEST( fork->shmem->frozen<=1 );
     329        4350 :   FD_TEST( fork->shmem->frozen>=0 );
     330        4350 :   fork->shmem->txnhash_offset = txnhash_offset;
     331             : 
     332        4350 :   memcpy( fork->shmem->blockhash.uc, blockhash, 32UL );
     333             : 
     334        4350 :   if( FD_LIKELY( !fork->shmem->frozen ) ) blockhash_map_ele_insert( tc->blockhash_map, fork->shmem, tc->blockcache_shmem_pool );
     335        4350 :   fork->shmem->frozen = 2;
     336             : 
     337        4350 :   fd_rwlock_unwrite( tc->shmem->lock );
     338        4350 : }
     339             : 
     340             : static inline void
     341             : remove_blockcache( fd_txncache_t * tc,
     342           0 :                    blockcache_t *  blockcache ) {
     343           0 :   FD_TEST( blockcache->shmem->frozen>=0 );
     344           0 :   memcpy( tc->txnpages_free+tc->shmem->txnpages_free_cnt, blockcache->pages, blockcache->shmem->pages_cnt*sizeof(tc->txnpages_free[ 0 ]) );
     345           0 :   tc->shmem->txnpages_free_cnt = (ushort)(tc->shmem->txnpages_free_cnt+blockcache->shmem->pages_cnt);
     346             : 
     347           0 :   ulong idx = blockcache_pool_idx( tc->blockcache_shmem_pool, blockcache->shmem );
     348           0 :   for( ulong i=0UL; i<tc->shmem->active_slots_max; i++ ) descends_set_remove( tc->blockcache_pool[ i ].descends, idx );
     349             : 
     350           0 :   if( FD_LIKELY( blockcache->shmem->frozen ) ) blockhash_map_ele_remove_fast( tc->blockhash_map, blockcache->shmem, tc->blockcache_shmem_pool );
     351           0 :   blockcache->shmem->frozen = -1;
     352           0 :   blockcache_pool_ele_release( tc->blockcache_shmem_pool, blockcache->shmem );
     353           0 : }
     354             : 
     355             : static inline void
     356             : remove_children( fd_txncache_t *      tc,
     357             :                  blockcache_t const * fork,
     358          99 :                  blockcache_t const * except ) {
     359          99 :   fd_txncache_fork_id_t sibling_idx = fork->shmem->child_id;
     360         198 :   while( sibling_idx.val!=USHORT_MAX ) {
     361          99 :     blockcache_t * sibling = &tc->blockcache_pool[ sibling_idx.val ];
     362             : 
     363          99 :     sibling_idx = sibling->shmem->sibling_id;
     364          99 :     if( FD_UNLIKELY( sibling==except ) ) continue;
     365             : 
     366           0 :     remove_children( tc, sibling, except );
     367           0 :     remove_blockcache( tc, sibling );
     368           0 :   }
     369          99 : }
     370             : 
     371             : void
     372             : fd_txncache_cancel_fork( fd_txncache_t *       tc,
     373           0 :                          fd_txncache_fork_id_t fork_id ) {
     374           0 :   fd_rwlock_write( tc->shmem->lock );
     375           0 :   blockcache_t * fork = &tc->blockcache_pool[ fork_id.val ];
     376           0 :   FD_TEST( fork->shmem->parent_id.val!=USHORT_MAX );
     377             : 
     378             :   /* The soon-to-be-pruned subtree must be unrooted. */
     379           0 :   fd_txncache_blockcache_shmem_t const * latest_root = root_slist_ele_peek_tail_const( tc->shmem->root_ll, tc->blockcache_shmem_pool );
     380           0 :   FD_TEST( latest_root );
     381           0 :   FD_TEST( descends_set_test( fork->descends, blockcache_pool_idx( tc->blockcache_shmem_pool, latest_root ) ) );
     382             : 
     383           0 :   remove_children( tc, fork, NULL );
     384           0 :   remove_blockcache( tc, fork );
     385           0 :   ushort * fork_id_p = &(tc->blockcache_pool[ fork->shmem->parent_id.val ].shmem->child_id.val);
     386           0 :   while( *fork_id_p!=fork_id.val ) {
     387           0 :     fork_id_p = &(tc->blockcache_pool[ *fork_id_p ].shmem->sibling_id.val);
     388           0 :   }
     389           0 :   *fork_id_p = fork->shmem->sibling_id.val;
     390           0 :   fd_rwlock_unwrite( tc->shmem->lock );
     391           0 : }
     392             : 
     393             : void
     394             : fd_txncache_advance_root( fd_txncache_t *       tc,
     395          99 :                           fd_txncache_fork_id_t fork_id ) {
     396          99 :   fd_rwlock_write( tc->shmem->lock );
     397             : 
     398          99 :   blockcache_t * fork = &tc->blockcache_pool[ fork_id.val ];
     399          99 :   FD_TEST( fork->shmem->parent_id.val!=USHORT_MAX );
     400             : 
     401          99 :   blockcache_t * parent_fork = &tc->blockcache_pool[ fork->shmem->parent_id.val ];
     402          99 :   if( FD_UNLIKELY( root_slist_ele_peek_tail( tc->shmem->root_ll, tc->blockcache_shmem_pool )!=parent_fork->shmem ) ) {
     403           0 :     FD_BASE58_ENCODE_32_BYTES( parent_fork->shmem->blockhash.uc, parent_blockhash_b58 );
     404           0 :     FD_BASE58_ENCODE_32_BYTES( fork->shmem->blockhash.uc, fork_blockhash_b58 );
     405           0 :     FD_BASE58_ENCODE_32_BYTES( root_slist_ele_peek_tail( tc->shmem->root_ll, tc->blockcache_shmem_pool )->blockhash.uc, root_blockhash_b58 );
     406           0 :     FD_LOG_CRIT(( "advancing root from %s to %s but that is not valid, last root is %s",
     407           0 :                   parent_blockhash_b58,
     408           0 :                   fork_blockhash_b58,
     409           0 :                   root_blockhash_b58 ));
     410           0 :   }
     411             : 
     412          99 :   FD_BASE58_ENCODE_32_BYTES( parent_fork->shmem->blockhash.uc, parent_blockhash_b58 );
     413          99 :   FD_BASE58_ENCODE_32_BYTES( fork->shmem->blockhash.uc, fork_blockhash_b58 );
     414          99 :   FD_LOG_DEBUG(( "advancing root from %s to %s",
     415          99 :                  parent_blockhash_b58,
     416          99 :                  fork_blockhash_b58 ));
     417             : 
     418             :   /* When a fork is rooted, any competing forks can be immediately
     419             :      removed as they will not be needed again.  This includes child
     420             :      forks of the pruned siblings as well. */
     421          99 :   remove_children( tc, parent_fork, fork );
     422          99 :   parent_fork->shmem->child_id = fork_id;
     423          99 :   fork->shmem->sibling_id = (fd_txncache_fork_id_t){ .val = USHORT_MAX };
     424             : 
     425             :   /* Now, the earliest known rooted fork can likely be removed since its
     426             :      blockhashes cannot be referenced anymore (they are older than 151
     427             :      blockhashes away). */
     428          99 :   tc->shmem->root_cnt++;
     429          99 :   root_slist_ele_push_tail( tc->shmem->root_ll, fork->shmem, tc->blockcache_shmem_pool );
     430          99 :   if( FD_LIKELY( tc->shmem->root_cnt>FD_TXNCACHE_MAX_BLOCKHASH_DISTANCE ) ) {
     431           0 :     fd_txncache_blockcache_shmem_t * old_root_shmem = root_slist_ele_pop_head( tc->shmem->root_ll, tc->blockcache_shmem_pool );
     432           0 :     FD_TEST( old_root_shmem );
     433           0 :     blockcache_t * old_root = &tc->blockcache_pool[ blockcache_pool_idx( tc->blockcache_shmem_pool, old_root_shmem ) ];
     434             : 
     435           0 :     root_slist_ele_peek_head( tc->shmem->root_ll, tc->blockcache_shmem_pool )->parent_id.val = USHORT_MAX;
     436             : 
     437           0 :     remove_blockcache( tc, old_root );
     438           0 :     tc->shmem->root_cnt--;
     439           0 :   }
     440             : 
     441          99 :   fd_rwlock_unwrite( tc->shmem->lock );
     442          99 : }
     443             : 
     444             : static inline blockcache_t *
     445             : blockhash_on_fork( fd_txncache_t *      tc,
     446             :                    blockcache_t const * fork,
     447         663 :                    uchar const *        blockhash ) {
     448         663 :   fd_txncache_blockcache_shmem_t const * candidate = blockhash_map_ele_query_const( tc->blockhash_map, fd_type_pun_const( blockhash ), NULL, tc->blockcache_shmem_pool );
     449         663 :   if( FD_UNLIKELY( !candidate ) ) return NULL;
     450             : 
     451         663 :   while( candidate ) {
     452         663 :     ulong candidate_idx = blockcache_pool_idx( tc->blockcache_shmem_pool, candidate );
     453         663 :     if( FD_LIKELY( descends_set_test( fork->descends, candidate_idx ) ) ) return &tc->blockcache_pool[ candidate_idx ];
     454           0 :     candidate = blockhash_map_ele_next_const( candidate, NULL, tc->blockcache_shmem_pool );
     455           0 :   }
     456           0 :   return NULL;
     457         663 : }
     458             : 
     459             : static void
     460             : purge_stale_on_blockcache( fd_txncache_t * tc,
     461           0 :                            blockcache_t *  blockcache ) {
     462           0 :   FD_TEST( blockcache->shmem->frozen>=0 );
     463           0 :   memset( tc->scratch_heads, 0xFF, tc->shmem->bucket_cnt*sizeof(tc->scratch_heads[ 0 ]) );
     464           0 :   memset( tc->scratch_pages, 0xFF, tc->shmem->txnpages_per_blockhash_max*sizeof(tc->scratch_pages[ 0 ]) );
     465           0 :   ushort scratch_pages_cnt = 0;
     466           0 :   ushort scratch_txnpage_idx = USHORT_MAX;
     467           0 :   tc->scratch_txnpage->free = 0;
     468           0 :   for( ulong i=0UL; i<blockcache->shmem->pages_cnt; i++ ) {
     469           0 :     ushort curr_txnpage_idx = blockcache->pages[ blockcache->shmem->pages_cnt-i-1UL ];
     470           0 :     ulong curr_txn_cnt = FD_TXNCACHE_TXNS_PER_PAGE-tc->txnpages[ curr_txnpage_idx ].free;
     471           0 :     for( ulong j=0UL; j<curr_txn_cnt; j++ ) {
     472           0 :       fd_txncache_single_txn_t * curr_txn = tc->txnpages[ curr_txnpage_idx ].txns[ curr_txn_cnt-j-1UL ];
     473           0 :       blockcache_t const * txn_fork = &tc->blockcache_pool[ curr_txn->fork_id.val ];
     474           0 :       if( FD_LIKELY( txn_fork->shmem->frozen>=0 && txn_fork->shmem->generation==curr_txn->generation ) ) {
     475             :         /* Valid transaction.  Keep. */
     476           0 :         if( FD_UNLIKELY( !tc->scratch_txnpage->free ) ) {
     477           0 :           FD_TEST( scratch_txnpage_idx!=curr_txnpage_idx );
     478           0 :           if( FD_LIKELY( scratch_txnpage_idx!=USHORT_MAX ) ) {
     479           0 :             fd_txncache_txnpage_t * txnpage = &tc->txnpages[ scratch_txnpage_idx ];
     480           0 :             memcpy( txnpage, tc->scratch_txnpage, sizeof(*txnpage) );
     481           0 :           }
     482           0 :           scratch_txnpage_idx = curr_txnpage_idx;
     483           0 :           tc->scratch_txnpage->free = FD_TXNCACHE_TXNS_PER_PAGE;
     484           0 :           tc->scratch_pages[ scratch_pages_cnt ] = scratch_txnpage_idx;
     485           0 :           scratch_pages_cnt++;
     486           0 :         }
     487           0 :         ulong txn_idx = FD_TXNCACHE_TXNS_PER_PAGE-tc->scratch_txnpage->free;
     488           0 :         memcpy( tc->scratch_txnpage->txns[ txn_idx ], curr_txn, sizeof(*curr_txn) );
     489           0 :         ulong txn_bucket = fd_txncache_bucket( tc, curr_txn->txnhash );
     490           0 :         uint head = tc->scratch_heads[ txn_bucket ];
     491           0 :         tc->scratch_txnpage->txns[ txn_idx ]->blockcache_next = head;
     492           0 :         ulong txn_gidx = FD_TXNCACHE_TXNS_PER_PAGE*scratch_txnpage_idx+txn_idx;
     493           0 :         FD_TEST( txn_gidx<UINT_MAX );
     494           0 :         tc->scratch_heads[ txn_bucket ] = (uint)txn_gidx;
     495           0 :         tc->scratch_txnpage->free--;
     496           0 :       } else {
     497             :         /* Stale transaction.  Drop. */
     498           0 :         continue;
     499           0 :       }
     500           0 :     }
     501           0 :     if( FD_UNLIKELY( curr_txnpage_idx!=scratch_txnpage_idx ) ) {
     502             :       /* The txnpage is not being used for compaction, free it up. */
     503           0 :       tc->txnpages_free[ tc->shmem->txnpages_free_cnt ] = curr_txnpage_idx;
     504           0 :       tc->shmem->txnpages_free_cnt++;
     505           0 :     }
     506           0 :   }
     507           0 :   if( FD_LIKELY( scratch_txnpage_idx!=USHORT_MAX ) ) {
     508           0 :     fd_txncache_txnpage_t * txnpage = &tc->txnpages[ scratch_txnpage_idx ];
     509           0 :     memcpy( txnpage, tc->scratch_txnpage, sizeof(*txnpage) );
     510           0 :   }
     511           0 :   blockcache->shmem->pages_cnt = scratch_pages_cnt;
     512           0 :   memcpy( blockcache->pages, tc->scratch_pages, tc->shmem->txnpages_per_blockhash_max*sizeof(blockcache->pages[0]) );
     513           0 :   memcpy( blockcache->heads, tc->scratch_heads, tc->shmem->bucket_cnt*sizeof(blockcache->heads[0]) );
     514           0 : }
     515             : 
     516             : static void
     517             : purge_stale_on_fork( fd_txncache_t * tc,
     518           0 :                      blockcache_t *  fork ) {
     519           0 :   purge_stale_on_blockcache( tc, fork );
     520             : 
     521           0 :   fd_txncache_fork_id_t sibling_idx = fork->shmem->child_id;
     522           0 :   while( sibling_idx.val!=USHORT_MAX ) {
     523           0 :     blockcache_t * sibling = &tc->blockcache_pool[ sibling_idx.val ];
     524           0 :     purge_stale_on_fork( tc, sibling );
     525           0 :     sibling_idx = sibling->shmem->sibling_id;
     526           0 :   }
     527           0 : }
     528             : 
     529             : static void
     530           0 : purge_stale( fd_txncache_t * tc ) {
     531           0 :   fd_txncache_blockcache_shmem_t * root_shmem = root_slist_ele_peek_head( tc->shmem->root_ll, tc->blockcache_shmem_pool );
     532           0 :   FD_TEST( root_shmem );
     533           0 :   blockcache_t * root = &tc->blockcache_pool[ blockcache_pool_idx( tc->blockcache_shmem_pool, root_shmem ) ];
     534           0 :   ushort free_before = tc->shmem->txnpages_free_cnt;
     535             :   /* One might think that an optimization here is to stop the descent on
     536             :      the latest rooted blockcache.  There could be no pruned minority
     537             :      forks from that point on.  As a result, there could be no stale
     538             :      transactions in any blockcache descending from that.
     539             :      Unfortunately, frontier eviction means that any blockcache in the
     540             :      fork tree can have stale transactions. */
     541           0 :   purge_stale_on_fork( tc, root );
     542           0 :   FD_LOG_WARNING(( "purge_stale: txnpages_free %hu -> %hu", free_before, tc->shmem->txnpages_free_cnt ));
     543           0 : }
     544             : 
     545             : void
     546             : fd_txncache_insert( fd_txncache_t *       tc,
     547             :                     fd_txncache_fork_id_t fork_id,
     548             :                     uchar const *         blockhash,
     549         273 :                     uchar const *         txnhash ) {
     550         273 :   fd_rwlock_read( tc->shmem->lock );
     551             : 
     552         273 :   blockcache_t const * fork = &tc->blockcache_pool[ fork_id.val ];
     553         273 :   FD_TEST( fork->shmem->frozen<=1 );
     554         273 :   FD_TEST( fork->shmem->frozen>=0 );
     555         273 :   blockcache_t * blockcache = blockhash_on_fork( tc, fork, blockhash );
     556         273 :   FD_TEST( blockcache );
     557             : 
     558         273 :   for(;;) {
     559         273 :     fd_txncache_txnpage_t * txnpage = fd_txncache_ensure_txnpage( tc, blockcache );
     560         273 :     if( FD_UNLIKELY( !txnpage ) ) {
     561             :       /* Because of sizing invariants when creating the structure, it is
     562             :          not typically possible to fill it, unless there are stale
     563             :          transactions from minority forks that were purged floating
     564             :          around, in which case we can purge them here and try again. */
     565           0 :       fd_rwlock_unread( tc->shmem->lock );
     566           0 :       fd_rwlock_write( tc->shmem->lock );
     567           0 :       if( FD_LIKELY( !fd_txncache_ensure_txnpage( tc, blockcache ) ) ) purge_stale( tc );
     568           0 :       fd_rwlock_unwrite( tc->shmem->lock );
     569           0 :       fd_rwlock_read( tc->shmem->lock );
     570           0 :       continue;
     571           0 :     }
     572             : 
     573         273 :     int success = fd_txncache_insert_txn( tc, blockcache, txnpage, fork_id, txnhash );
     574         273 :     if( FD_LIKELY( success ) ) break;
     575             : 
     576           0 :     FD_SPIN_PAUSE();
     577           0 :   }
     578             : 
     579         273 :   fd_rwlock_unread( tc->shmem->lock );
     580         273 : }
     581             : 
     582             : int
     583             : fd_txncache_query( fd_txncache_t *       tc,
     584             :                    fd_txncache_fork_id_t fork_id,
     585             :                    uchar const *         blockhash,
     586         390 :                    uchar const *         txnhash ) {
     587         390 :   fd_rwlock_read( tc->shmem->lock );
     588             : 
     589         390 :   blockcache_t const * fork = &tc->blockcache_pool[ fork_id.val ];
     590         390 :   FD_TEST( fork->shmem->frozen>=0 );
     591         390 :   blockcache_t const * blockcache = blockhash_on_fork( tc, fork, blockhash );
     592         390 :   FD_TEST( blockcache );
     593         390 :   FD_TEST( blockcache->shmem->frozen==2 );
     594             : 
     595         390 :   int found = 0;
     596             : 
     597         390 :   ulong txnhash_offset = blockcache->shmem->txnhash_offset;
     598         390 :   ulong head_hash = fd_txncache_bucket( tc, txnhash+txnhash_offset );
     599         405 :   for( uint head=blockcache->heads[ head_hash ]; head!=UINT_MAX; head=tc->txnpages[ head/FD_TXNCACHE_TXNS_PER_PAGE ].txns[ head%FD_TXNCACHE_TXNS_PER_PAGE ]->blockcache_next ) {
     600          18 :     fd_txncache_single_txn_t * txn = tc->txnpages[ head/FD_TXNCACHE_TXNS_PER_PAGE ].txns[ head%FD_TXNCACHE_TXNS_PER_PAGE ];
     601             : 
     602          18 :     blockcache_t const * txn_fork = &tc->blockcache_pool[ txn->fork_id.val ];
     603          18 :     int descends = (txn->fork_id.val==fork_id.val || descends_set_test( fork->descends, txn->fork_id.val )) && txn_fork->shmem->frozen>=0 && txn_fork->shmem->generation==txn->generation;
     604          18 :     if( FD_LIKELY( descends && !memcmp( txnhash+txnhash_offset, txn->txnhash, 20UL ) ) ) {
     605           3 :       found = 1;
     606           3 :       break;
     607           3 :     }
     608          18 :   }
     609             : 
     610         390 :   fd_rwlock_unread( tc->shmem->lock );
     611         390 :   return found;
     612         390 : }

Generated by: LCOV version 1.14