LCOV - code coverage report
Current view: top level - flamenco/runtime - fd_txncache.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 607 0.0 %
Date: 2024-11-13 11:58:15 Functions: 0 35 0.0 %

          Line data    Source code
       1             : #include "fd_txncache.h"
       2             : #include "../fd_rwlock.h"
       3             : 
       4             : #define SORT_NAME        sort_slot_ascend
       5             : #define SORT_KEY_T       ulong
       6             : #define SORT_BEFORE(a,b) (a)<(b)
       7             : #include "../../util/tmpl/fd_sort.c"
       8             : 
       9             : /* The number of transactions in each page.  This needs to be high
      10             :    enough to amoritze the cost of caller code reserving pages from,
      11             :    and returning pages to the pool, but not so high that the memory
      12             :    wasted from blockhashes with only one transaction is significant. */
      13             : 
      14           0 : #define FD_TXNCACHE_TXNS_PER_PAGE (16384UL)
      15             : 
      16             : /* The number of unique entries in the hash lookup table for each
      17             :    blockhash.  A higher value here uses more memory but enables faster
      18             :    lookups. */
      19             : 
      20           0 : #define FD_TXNCACHE_BLOCKCACHE_MAP_CNT (524288UL)
      21             : 
      22             : /* The number of unique entries in the hash lookup table for each
      23             :    (slot, blockhash).  This prevents all the entries needing to be in
      24             :    one slotcache list, so insertions can happen concurrently. */
      25             : 
      26           0 : #define FD_TXNCACHE_SLOTCACHE_MAP_CNT (1024UL)
      27             : 
      28             : /* Value for an empty blockcache `max_slot` or empty slotcache
      29             :   `slot` entry. When the entries are set to this value, we can insert
      30             :   to the entry, but stop iterating while running queries. */
      31             : 
      32           0 : #define FD_TXNCACHE_EMPTY_ENTRY (ULONG_MAX)
      33             : 
      34             : /* Value for a deleted cache entry. We can insert to such entries,
      35             :    and keep iterating while running queries. */
      36             : 
      37           0 : #define FD_TXNCACHE_TOMBSTONE_ENTRY (ULONG_MAX-1UL)
      38             : 
      39             : /* Placeholder value used for critical sections. */
      40             : #define FD_TXNCACHE_TEMP_ENTRY (ULONG_MAX-2UL)
      41             : 
      42             : struct fd_txncache_private_txn {
      43             :   uint  blockcache_next; /* Pointer to the next element in the blockcache hash chain containing this entry from the pool. */
      44             :   uint  slotblockcache_next;  /* Pointer to the next element in the slotcache hash chain containing this entry from the pool. */
      45             : 
      46             :   ulong slot;            /* Slot that the transaction was executed.  A transaction might be in the cache
      47             :                             multiple times if it was executed in a different slot on different forks.  The
      48             :                             same slot will not appear multiple times however. */
      49             :   uchar txnhash[ 20 ];   /* The transaction hash, truncated to 20 bytes.  The hash is not always the first 20
      50             :                             bytes, but is 20 bytes starting at some arbitrary offset given by the key_offset value
      51             :                             of the containing by_blockhash entry. */
      52             :   uchar result;          /* The result of executing the transaction. This is the discriminant of the transaction
      53             :                             result type. 0 means success. */
      54             : };
      55             : 
      56             : typedef struct fd_txncache_private_txn fd_txncache_private_txn_t;
      57             : 
      58             : struct fd_txncache_private_txnpage {
      59             :   ushort                    free; /* The number of free txn entries in this page. */
      60             :   fd_txncache_private_txn_t txns[ FD_TXNCACHE_TXNS_PER_PAGE][ 1 ]; /* The transactions in the page. */
      61             : };
      62             : 
      63             : typedef struct fd_txncache_private_txnpage fd_txncache_private_txnpage_t;
      64             : 
      65             : struct fd_txncache_private_blockcache {
      66             :   uchar blockhash[ 32 ]; /* The actual blockhash of these transactions. */
      67             :   ulong max_slot;        /* The max slot we have seen that contains a transaction referencing this blockhash.
      68             :                             The blockhash entry will not be purged until the lowest rooted slot is greater than this. */
      69             :   ulong txnhash_offset;  /* To save memory, the Agave validator decided to truncate the hash of transactions stored in
      70             :                             this memory to 20 bytes rather than 32 bytes.  The bytes used are not the first 20 as you
      71             :                             might expect, but instead the first 20 starting at some random offset into the transaction
      72             :                             hash (starting between 0 and len(hash)-20, a/k/a 44 for signatures, and 12 for hashes).
      73             : 
      74             :                             In an unfortunate turn, the offset is also propogated to peers via. snapshot responses,
      75             :                             which only communicate the offset and the respective 20 bytes.  To make sure we are
      76             :                             deduplicating incoming transactions correctly, we must replicate this system even though
      77             :                             it would be easier to just always take the first 20 bytes.  For transactions that we
      78             :                             insert into the cache ourselves, we do just always use a key_offset of zero, so this is
      79             :                             only nonzero when constructed form a peer snapshot. */
      80             : 
      81             :   uint  heads[ FD_TXNCACHE_BLOCKCACHE_MAP_CNT ]; /* The hash table for the blockhash.  Each entry is a pointer to the head of a
      82             :                                                     linked list of transactions that reference this blockhash.  As we add
      83             :                                                     transactions to the bucket, the head pointer is updated to the new item, and
      84             :                                                     the new item is pointed to the previous head. */
      85             : 
      86             :   ushort pages_cnt;      /* The number of txnpages currently in use to store the transactions in this blockcache. */
      87             :   uint * pages;          /* A list of the txnpages containing the transactions for this blockcache. */
      88             : };
      89             : 
      90             : typedef struct fd_txncache_private_blockcache fd_txncache_private_blockcache_t;
      91             : 
      92             : struct fd_txncache_private_slotblockcache {
      93             :   uchar blockhash[ 32 ]; /* The actual blockhash of these transactions. */
      94             :   ulong txnhash_offset;  /* As described above. */
      95             :   uint  heads[ FD_TXNCACHE_SLOTCACHE_MAP_CNT ]; /* A map of the head of a linked list of tansactions in this slot and blockhash */
      96             : };
      97             : 
      98             : typedef struct fd_txncache_private_slotblockcache fd_txncache_private_slotblockcache_t;
      99             : 
     100             : struct fd_txncache_private_slotcache {
     101             :   ulong                                slot; /* The slot that this slotcache is for. */
     102             :   fd_txncache_private_slotblockcache_t blockcache[ 300UL ];
     103             : };
     104             : 
     105             : typedef struct fd_txncache_private_slotcache fd_txncache_private_slotcache_t;
     106             : 
     107             : struct __attribute__((aligned(FD_TXNCACHE_ALIGN))) fd_txncache_private {
     108             :   fd_rwlock_t lock[ 1 ]; /* The txncache is a concurrent structure and will be accessed by multiple threads
     109             :                             concurrently.  Insertion and querying only take a read lock as they can be done
     110             :                             lockless but all other operations will take a write lock internally. */
     111             : 
     112             :   ulong  root_slots_max;
     113             :   ulong  live_slots_max;
     114             :   ushort txnpages_per_blockhash_max;
     115             :   uint   txnpages_max;
     116             : 
     117             :   ulong   root_slots_cnt; /* The number of root slots being tracked in the below array. */
     118             :   ulong   root_slots_off; /* The highest N slots that have been rooted.  These slots are
     119             :                              used to determine which transactions should be kept around to
     120             :                              be queried and served to snapshot requests.  The actual
     121             :                              footprint for this data (and other data below) are declared
     122             :                              immediately following the struct.  I.e. these pointers point to
     123             :                              memory not far after the struct. */
     124             : 
     125             :   ulong blockcache_off; /* The actual cache of transactions.  This is a linear probed hash
     126             :                            table that maps blockhashes to the transactions that reference them.
     127             :                            The depth of the hash table is live_slots_max, since this is the
     128             :                            maximum number of blockhashes that can be alive.  The loading factor
     129             :                            if they were all alive would be 1.0, but this is rare because we
     130             :                            will almost never fork repeatedly to hit this limit.  These
     131             :                            blockcaches are just pointers to pages from the txnpages below, so
     132             :                            they don't take up much memory. */
     133             : 
     134             :   ulong slotcache_off; /* The cache of transactions by slot instead of by blockhash, so we
     135             :                           can quickly serialize the slot deltas for the root slots which are
     136             :                           served to peers in snapshots.  Similar to the above, it uses the
     137             :                           same underlying transaction storage, but different lookup tables. */
     138             : 
     139             :   uint     txnpages_free_cnt; /* The number of pages in the txnpages that are not currently in use. */
     140             :   ulong    txnpages_free_off; /* The index in the txnpages array that is free, for each of the free pages. */
     141             : 
     142             :   ulong    txnpages_off; /* The actual storage for the transactions.  The blockcache points to these
     143             :                             pages when storing transactions.  Transaction are grouped into pages of
     144             :                             size 16384 to make certain allocation and deallocation operations faster
     145             :                             (just the pages are acquired/released, rather than each txn). */
     146             : 
     147             :   ulong blockcache_pages_off; /* The pages for the blockcache entries. */
     148             : 
     149             :   ulong probed_entries_off; /* The map of index to number of entries which oveflowed over this index.
     150             :                                Overflow for index i is defined as every entry j > i where j should have
     151             :                                been inserted at k < i. */
     152             :   ulong magic; /* ==FD_TXNCACHE_MAGIC */
     153             : };
     154             : 
     155             : FD_FN_PURE static ulong *
     156           0 : fd_txncache_get_root_slots( fd_txncache_t * tc ) {
     157           0 :   return (ulong *)( (uchar *)tc + tc->root_slots_off );
     158           0 : }
     159             : 
     160             : FD_FN_PURE static fd_txncache_private_blockcache_t *
     161           0 : fd_txncache_get_blockcache( fd_txncache_t * tc ) {
     162           0 :   return (fd_txncache_private_blockcache_t *)( (uchar *)tc + tc->blockcache_off );
     163           0 : }
     164             : 
     165             : FD_FN_PURE static fd_txncache_private_blockcache_t *
     166           0 : fd_txncache_get_blockcache_const( fd_txncache_t const * tc ) {
     167           0 :   return (fd_txncache_private_blockcache_t *)( (uchar const *)tc + tc->blockcache_off );
     168           0 : }
     169             : 
     170             : FD_FN_PURE static fd_txncache_private_slotcache_t *
     171           0 : fd_txncache_get_slotcache( fd_txncache_t * tc ) {
     172           0 :   return (fd_txncache_private_slotcache_t *)( (uchar *)tc + tc->slotcache_off );
     173           0 : }
     174             : 
     175             : FD_FN_PURE static fd_txncache_private_slotcache_t *
     176           0 : fd_txncache_get_slotcache_const( fd_txncache_t const * tc ) {
     177           0 :   return (fd_txncache_private_slotcache_t *)( (uchar const *)tc + tc->slotcache_off );
     178           0 : }
     179             : 
     180             : FD_FN_PURE static uint *
     181           0 : fd_txncache_get_txnpages_free( fd_txncache_t * tc ) {
     182           0 :   return (uint *)( (uchar *)tc + tc->txnpages_free_off );
     183           0 : }
     184             : 
     185             : FD_FN_PURE static fd_txncache_private_txnpage_t *
     186           0 : fd_txncache_get_txnpages( fd_txncache_t * tc ) {
     187           0 :   return (fd_txncache_private_txnpage_t *)( (uchar *)tc + tc->txnpages_off );
     188           0 : }
     189             : 
     190             : FD_FN_PURE static ulong *
     191           0 : fd_txncache_get_probed_entries( fd_txncache_t * tc ) {
     192           0 :   return (ulong *)( (uchar *)tc + tc->probed_entries_off );
     193           0 : }
     194             : 
     195             : FD_FN_PURE static ulong *
     196           0 : fd_txncache_get_probed_entries_const( fd_txncache_t const * tc ) {
     197           0 :   return (ulong *)( (uchar const *)tc + tc->probed_entries_off );
     198           0 : }
     199             : 
     200             : FD_FN_CONST static ushort
     201           0 : fd_txncache_max_txnpages_per_blockhash( ulong max_txn_per_slot ) {
     202             :   /* The maximum number of transaction pages we might need to store all
     203             :      the transactions that could be seen in a blockhash.
     204             : 
     205             :      In the worst case, every transaction in every slot refers to
     206             :      the same blockhash for as long as it is possible (150 slots
     207             :      following the slot where the blockhash is produced).  So there
     208             :      could be up to
     209             : 
     210             :         524,288 * 150 = 78,643,200
     211             : 
     212             :      Note that the blockcaches store txns for forks, and the same txn
     213             :      might appear multiple times in one block, but if there's a fork,
     214             :      the fork has to have skipped slots (had 0 txns in them), so it
     215             :      cannot cause this limit to go higher.
     216             : 
     217             :      Transactions referenced by a particular blockhash.
     218             :      Transactions are stored in pages of 16,384, so we might need up
     219             :      to 4,800 of these pages to store all the transactions in a
     220             :      slot. */
     221             : 
     222           0 :   ulong result = 1UL+(max_txn_per_slot*150UL-1UL)/FD_TXNCACHE_TXNS_PER_PAGE;
     223           0 :   if( FD_UNLIKELY( result>USHORT_MAX ) ) return 0;
     224           0 :   return (ushort)result;
     225           0 : }
     226             : 
     227             : FD_FN_CONST static uint
     228             : fd_txncache_max_txnpages( ulong max_live_slots,
     229           0 :                           ulong max_txn_per_slot ) {
     230             :   /* We need to be able to store potentially every slot that is live
     231             :      being completely full of transactions.  This would be
     232             : 
     233             :        max_live_slots*max_txn_per_slot
     234             : 
     235             :      transactions, except that we are counting pages here, not
     236             :      transactions.  It's not enough to divide by the page size, because
     237             :      pages might be wasted.  The maximum page wastage occurs when all
     238             :      the blockhashes except one have one transaction in them, and the
     239             :      remaining blockhash has all other transactions.  In that case, the
     240             :      full blockhash needs
     241             : 
     242             :        (max_live_slots*max_txn_per_slot)/FD_TXNCACHE_TXNS_PER_PAGE
     243             : 
     244             :      pages, and the other blockhashes need 1 page each. */
     245             : 
     246           0 :   ulong result = max_live_slots-1UL+max_live_slots*(1UL+(max_txn_per_slot-1UL)/FD_TXNCACHE_TXNS_PER_PAGE);
     247           0 :   if( FD_UNLIKELY( result>UINT_MAX ) ) return 0;
     248           0 :   return (uint)result;
     249           0 : }
     250             : 
     251             : FD_FN_CONST ulong
     252           0 : fd_txncache_align( void ) {
     253           0 :   return FD_TXNCACHE_ALIGN;
     254           0 : }
     255             : 
     256             : FD_FN_CONST ulong
     257             : fd_txncache_footprint( ulong max_rooted_slots,
     258             :                        ulong max_live_slots,
     259           0 :                        ulong max_txn_per_slot ) {
     260           0 :   if( FD_UNLIKELY( max_rooted_slots<1UL || max_live_slots<1UL ) ) return 0UL;
     261           0 :   if( FD_UNLIKELY( max_live_slots<max_rooted_slots ) ) return 0UL;
     262           0 :   if( FD_UNLIKELY( max_txn_per_slot<1UL ) ) return 0UL;
     263           0 :   if( FD_UNLIKELY( !fd_ulong_is_pow2( max_live_slots ) || !fd_ulong_is_pow2( max_txn_per_slot ) ) ) return 0UL;
     264             : 
     265             :   /* To save memory, txnpages are referenced as uint which is enough
     266             :      to support mainnet parameters without overflow. */
     267           0 :   uint max_txnpages = fd_txncache_max_txnpages( max_live_slots, max_txn_per_slot );
     268           0 :   if( FD_UNLIKELY( !max_txnpages ) ) return 0UL;
     269             : 
     270           0 :   ulong max_txnpages_per_blockhash = fd_txncache_max_txnpages_per_blockhash( max_txn_per_slot );
     271           0 :   if( FD_UNLIKELY( !max_txnpages_per_blockhash ) ) return 0UL;
     272             : 
     273           0 :   ulong l;
     274           0 :   l = FD_LAYOUT_INIT;
     275           0 :   l = FD_LAYOUT_APPEND( l, FD_TXNCACHE_ALIGN,                         sizeof(fd_txncache_t)                                   );
     276           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),                            max_rooted_slots*sizeof(ulong)                          ); /* root_slots */
     277           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_txncache_private_blockcache_t), max_live_slots*sizeof(fd_txncache_private_blockcache_t) ); /* blockcache */
     278           0 :   l = FD_LAYOUT_APPEND( l, alignof(uint),                             max_live_slots*max_txnpages_per_blockhash*sizeof(uint)  ); /* blockcache->pages */
     279           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_txncache_private_slotcache_t),  max_live_slots*sizeof(fd_txncache_private_slotcache_t ) ); /* slotcache */
     280           0 :   l = FD_LAYOUT_APPEND( l, alignof(uint),                             max_txnpages*sizeof(uint)                               ); /* txnpages_free */
     281           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_txncache_private_txnpage_t),    max_txnpages*sizeof(fd_txncache_private_txnpage_t)      ); /* txnpages */
     282           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),                            max_live_slots*sizeof(ulong)                            ); /* probed entries */
     283           0 :   return FD_LAYOUT_FINI( l, FD_TXNCACHE_ALIGN );
     284           0 : }
     285             : 
     286             : void *
     287             : fd_txncache_new( void * shmem,
     288             :                  ulong  max_rooted_slots,
     289             :                  ulong  max_live_slots,
     290           0 :                  ulong  max_txn_per_slot ) {
     291           0 :   fd_txncache_t * tc = (fd_txncache_t *)shmem;
     292             : 
     293           0 :   if( FD_UNLIKELY( !shmem ) ) {
     294           0 :     FD_LOG_WARNING(( "NULL shmem" ));
     295           0 :     return NULL;
     296           0 :   }
     297             : 
     298           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_txncache_align() ) ) ) {
     299           0 :     FD_LOG_WARNING(( "misaligned shmem" ));
     300           0 :     return NULL;
     301           0 :   }
     302             : 
     303           0 :   if( FD_UNLIKELY( !max_rooted_slots ) ) return NULL;
     304           0 :   if( FD_UNLIKELY( !max_live_slots ) ) return NULL;
     305           0 :   if( FD_UNLIKELY( max_live_slots<max_rooted_slots ) ) return NULL;
     306           0 :   if( FD_UNLIKELY( !max_txn_per_slot ) ) return NULL;
     307           0 :   if( FD_UNLIKELY( !fd_ulong_is_pow2( max_live_slots ) || !fd_ulong_is_pow2( max_txn_per_slot ) ) ) return NULL;
     308             : 
     309           0 :   uint max_txnpages                 = fd_txncache_max_txnpages( max_live_slots, max_txn_per_slot );
     310           0 :   ushort max_txnpages_per_blockhash = fd_txncache_max_txnpages_per_blockhash( max_txn_per_slot );
     311             : 
     312           0 :   if( FD_UNLIKELY( !max_txnpages ) ) return NULL;
     313           0 :   if( FD_UNLIKELY( !max_txnpages_per_blockhash ) ) return NULL;
     314             : 
     315           0 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     316           0 :   fd_txncache_t * txncache = FD_SCRATCH_ALLOC_APPEND( l,  FD_TXNCACHE_ALIGN,                        sizeof(fd_txncache_t)                                   );
     317           0 :   void * _root_slots       = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong),                            max_rooted_slots*sizeof(ulong)                          );
     318           0 :   void * _blockcache       = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txncache_private_blockcache_t), max_live_slots*sizeof(fd_txncache_private_blockcache_t) );
     319           0 :   void * _blockcache_pages = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),                             max_live_slots*max_txnpages_per_blockhash*sizeof(uint)  );
     320           0 :   void * _slotcache        = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txncache_private_slotcache_t),  max_live_slots*sizeof(fd_txncache_private_slotcache_t ) );
     321           0 :   void * _txnpages_free    = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),                             max_txnpages*sizeof(uint)                               );
     322           0 :   void * _txnpages         = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txncache_private_txnpage_t),    max_txnpages*sizeof(fd_txncache_private_txnpage_t)      );
     323           0 :   void * _probed_entries   = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong),                            max_live_slots*sizeof(ulong)                            );
     324             : 
     325             :   /* We calculate and store the offsets for these allocations. */
     326           0 :   txncache->root_slots_off       = (ulong)_root_slots - (ulong)txncache;
     327           0 :   txncache->blockcache_off       = (ulong)_blockcache - (ulong)txncache;
     328           0 :   txncache->slotcache_off        = (ulong)_slotcache - (ulong)txncache;
     329           0 :   txncache->txnpages_free_off    = (ulong)_txnpages_free - (ulong)txncache;
     330           0 :   txncache->txnpages_off         = (ulong)_txnpages - (ulong)txncache;
     331           0 :   txncache->blockcache_pages_off = (ulong)_blockcache_pages - (ulong)txncache;
     332           0 :   txncache->probed_entries_off   = (ulong)_probed_entries - (ulong)txncache;
     333             : 
     334           0 :   tc->lock->value = 0;
     335           0 :   tc->root_slots_cnt = 0UL;
     336             : 
     337           0 :   tc->root_slots_max             = max_rooted_slots;
     338           0 :   tc->live_slots_max             = max_live_slots;
     339           0 :   tc->txnpages_per_blockhash_max = max_txnpages_per_blockhash;
     340           0 :   tc->txnpages_max               = max_txnpages;
     341             : 
     342           0 :   ulong * root_slots = (ulong *)_root_slots;
     343           0 :   memset( root_slots, 0xFF, max_rooted_slots*sizeof(ulong) );
     344             : 
     345           0 :   fd_txncache_private_blockcache_t * blockcache = (fd_txncache_private_blockcache_t *)_blockcache;
     346           0 :   fd_txncache_private_slotcache_t  * slotcache  = (fd_txncache_private_slotcache_t *)_slotcache;
     347           0 :   ulong * probed_entries = (ulong *)_probed_entries;
     348           0 :   for( ulong i=0UL; i<max_live_slots; i++ ) {
     349           0 :     blockcache[ i ].max_slot = FD_TXNCACHE_EMPTY_ENTRY;
     350           0 :     slotcache[ i ].slot      = FD_TXNCACHE_EMPTY_ENTRY;
     351           0 :     probed_entries[ i ]      = 0UL;
     352           0 :   }
     353             : 
     354           0 :   tc->txnpages_free_cnt = max_txnpages;
     355           0 :   uint * txnpages_free  = _txnpages_free;
     356           0 :   for( uint i=0; i<max_txnpages; i++ ) txnpages_free[ i ] = i;
     357             : 
     358           0 :   FD_COMPILER_MFENCE();
     359           0 :   FD_VOLATILE( tc->magic ) = FD_TXNCACHE_MAGIC;
     360           0 :   FD_COMPILER_MFENCE();
     361             : 
     362           0 :   return (void *)tc;
     363           0 : }
     364             : 
     365             : fd_txncache_t *
     366           0 : fd_txncache_join( void * shtc ) {
     367           0 :   if( FD_UNLIKELY( !shtc ) ) {
     368           0 :     FD_LOG_WARNING(( "NULL shtc" ));
     369           0 :     return NULL;
     370           0 :   }
     371             : 
     372           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shtc, fd_txncache_align() ) ) ) {
     373           0 :     FD_LOG_WARNING(( "misaligned shtc" ));
     374           0 :     return NULL;
     375           0 :   }
     376             : 
     377           0 :   fd_txncache_t * tc = (fd_txncache_t *)shtc;
     378             : 
     379           0 :   if( FD_UNLIKELY( tc->magic!=FD_TXNCACHE_MAGIC ) ) {
     380           0 :     FD_LOG_WARNING(( "bad magic" ));
     381           0 :     return NULL;
     382           0 :   }
     383             : 
     384           0 :   uchar * base = (uchar *)tc;
     385           0 :   fd_txncache_private_blockcache_t * blockcache = (fd_txncache_private_blockcache_t *)( base + tc->blockcache_off );
     386             : 
     387           0 :   void * _blockcache_pages = base + tc->blockcache_pages_off;
     388           0 :   for( ulong i=0UL; i<tc->live_slots_max; i++ ) {
     389           0 :     blockcache[ i ].pages       = (uint *)_blockcache_pages + i*tc->txnpages_per_blockhash_max;
     390           0 :   }
     391           0 :   return tc;
     392           0 : }
     393             : 
     394             : void *
     395           0 : fd_txncache_leave( fd_txncache_t * tc ) {
     396           0 :   if( FD_UNLIKELY( !tc ) ) {
     397           0 :     FD_LOG_WARNING(( "NULL tc" ));
     398           0 :     return NULL;  }
     399             : 
     400           0 :   return (void *)tc;
     401           0 : }
     402             : 
     403             : void *
     404           0 : fd_txncache_delete( void * shtc ) {
     405           0 :   if( FD_UNLIKELY( !shtc ) ) {
     406           0 :     FD_LOG_WARNING(( "NULL shtc" ));
     407           0 :     return NULL;
     408           0 :   }
     409             : 
     410           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shtc, fd_txncache_align() ) ) ) {
     411           0 :     FD_LOG_WARNING(( "misaligned shtc" ));
     412           0 :     return NULL;
     413           0 :   }
     414             : 
     415           0 :   fd_txncache_t * tc = (fd_txncache_t *)shtc;
     416             : 
     417           0 :   if( FD_UNLIKELY( tc->magic!=FD_TXNCACHE_MAGIC ) ) {
     418           0 :     FD_LOG_WARNING(( "bad magic" ));
     419           0 :     return NULL;
     420           0 :   }
     421             : 
     422           0 :   FD_COMPILER_MFENCE();
     423           0 :   FD_VOLATILE( tc->magic ) = 0UL;
     424           0 :   FD_COMPILER_MFENCE();
     425             : 
     426           0 :   return (void *)tc;
     427           0 : }
     428             : 
     429             : static void
     430             : fd_txncache_remove_blockcache_idx( fd_txncache_t * tc,
     431           0 :                                    ulong idx ) {
     432           0 :   fd_txncache_private_blockcache_t * blockcache = fd_txncache_get_blockcache( tc );
     433           0 :   ulong * probed_entries = fd_txncache_get_probed_entries( tc );
     434           0 :   uint * txnpages_free = fd_txncache_get_txnpages_free( tc );
     435             : 
     436             :   /* Check if removing this element caused there to be no overflow for a hash index. */
     437           0 :   ulong hash_idx = FD_LOAD( ulong, blockcache[ idx ].blockhash )%tc->live_slots_max;
     438             : 
     439           0 :   ulong j = hash_idx;
     440           0 :   while( j != idx ) {
     441           0 :     probed_entries[ j ]--;
     442             :     /* If there is no overflow and the slot is a tombstone, mark it as free. */
     443           0 :     if( probed_entries[ j ] == 0 && blockcache[ j ].max_slot == FD_TXNCACHE_TOMBSTONE_ENTRY ) {
     444           0 :       blockcache[ j ].max_slot = FD_TXNCACHE_EMPTY_ENTRY;
     445           0 :     }
     446           0 :     j = (j+1)%tc->live_slots_max;
     447           0 :   }
     448             : 
     449             :   /* Remove from block cache. */
     450           0 :   blockcache[ idx ].max_slot = (probed_entries[ idx ] == 0 ? FD_TXNCACHE_EMPTY_ENTRY : FD_TXNCACHE_TOMBSTONE_ENTRY);
     451             : 
     452             :   /* Free pages. */
     453           0 :   memcpy( txnpages_free+tc->txnpages_free_cnt, blockcache[ idx ].pages, blockcache[ idx ].pages_cnt*sizeof(uint) );
     454           0 :   tc->txnpages_free_cnt += blockcache[ idx ].pages_cnt;
     455           0 : }
     456             : 
     457             : static void
     458             : fd_txncache_remove_slotcache_idx( fd_txncache_t * tc,
     459           0 :                                   ulong idx ) {
     460           0 :   fd_txncache_private_slotcache_t * slotcache = fd_txncache_get_slotcache( tc );
     461           0 :   slotcache[ idx ].slot = FD_TXNCACHE_TOMBSTONE_ENTRY;
     462           0 : }
     463             : 
     464             : static void
     465             : fd_txncache_purge_slot( fd_txncache_t * tc,
     466           0 :                         ulong           slot ) {
     467           0 :   ulong not_purged_cnt = 0;
     468           0 :   ulong purged_cnt = 0;
     469           0 :   ulong max_distance = 0;
     470           0 :   ulong sum_distance = 0;
     471           0 :   ulong empty_entry_cnt = 0;
     472           0 :   ulong tombstone_entry_cnt = 0;
     473           0 :   fd_txncache_private_blockcache_t * blockcache = fd_txncache_get_blockcache( tc );
     474           0 :   for( ulong i=0UL; i<tc->live_slots_max; i++ ) {
     475           0 :     if( FD_LIKELY( blockcache[ i ].max_slot==FD_TXNCACHE_EMPTY_ENTRY || blockcache[ i ].max_slot==FD_TXNCACHE_TOMBSTONE_ENTRY || (blockcache[ i ].max_slot)>slot ) ) {
     476           0 :       if( blockcache[ i ].max_slot==FD_TXNCACHE_EMPTY_ENTRY ) {
     477           0 :         empty_entry_cnt++;
     478           0 :       } else if ( blockcache[ i ].max_slot==FD_TXNCACHE_TOMBSTONE_ENTRY ) {
     479           0 :         tombstone_entry_cnt++;
     480           0 :       } else {
     481           0 :         not_purged_cnt++;
     482           0 :         ulong dist = blockcache[ i ].max_slot-slot;
     483           0 :         max_distance = fd_ulong_max( max_distance, dist );
     484           0 :         sum_distance += blockcache[ i ].max_slot-slot;
     485           0 :       }
     486           0 :       continue;
     487           0 :     }
     488           0 :     fd_txncache_remove_blockcache_idx( tc, i );
     489           0 :     purged_cnt++;
     490           0 :   }
     491           0 :   ulong avg_distance = (not_purged_cnt==0) ? ULONG_MAX : (sum_distance/not_purged_cnt);
     492           0 :   FD_LOG_INFO(( "not purging cnt - purge_slot: %lu, purged_cnt: %lu, not_purged_cnt: %lu, empty_entry_cnt: %lu, tombstone_entry_cnt: %lu, max_distance: %lu, avg_distance: %lu",
     493           0 :       slot, purged_cnt, not_purged_cnt, empty_entry_cnt, tombstone_entry_cnt, max_distance, avg_distance ));
     494             : 
     495             :   /* TODO: figure out how to make slotcache purging work with the max_slot purge for blockcache.
     496             :      The blockcache and slotcache share txnpages and the aggressive purging from the blockcache
     497             :      can lead to corruption in the slotcache. Does it make sense to generate the delta map (as
     498             :      generated by Agave) from the blockcache when producing snapshots? TBD. */
     499           0 :   fd_txncache_private_slotcache_t * slotcache = fd_txncache_get_slotcache( tc );
     500           0 :   for( ulong i=0UL; i<tc->live_slots_max; i++ ) {
     501           0 :     if( FD_LIKELY( slotcache[ i ].slot==FD_TXNCACHE_EMPTY_ENTRY || slotcache[ i ].slot==FD_TXNCACHE_TOMBSTONE_ENTRY || slotcache[ i ].slot>slot ) ) continue;
     502           0 :     fd_txncache_remove_slotcache_idx( tc, i );
     503           0 :   }
     504           0 : }
     505             : 
     506             : void
     507             : fd_txncache_register_root_slot( fd_txncache_t * tc,
     508           0 :                                 ulong           slot ) {
     509           0 :   fd_rwlock_write( tc->lock );
     510             : 
     511           0 :   ulong * root_slots = fd_txncache_get_root_slots( tc );
     512           0 :   ulong idx;
     513           0 :   for( idx=0UL; idx<tc->root_slots_cnt; idx++ ) {
     514           0 :     if( FD_UNLIKELY( root_slots[ idx ]==slot ) ) goto unlock;
     515           0 :     if( FD_UNLIKELY( root_slots[ idx ]>slot ) ) break;
     516           0 :   }
     517             : 
     518           0 :   if( FD_UNLIKELY( tc->root_slots_cnt>=tc->root_slots_max ) ) {
     519           0 :     if( FD_LIKELY( idx ) ) {
     520           0 :       fd_txncache_purge_slot( tc, root_slots[ 0 ] );
     521           0 :       memmove( root_slots, root_slots+1UL, (idx-1UL)*sizeof(ulong) );
     522           0 :       root_slots[ (idx-1UL) ] = slot;
     523           0 :     } else {
     524           0 :       fd_txncache_purge_slot( tc, slot );
     525           0 :     }
     526           0 :   } else {
     527           0 :     if( FD_UNLIKELY( idx<tc->root_slots_cnt ) ) {
     528           0 :       memmove( root_slots+idx+1UL, root_slots+idx, (tc->root_slots_cnt-idx)*sizeof(ulong) );
     529           0 :     }
     530           0 :     root_slots[ idx ] = slot;
     531           0 :     tc->root_slots_cnt++;
     532           0 :   }
     533             : 
     534           0 : unlock:
     535           0 :   fd_rwlock_unwrite( tc->lock );
     536           0 : }
     537             : 
     538             : void
     539             : fd_txncache_root_slots( fd_txncache_t * tc,
     540           0 :                         ulong *         out_slots ) {
     541           0 :   fd_rwlock_write( tc->lock );
     542           0 :   ulong * root_slots = fd_txncache_get_root_slots( tc );
     543           0 :   memcpy( out_slots, root_slots, tc->root_slots_max*sizeof(ulong) );
     544           0 :   fd_rwlock_unwrite( tc->lock );
     545           0 : }
     546             : 
     547           0 : #define FD_TXNCACHE_FIND_FOUND      (0)
     548           0 : #define FD_TXNCACHE_FIND_FOUNDEMPTY (1)
     549           0 : #define FD_TXNCACHE_FIND_FULL       (2)
     550             : 
     551             : static int
     552             : fd_txncache_find_blockhash( fd_txncache_t const *               tc,
     553             :                             uchar const                         blockhash[ static 32 ],
     554             :                             uint                                is_insert,
     555           0 :                             fd_txncache_private_blockcache_t ** out_blockcache ) {
     556           0 :   ulong hash = FD_LOAD( ulong, blockhash );
     557           0 :   fd_txncache_private_blockcache_t * tc_blockcache = fd_txncache_get_blockcache_const( tc );
     558           0 :   ulong * probed_entries = fd_txncache_get_probed_entries_const( tc );
     559           0 :   ulong first_tombstone = ULONG_MAX;
     560             : 
     561           0 :   for( ulong i=0UL; i<tc->live_slots_max; i++ ) {
     562           0 :     ulong blockcache_idx = (hash+i)%tc->live_slots_max;
     563           0 :     fd_txncache_private_blockcache_t * blockcache = &tc_blockcache[ blockcache_idx ];
     564             : 
     565           0 :     if( FD_UNLIKELY( blockcache->max_slot==FD_TXNCACHE_EMPTY_ENTRY ) ) {
     566           0 :       if( first_tombstone != ULONG_MAX ) {
     567           0 :         *out_blockcache = &tc_blockcache[ first_tombstone ];
     568           0 :         return FD_TXNCACHE_FIND_FOUNDEMPTY;
     569           0 :       }
     570           0 :       *out_blockcache = blockcache;
     571           0 :       return FD_TXNCACHE_FIND_FOUNDEMPTY;
     572           0 :     } else if ( blockcache->max_slot==FD_TXNCACHE_TOMBSTONE_ENTRY ) {
     573           0 :       if( is_insert && first_tombstone == ULONG_MAX ) {
     574           0 :         first_tombstone = blockcache_idx;
     575           0 :       }
     576           0 :       continue;
     577           0 :     }
     578             : 
     579           0 :     while( FD_UNLIKELY( blockcache->max_slot==FD_TXNCACHE_TEMP_ENTRY ) ) {
     580           0 :       FD_SPIN_PAUSE();
     581           0 :     }
     582           0 :     FD_COMPILER_MFENCE(); /* Prevent reordering of the blockhash read to before the atomic lock
     583             :                              (highest_slot) has been fully released by the writer. */
     584           0 :     if( FD_LIKELY( !memcmp( blockcache->blockhash, blockhash, 32UL ) ) ) {
     585           0 :       *out_blockcache = blockcache;
     586           0 :       if( is_insert ) {
     587             :         /* Undo the probed entry changes since we found the blockhash. */
     588           0 :         for( ulong j=hash%tc->live_slots_max; j!=fd_ulong_min(first_tombstone, blockcache_idx); ) {
     589           0 :           probed_entries[ j ]--;
     590           0 :           j = (j+1)%tc->live_slots_max;
     591           0 :         }
     592           0 :       }
     593           0 :       return FD_TXNCACHE_FIND_FOUND;
     594           0 :     }
     595             :     /* If the entry we are passing is full and we haven't seen tombstones,
     596             :        there is an overflow. */
     597           0 :     if( is_insert && first_tombstone == ULONG_MAX ) {
     598           0 :       probed_entries[ blockcache_idx ]++;
     599           0 :     }
     600           0 :   }
     601             : 
     602           0 :   if( first_tombstone != ULONG_MAX ) {
     603           0 :     *out_blockcache = &tc_blockcache[ first_tombstone ];
     604           0 :     return FD_TXNCACHE_FIND_FOUNDEMPTY;
     605           0 :   }
     606           0 :   return FD_TXNCACHE_FIND_FULL;
     607           0 : }
     608             : 
     609             : static int
     610             : fd_txncache_find_slot( fd_txncache_t const *              tc,
     611             :                        ulong                              slot,
     612             :                        uint                               is_insert,
     613           0 :                        fd_txncache_private_slotcache_t ** out_slotcache ) {
     614           0 :   fd_txncache_private_slotcache_t * tc_slotcache = fd_txncache_get_slotcache_const( tc );
     615           0 :   for( ulong i=0UL; i<tc->live_slots_max; i++ ) {
     616           0 :     ulong slotcache_idx = (slot+i)%tc->live_slots_max;
     617           0 :     fd_txncache_private_slotcache_t * slotcache = &tc_slotcache[ slotcache_idx ];
     618           0 :     if( FD_UNLIKELY( slotcache->slot==FD_TXNCACHE_EMPTY_ENTRY ) ) {
     619           0 :       *out_slotcache = slotcache;
     620           0 :       return FD_TXNCACHE_FIND_FOUNDEMPTY;
     621           0 :     } else if( FD_UNLIKELY( slotcache->slot==FD_TXNCACHE_TOMBSTONE_ENTRY ) ) {
     622           0 :       if( is_insert ) {
     623           0 :         *out_slotcache = slotcache;
     624           0 :         return FD_TXNCACHE_FIND_FOUNDEMPTY;
     625           0 :       }
     626           0 :       continue;
     627           0 :     }
     628           0 :     while( FD_UNLIKELY( slotcache->slot==FD_TXNCACHE_TEMP_ENTRY ) ) {
     629           0 :       FD_SPIN_PAUSE();
     630           0 :     }
     631           0 :     FD_COMPILER_MFENCE(); /* Prevent reordering of the slot read to before the atomic lock
     632             :                              (slot) has been fully released by the writer. */
     633           0 :     if( FD_LIKELY( slotcache->slot==slot ) ) {
     634           0 :       *out_slotcache = slotcache;
     635           0 :       return FD_TXNCACHE_FIND_FOUND;
     636           0 :     }
     637           0 :   }
     638           0 :   return FD_TXNCACHE_FIND_FULL;
     639           0 : }
     640             : 
     641             : static int
     642             : fd_txncache_find_slot_blockhash( fd_txncache_private_slotcache_t *       slotcache,
     643             :                                  uchar const                             blockhash[ static 32 ],
     644           0 :                                  fd_txncache_private_slotblockcache_t ** out_slotblockcache ) {
     645           0 :   ulong hash = FD_LOAD( ulong, blockhash );
     646           0 :   for( ulong i=0UL; i<300UL; i++ ) {
     647           0 :     ulong slotblockcache_idx = (hash+i)%300UL;
     648           0 :     fd_txncache_private_slotblockcache_t * slotblockcache = &slotcache->blockcache[ slotblockcache_idx ];
     649           0 :     if( FD_UNLIKELY( slotblockcache->txnhash_offset==ULONG_MAX ) ) {
     650           0 :       *out_slotblockcache = slotblockcache;
     651           0 :       return FD_TXNCACHE_FIND_FOUNDEMPTY;
     652           0 :     }
     653           0 :     while( FD_UNLIKELY( slotblockcache->txnhash_offset==ULONG_MAX-1UL ) ) {
     654           0 :       FD_SPIN_PAUSE();
     655           0 :     }
     656           0 :     FD_COMPILER_MFENCE(); /* Prevent reordering of the blockhash read to before the atomic lock
     657             :                              (txnhash_offset) has been fully released by the writer. */
     658           0 :     if( FD_LIKELY( !memcmp( slotblockcache->blockhash, blockhash, 32UL ) ) ) {
     659           0 :       *out_slotblockcache = slotblockcache;
     660           0 :       return FD_TXNCACHE_FIND_FOUND;
     661           0 :     }
     662           0 :   }
     663           0 :   return FD_TXNCACHE_FIND_FULL;
     664           0 : }
     665             : 
     666             : static int
     667             : fd_txncache_ensure_blockcache( fd_txncache_t *                     tc,
     668             :                                uchar const                         blockhash[ static 32 ],
     669           0 :                                fd_txncache_private_blockcache_t ** out_blockcache ) {
     670           0 :   for(;;) {
     671           0 :     int blockcache_find = fd_txncache_find_blockhash( tc, blockhash, 1, out_blockcache );
     672           0 :     if( FD_LIKELY( blockcache_find==FD_TXNCACHE_FIND_FOUND ) ) return 1;
     673           0 :     else if( FD_UNLIKELY( blockcache_find==FD_TXNCACHE_FIND_FULL ) ) return 0;
     674             : 
     675           0 :     if( FD_LIKELY( FD_ATOMIC_CAS( &(*out_blockcache)->max_slot, FD_TXNCACHE_EMPTY_ENTRY, FD_TXNCACHE_TEMP_ENTRY ) ||
     676           0 :         FD_ATOMIC_CAS( &(*out_blockcache)->max_slot, FD_TXNCACHE_TOMBSTONE_ENTRY, FD_TXNCACHE_TEMP_ENTRY ) ) ) {
     677           0 :       memcpy( (*out_blockcache)->blockhash, blockhash, 32UL );
     678           0 :       memset( (*out_blockcache)->heads, 0xFF, FD_TXNCACHE_BLOCKCACHE_MAP_CNT*sizeof(uint) );
     679           0 :       (*out_blockcache)->pages_cnt      = 0;
     680           0 :       (*out_blockcache)->txnhash_offset = 0UL;
     681           0 :       memset( (*out_blockcache)->pages, 0xFF, tc->txnpages_per_blockhash_max*sizeof(uint) );
     682           0 :       FD_COMPILER_MFENCE();
     683             :       /* Set it to max unreserved value possible */
     684           0 :       (*out_blockcache)->max_slot    = ULONG_MAX-3UL;
     685           0 :       return 1;
     686           0 :     }
     687           0 :     FD_SPIN_PAUSE();
     688           0 :   }
     689           0 : }
     690             : 
     691             : static int
     692             : fd_txncache_ensure_slotcache( fd_txncache_t *                    tc,
     693             :                               ulong                              slot,
     694           0 :                               fd_txncache_private_slotcache_t ** out_slotcache ) {
     695           0 :   for(;;) {
     696           0 :     int slotcache_find = fd_txncache_find_slot( tc, slot, 1, out_slotcache );
     697           0 :     if( FD_LIKELY( slotcache_find==FD_TXNCACHE_FIND_FOUND ) ) return 1;
     698           0 :     else if( FD_UNLIKELY( slotcache_find==FD_TXNCACHE_FIND_FULL ) ) return 0;
     699             : 
     700           0 :     if( FD_LIKELY( FD_ATOMIC_CAS( &(*out_slotcache)->slot, FD_TXNCACHE_EMPTY_ENTRY, FD_TXNCACHE_TEMP_ENTRY ) ||
     701           0 :         FD_ATOMIC_CAS( &(*out_slotcache)->slot, FD_TXNCACHE_TOMBSTONE_ENTRY, FD_TXNCACHE_TEMP_ENTRY ) ) ) {
     702           0 :       for( ulong i=0UL; i<300UL; i++ ) {
     703           0 :         (*out_slotcache)->blockcache[ i ].txnhash_offset = ULONG_MAX;
     704           0 :       }
     705           0 :       FD_COMPILER_MFENCE();
     706           0 :       (*out_slotcache)->slot = slot;
     707           0 :       return 1;
     708           0 :     }
     709           0 :     FD_SPIN_PAUSE();
     710           0 :   }
     711           0 : }
     712             : 
     713             : static int
     714             : fd_txncache_ensure_slotblockcache( fd_txncache_private_slotcache_t *       slotcache,
     715             :                                    uchar const                             blockhash[ static 32 ],
     716           0 :                                    fd_txncache_private_slotblockcache_t ** out_slotblockcache ) {
     717           0 :   for(;;) {
     718           0 :     int slotblockcache_find = fd_txncache_find_slot_blockhash( slotcache, blockhash, out_slotblockcache );
     719           0 :     if( FD_LIKELY( slotblockcache_find==FD_TXNCACHE_FIND_FOUND ) ) return 1;
     720           0 :     else if( FD_UNLIKELY( slotblockcache_find==FD_TXNCACHE_FIND_FULL ) ) return 0;
     721             : 
     722           0 :     if( FD_LIKELY( FD_ATOMIC_CAS( &(*out_slotblockcache)->txnhash_offset, ULONG_MAX, ULONG_MAX-1UL ) ) ) {
     723           0 :       memcpy( (*out_slotblockcache)->blockhash, blockhash, 32UL );
     724           0 :       memset( (*out_slotblockcache)->heads, 0xFF, FD_TXNCACHE_SLOTCACHE_MAP_CNT*sizeof(uint) );
     725           0 :       FD_COMPILER_MFENCE();
     726           0 :       (*out_slotblockcache)->txnhash_offset = 0UL;
     727           0 :       return 1;
     728           0 :     }
     729           0 :     FD_SPIN_PAUSE();
     730           0 :   }
     731           0 : }
     732             : 
     733             : static fd_txncache_private_txnpage_t *
     734             : fd_txncache_ensure_txnpage( fd_txncache_t *                    tc,
     735           0 :                             fd_txncache_private_blockcache_t * blockcache ) {
     736           0 :   ushort page_cnt = blockcache->pages_cnt;
     737           0 :   if( FD_UNLIKELY( page_cnt>tc->txnpages_per_blockhash_max ) ) return NULL;
     738           0 :   fd_txncache_private_txnpage_t * txnpages = fd_txncache_get_txnpages( tc );
     739             : 
     740           0 :   if( FD_LIKELY( page_cnt ) ) {
     741           0 :     uint txnpage_idx = blockcache->pages[ page_cnt-1 ];
     742           0 :     ushort txnpage_free = txnpages[ txnpage_idx ].free;
     743           0 :     if( FD_LIKELY( txnpage_free ) ) return &txnpages[ txnpage_idx ];
     744           0 :   }
     745             : 
     746           0 :   if( FD_UNLIKELY( page_cnt==tc->txnpages_per_blockhash_max ) ) return NULL;
     747           0 :   if( FD_LIKELY( FD_ATOMIC_CAS( &blockcache->pages[ page_cnt ], UINT_MAX, UINT_MAX-1UL )==UINT_MAX ) ) {
     748           0 :     ulong txnpages_free_cnt = tc->txnpages_free_cnt;
     749           0 :     for(;;) {
     750           0 :       if( FD_UNLIKELY( !txnpages_free_cnt ) ) return NULL;
     751           0 :       ulong old_txnpages_free_cnt = FD_ATOMIC_CAS( &tc->txnpages_free_cnt, (uint)txnpages_free_cnt, (uint)(txnpages_free_cnt-1UL) );
     752           0 :       if( FD_LIKELY( old_txnpages_free_cnt==txnpages_free_cnt ) ) break;
     753           0 :       txnpages_free_cnt = old_txnpages_free_cnt;
     754           0 :       FD_SPIN_PAUSE();
     755           0 :     }
     756           0 :     uint * txnpages_free = fd_txncache_get_txnpages_free( tc );
     757             : 
     758           0 :     uint txnpage_idx = txnpages_free[ txnpages_free_cnt-1UL ];
     759           0 :     fd_txncache_private_txnpage_t * txnpage = &txnpages[ txnpage_idx ];
     760           0 :     txnpage->free = FD_TXNCACHE_TXNS_PER_PAGE;
     761           0 :     FD_COMPILER_MFENCE();
     762           0 :     blockcache->pages[ page_cnt ] = txnpage_idx;
     763           0 :     FD_COMPILER_MFENCE();
     764           0 :     blockcache->pages_cnt = (ushort)(page_cnt+1);
     765           0 :     return txnpage;
     766           0 :   } else {
     767           0 :     uint txnpage_idx = blockcache->pages[ page_cnt ];
     768           0 :     while( FD_UNLIKELY( txnpage_idx>=UINT_MAX-1UL ) ) {
     769           0 :       txnpage_idx = blockcache->pages[ page_cnt ];
     770           0 :       FD_SPIN_PAUSE();
     771           0 :     }
     772           0 :     return &txnpages[ txnpage_idx ];
     773           0 :   }
     774           0 : }
     775             : 
     776             : static int
     777             : fd_txncache_insert_txn( fd_txncache_t *                        tc,
     778             :                         fd_txncache_private_blockcache_t *     blockcache,
     779             :                         fd_txncache_private_slotblockcache_t * slotblockcache,
     780             :                         fd_txncache_private_txnpage_t *        txnpage,
     781           0 :                         fd_txncache_insert_t const *           txn ) {
     782           0 :   fd_txncache_private_txnpage_t * txnpages = fd_txncache_get_txnpages( tc );
     783           0 :   ulong txnpage_idx = (ulong)(txnpage - txnpages);
     784             : 
     785           0 :   for(;;) {
     786           0 :     ushort txnpage_free = txnpage->free;
     787           0 :     if( FD_UNLIKELY( !txnpage_free ) ) return 0;
     788           0 :     if( FD_UNLIKELY( FD_ATOMIC_CAS( &txnpage->free, txnpage_free, txnpage_free-1UL )!=txnpage_free ) ) continue;
     789             : 
     790           0 :     ulong txn_idx = FD_TXNCACHE_TXNS_PER_PAGE-txnpage_free;
     791           0 :     ulong txnhash_offset = blockcache->txnhash_offset;
     792           0 :     ulong txnhash = FD_LOAD( ulong, txn->txnhash+txnhash_offset );
     793           0 :     memcpy( txnpage->txns[ txn_idx ]->txnhash, txn->txnhash+txnhash_offset, 20UL );
     794           0 :     txnpage->txns[ txn_idx ]->result = *txn->result;
     795           0 :     txnpage->txns[ txn_idx ]->slot   = txn->slot;
     796           0 :     FD_COMPILER_MFENCE();
     797             : 
     798           0 :     for(;;) {
     799           0 :       ulong txn_bucket = txnhash%FD_TXNCACHE_BLOCKCACHE_MAP_CNT;
     800           0 :       uint head = blockcache->heads[ txn_bucket ];
     801           0 :       txnpage->txns[ txn_idx ]->blockcache_next = head;
     802           0 :       FD_COMPILER_MFENCE();
     803           0 :       if( FD_LIKELY( FD_ATOMIC_CAS( &blockcache->heads[ txn_bucket ], head, (uint)(FD_TXNCACHE_TXNS_PER_PAGE*txnpage_idx+txn_idx) )==head ) ) break;
     804           0 :       FD_SPIN_PAUSE();
     805           0 :     }
     806             : 
     807           0 :     for(;;) {
     808           0 :       ulong txn_bucket = txnhash%FD_TXNCACHE_SLOTCACHE_MAP_CNT;
     809           0 :       uint head = slotblockcache->heads[ txn_bucket ];
     810           0 :       txnpage->txns[ txn_idx ]->slotblockcache_next = head;
     811           0 :       FD_COMPILER_MFENCE();
     812           0 :       if( FD_LIKELY( FD_ATOMIC_CAS( &slotblockcache->heads[ txn_bucket ], head, (uint)(FD_TXNCACHE_TXNS_PER_PAGE*txnpage_idx+txn_idx) )==head ) ) break;
     813           0 :       FD_SPIN_PAUSE();
     814           0 :     }
     815             : 
     816           0 :     for(;;) {
     817           0 :       ulong max_slot = blockcache->max_slot;
     818             : 
     819           0 :       if( FD_UNLIKELY( txn->slot<=max_slot && max_slot != ULONG_MAX-3UL) ) break;
     820           0 :       if( FD_LIKELY( FD_ATOMIC_CAS( &blockcache->max_slot, max_slot, txn->slot )==max_slot ) ) break;
     821           0 :       FD_SPIN_PAUSE();
     822           0 :     }
     823           0 :     return 1;
     824           0 :   }
     825           0 : }
     826             : 
     827             : int
     828             : fd_txncache_insert_batch( fd_txncache_t *              tc,
     829             :                           fd_txncache_insert_t const * txns,
     830           0 :                           ulong                        txns_cnt ) {
     831           0 :   fd_rwlock_read( tc->lock );
     832             : 
     833           0 :   for( ulong i=0UL; i<txns_cnt; i++ ) {
     834           0 :     fd_txncache_private_blockcache_t * blockcache;
     835           0 :     if( FD_UNLIKELY( !fd_txncache_ensure_blockcache( tc, txns[ i ].blockhash, &blockcache ) ) ) {
     836           0 :       FD_LOG_WARNING(( "no blockcache found" ));
     837           0 :       goto unlock_fail;
     838           0 :     }
     839             : 
     840           0 :     fd_txncache_private_slotcache_t * slotcache;
     841           0 :     if( FD_UNLIKELY( !fd_txncache_ensure_slotcache( tc, txns[ i ].slot, &slotcache ) ) ) {
     842           0 :       FD_LOG_WARNING(( "no slotcache found" ));
     843           0 :       goto unlock_fail;
     844           0 :     }
     845             : 
     846           0 :     fd_txncache_private_slotblockcache_t * slotblockcache;
     847           0 :     if( FD_UNLIKELY( !fd_txncache_ensure_slotblockcache( slotcache, txns[ i ].blockhash, &slotblockcache ) ) ) {
     848           0 :       FD_LOG_WARNING(( "no slotblockcache found" ));
     849           0 :       goto unlock_fail;
     850           0 :     }
     851             : 
     852           0 :     for(;;) {
     853           0 :       fd_txncache_private_txnpage_t * txnpage = fd_txncache_ensure_txnpage( tc, blockcache );
     854           0 :       if( FD_UNLIKELY( !txnpage ) ) {
     855           0 :         goto unlock_fail;
     856           0 :         FD_LOG_WARNING(( "no txnpage found" ));
     857           0 :       }
     858             : 
     859           0 :       int success = fd_txncache_insert_txn( tc, blockcache, slotblockcache, txnpage, &txns[ i ] );
     860           0 :       if( FD_LIKELY( success ) ) break;
     861           0 :       FD_SPIN_PAUSE();
     862           0 :     }
     863           0 :   }
     864             : 
     865           0 :   fd_rwlock_unread( tc->lock );
     866           0 :   return 1;
     867             : 
     868           0 : unlock_fail:
     869           0 :   fd_rwlock_unread( tc->lock );
     870           0 :   return 0;
     871           0 : }
     872             : 
     873             : void
     874             : fd_txncache_query_batch( fd_txncache_t *             tc,
     875             :                          fd_txncache_query_t const * queries,
     876             :                          ulong                       queries_cnt,
     877             :                          void *                      query_func_ctx,
     878             :                          int ( * query_func )( ulong slot, void * ctx ),
     879           0 :                          int *                       out_results ) {
     880           0 :   fd_rwlock_read( tc->lock );
     881           0 :   fd_txncache_private_txnpage_t * txnpages = fd_txncache_get_txnpages( tc );
     882           0 :   for( ulong i=0UL; i<queries_cnt; i++ ) {
     883           0 :     out_results[ i ] = 0;
     884             : 
     885           0 :     fd_txncache_query_t const * query = &queries[ i ];
     886           0 :     fd_txncache_private_blockcache_t * blockcache;
     887           0 :     int result = fd_txncache_find_blockhash( tc, query->blockhash, 0, &blockcache );
     888             : 
     889           0 :     if( FD_UNLIKELY( result!=FD_TXNCACHE_FIND_FOUND ) ) {
     890           0 :       continue;
     891           0 :     }
     892             : 
     893           0 :     ulong txnhash_offset = blockcache->txnhash_offset;
     894           0 :     ulong head_hash = FD_LOAD( ulong, query->txnhash+txnhash_offset ) % FD_TXNCACHE_BLOCKCACHE_MAP_CNT;
     895           0 :     for( uint head=blockcache->heads[ head_hash ]; head!=UINT_MAX; head=txnpages[ head/FD_TXNCACHE_TXNS_PER_PAGE ].txns[ head%FD_TXNCACHE_TXNS_PER_PAGE ]->blockcache_next ) {
     896           0 :       fd_txncache_private_txn_t * txn = txnpages[ head/FD_TXNCACHE_TXNS_PER_PAGE ].txns[ head%FD_TXNCACHE_TXNS_PER_PAGE ];
     897           0 :       if( FD_LIKELY( !memcmp( query->txnhash+txnhash_offset, txn->txnhash, 20UL ) ) ) {
     898           0 :         if( FD_LIKELY( !query_func || query_func( txn->slot, query_func_ctx ) ) ) {
     899           0 :           out_results[ i ] = 1;
     900           0 :           break;
     901           0 :         }
     902           0 :       }
     903           0 :     }
     904           0 :   }
     905             : 
     906           0 :   fd_rwlock_unread( tc->lock );
     907           0 : }
     908             : 
     909             : int
     910             : fd_txncache_snapshot( fd_txncache_t * tc,
     911             :                       void *          ctx,
     912           0 :                       int ( * write )( uchar const * data, ulong data_sz, void * ctx ) ) {
     913           0 :   if( !write ) {
     914           0 :     FD_LOG_WARNING(("No write method provided to snapshotter"));
     915           0 :     return 1;
     916           0 :   }
     917           0 :   fd_rwlock_read( tc->lock );
     918             : 
     919           0 :   fd_txncache_private_txnpage_t * txnpages = fd_txncache_get_txnpages( tc );
     920           0 :   ulong * root_slots = fd_txncache_get_root_slots( tc );
     921           0 :   for( ulong i=0UL; i<tc->root_slots_cnt; i++ ) {
     922           0 :     ulong slot = root_slots[ i ];
     923             : 
     924           0 :     fd_txncache_private_slotcache_t * slotcache;
     925           0 :     if( FD_UNLIKELY( FD_TXNCACHE_FIND_FOUND!=fd_txncache_find_slot( tc, slot, 0, &slotcache ) ) ) continue;
     926             : 
     927           0 :     for( ulong j=0UL; j<300UL; j++ ) {
     928           0 :       fd_txncache_private_slotblockcache_t * slotblockcache = &slotcache->blockcache[ j ];
     929           0 :       if( FD_UNLIKELY( slotblockcache->txnhash_offset>=ULONG_MAX-1UL ) ) continue;
     930             : 
     931           0 :       for( ulong k=0UL; k<FD_TXNCACHE_SLOTCACHE_MAP_CNT; k++ ) {
     932           0 :         uint head = slotblockcache->heads[ k ];
     933           0 :         for( ; head!=UINT_MAX; head=txnpages[ head/FD_TXNCACHE_TXNS_PER_PAGE ].txns[ head%FD_TXNCACHE_TXNS_PER_PAGE ]->slotblockcache_next ) {
     934           0 :           fd_txncache_private_txn_t * txn = txnpages[ head/FD_TXNCACHE_TXNS_PER_PAGE ].txns[ head%FD_TXNCACHE_TXNS_PER_PAGE ];
     935             : 
     936           0 :           fd_txncache_snapshot_entry_t entry = {
     937           0 :             .slot      = slot,
     938           0 :             .txn_idx   = slotblockcache->txnhash_offset,
     939           0 :             .result    = txn->result
     940           0 :           };
     941           0 :           fd_memcpy( entry.blockhash, slotblockcache->blockhash, 32 );
     942           0 :           fd_memcpy( entry.txnhash, txn->txnhash, 20 );
     943           0 :           int err = write( (uchar*)&entry, sizeof(fd_txncache_snapshot_entry_t), ctx );
     944           0 :           if( err ) {
     945           0 :             fd_rwlock_unread( tc->lock );
     946           0 :             return err;
     947           0 :           }
     948           0 :         }
     949           0 :       }
     950           0 :     }
     951           0 :   }
     952             : 
     953           0 :   fd_rwlock_unread( tc->lock );
     954           0 :   return 0;
     955           0 : }
     956             : 
     957             : int
     958             : fd_txncache_set_txnhash_offset( fd_txncache_t * tc,
     959             :                                 ulong slot,
     960             :                                 uchar blockhash[ 32 ],
     961           0 :                                 ulong txnhash_offset ) {
     962           0 :   fd_rwlock_read( tc->lock );
     963           0 :   fd_txncache_private_blockcache_t * blockcache;
     964           0 :   if( FD_UNLIKELY( !fd_txncache_ensure_blockcache( tc, blockhash, &blockcache ) ) ) goto unlock_fail;
     965             : 
     966           0 :   blockcache->txnhash_offset = txnhash_offset;
     967           0 :   fd_txncache_private_slotcache_t * slotcache;
     968           0 :   if( FD_UNLIKELY( !fd_txncache_ensure_slotcache( tc, slot, &slotcache ) ) ) goto unlock_fail;
     969             : 
     970           0 :   fd_txncache_private_slotblockcache_t * slotblockcache;
     971           0 :   if( FD_UNLIKELY( !fd_txncache_ensure_slotblockcache( slotcache, blockhash, &slotblockcache ) ) ) goto unlock_fail;
     972           0 :   slotblockcache->txnhash_offset = txnhash_offset;
     973             : 
     974           0 :   fd_rwlock_unread( tc->lock );
     975           0 :   return 0;
     976             : 
     977           0 : unlock_fail:
     978           0 :   fd_rwlock_unread( tc->lock );
     979           0 :   return 1;
     980           0 : }
     981             : 
     982             : int
     983             : fd_txncache_is_rooted_slot( fd_txncache_t * tc,
     984           0 :                             ulong slot ) {
     985           0 :   fd_rwlock_read( tc->lock );
     986             : 
     987           0 :   ulong * root_slots = fd_txncache_get_root_slots( tc );
     988           0 :   for( ulong idx=0UL; idx<tc->root_slots_cnt; idx++ ) {
     989           0 :     if( FD_UNLIKELY( root_slots[ idx ]==slot ) ) {
     990           0 :       fd_rwlock_unread( tc->lock );
     991           0 :       return 1;
     992           0 :     }
     993           0 :     if( FD_UNLIKELY( root_slots[ idx ]>slot ) ) break;
     994           0 :   }
     995             : 
     996           0 :   fd_rwlock_unread( tc->lock );
     997           0 :   return 0;
     998           0 : }

Generated by: LCOV version 1.14