LCOV - code coverage report
Current view: top level - flamenco/runtime - fd_txncache_private.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 31 32 96.9 %
Date: 2026-10-16 04:29:28 Functions: 7 36 19.4 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_flamenco_runtime_fd_txncache_private_h
       2             : #define HEADER_fd_src_flamenco_runtime_fd_txncache_private_h
       3             : 
       4             : #include "fd_txncache_shmem.h"
       5             : #include "../fd_flamenco_base.h"
       6             : #include "../fd_rwlock.h"
       7             : 
       8             : /* The number of transactions in each page.  This needs to be high
       9             :    enough to amoritze the cost of caller code reserving pages from,
      10             :    and returning pages to the pool, but not so high that the memory
      11             :    wasted from blockhashes with only one transaction is significant. */
      12             : 
      13   215691689 : #define FD_TXNCACHE_TXNS_PER_PAGE (8192UL)
      14             : 
      15             : /* The maximum distance a transaction blockhash reference can be
      16             :    (inclusive).  For example, if no slots were skipped, and the value is
      17             :    151, slot 300 is allowed to reference blockhashes from slots
      18             :    [149, 300). */
      19         933 : #define FD_TXNCACHE_MAX_BLOCKHASH_DISTANCE (151UL)
      20             : 
      21             : /* The global txn index (page*FD_TXNCACHE_TXNS_PER_PAGE+txn) is a uint
      22             :    with UINT_MAX as the null link, which bounds the txnpage pool. */
      23             : 
      24             : #define FD_TXNCACHE_MAX_TXNPAGES (UINT_MAX/FD_TXNCACHE_TXNS_PER_PAGE)
      25             : 
      26             : struct __attribute__((packed)) fd_txncache_single_txn {
      27             :   uint  blockcache_next; /* Pointer to the next element in the blockcache hash chain containing this entry from the pool. */
      28             :   uint  generation;      /* The generation of the fork when this transaction was inserted.  Used to
      29             :                             determine if the transaction is still valid for a fork that might have
      30             :                             advanced since insertion. */
      31             : 
      32             :   fd_txncache_fork_id_t fork_id; /* Fork that the transaction was executed on.  A transaction might be in the cache
      33             :                                     multiple times if it was executed on multiple forks. */
      34             :   uchar txnhash[ 20UL ]; /* The transaction message hash, truncated to 20 bytes.  The hash is not always the first 20
      35             :                             bytes, but is 20 bytes starting at some arbitrary offset given by the txnhash_offset value
      36             :                             of the containing blockcache entry. */
      37             : };
      38             : 
      39             : typedef struct fd_txncache_single_txn fd_txncache_single_txn_t;
      40             : 
      41             : FD_STATIC_ASSERT( sizeof(fd_txncache_single_txn_t)==30UL, fd_txncache_single_txn );
      42             : 
      43             : struct fd_txncache_txnpage {
      44             :   ushort                   free; /* The number of free txn entries in this page. */
      45             :   fd_txncache_single_txn_t txns[ FD_TXNCACHE_TXNS_PER_PAGE][ 1 ]; /* The transactions in the page. */
      46             : };
      47             : 
      48             : typedef struct fd_txncache_txnpage fd_txncache_txnpage_t;
      49             : 
      50             : struct fd_txncache_blockcache_shmem {
      51             :   fd_txncache_fork_id_t parent_id;
      52             :   fd_txncache_fork_id_t child_id;
      53             :   fd_txncache_fork_id_t sibling_id;
      54             : 
      55             :   int frozen;            /* This is used to enforce invariants on the caller of the txncache.
      56             :                             -1: invalid
      57             :                              0: active
      58             :                              1: semi frozen, only happens during snapshot load
      59             :                              2: frozen, should not be modified */
      60             : 
      61             :   uint generation;
      62             : 
      63             :   fd_hash_t blockhash;   /* The blockhash that this entry is for. */
      64             :   ulong txnhash_offset;  /* To save memory, the Agave validator decided to truncate the hash of transactions stored in
      65             :                             this memory to 20 bytes rather than 32 bytes.  The bytes used are not the first 20 as you
      66             :                             might expect, but instead the first 20 starting at some random offset into the transaction
      67             :                             hash (starting between 0 and len(hash)-20, a/k/a 44 for signatures, and 12 for hashes).
      68             : 
      69             :                             In an unfortunate turn, the offset is also propagated to peers via. snapshot responses,
      70             :                             which only communicate the offset and the respective 20 bytes.  To make sure we are
      71             :                             deduplicating incoming transactions correctly, we must replicate this system even though
      72             :                             it would be easier to just always take the first 20 bytes.  For transactions that we
      73             :                             insert into the cache ourselves, we do just always use a key_offset of zero, so this is
      74             :                             only nonzero when constructed form a peer snapshot. */
      75             : 
      76             :   ulong pages_cnt;       /* The number of txnpages currently in use to store the transactions in this blockcache. */
      77             : 
      78             :   struct {
      79             :     ulong next;
      80             :   } pool;
      81             : 
      82             :   struct {
      83             :     ulong next;
      84             :   } slist;
      85             : 
      86             :   struct {
      87             :     ulong next;
      88             :     ulong prev;
      89             :   } blockhash_map;
      90             : };
      91             : 
      92             : typedef struct fd_txncache_blockcache_shmem fd_txncache_blockcache_shmem_t;
      93             : 
      94             : #define POOL_NAME       blockcache_pool
      95             : #define POOL_T          fd_txncache_blockcache_shmem_t
      96             : #define POOL_IDX_T      ulong
      97       29862 : #define POOL_NEXT       pool.next
      98             : #define POOL_IMPL_STYLE 1
      99             : #include "../../util/tmpl/fd_pool.c"
     100             : 
     101             : #define MAP_NAME               blockhash_map
     102             : #define MAP_KEY                blockhash
     103             : #define MAP_ELE_T              fd_txncache_blockcache_shmem_t
     104             : #define MAP_KEY_T              fd_hash_t
     105             : #define MAP_PREV               blockhash_map.prev
     106           0 : #define MAP_NEXT               blockhash_map.next
     107    12772482 : #define MAP_KEY_EQ(k0,k1)      fd_hash_eq( k0, k1 )
     108    12780651 : #define MAP_KEY_HASH(key,seed) (__extension__({ (void)(seed); fd_ulong_load_8_fast( (key)->uc ); }))
     109             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
     110             : #define MAP_MULTI              1
     111             : #define MAP_IMPL_STYLE         1
     112             : #include "../../util/tmpl/fd_map_chain.c"
     113             : 
     114             : #define SLIST_NAME       root_slist
     115             : #define SLIST_ELE_T      fd_txncache_blockcache_shmem_t
     116         906 : #define SLIST_IDX_T      ulong
     117       65261 : #define SLIST_NEXT       slist.next
     118             : #define SLIST_IMPL_STYLE 1
     119             : #include "../../util/tmpl/fd_slist.c"
     120             : 
     121             : #define SET_NAME descends_set
     122             : #include "../../util/tmpl/fd_set_dynamic.c"
     123             : 
     124             : struct __attribute__((aligned(FD_TXNCACHE_SHMEM_ALIGN))) fd_txncache_shmem_private {
     125             :   /* The txncache is a concurrent structure and will be accessed by multiple threads
     126             :      concurrently.  Insertion and querying only take a read lock as they can be done
     127             :      lockless but all other operations will take a write lock internally.
     128             : 
     129             :      The lock needs to be aligned to 128 bytes to avoid false sharing with other
     130             :      data that might be on the same cache line. */
     131             :   fd_rwlock_t lock[ 1 ] __attribute__((aligned(128UL)));
     132             : 
     133             :   ulong  txn_per_slot_max;
     134             :   ulong  active_slots_max;
     135             :   ulong  bucket_cnt;      /* Hash buckets per blockcache.  Decoupled from txn_per_slot_max (load
     136             :                              factor 8) to reduce the heads arrays' memory footprint. */
     137             :   ulong  txnpages_per_blockhash_max;
     138             :   ulong  max_txnpages;
     139             :   ulong  txnpage_idx_sz;  /* Element size of every txnpage index array (blockcache->pages,
     140             :                              txnpages_free, scratch_pages), see fd_txncache_txnpage_idx_sz. */
     141             : 
     142             :   uint  blockcache_generation; /* Incremented for every blockcache. */
     143             :   ulong txnpages_free_cnt; /* The number of pages in the txnpages that are not currently in use. */
     144             : 
     145             :   /* Helps the snapshot producer walk the txncache lock-free and detect
     146             :      relevant changes to the txncache.  mutation_gen covers transaction
     147             :      storage, and root_gen covers the root history.  mutation_gen is odd
     148             :      when the storage is being modified in ways that could affect the
     149             :      snapshot, such as during reset, root advancement, and compaction,
     150             :      and bumped once the mutation is done.  root_gen increments on root
     151             :      advancement and reset.
     152             : 
     153             :      For the snapshot producer, fd_txncache_writer, mutation_gen changes
     154             :      trigger retry in the writer, and root_gen changes are treated as
     155             :      fatal invariant violation because the replay tile is expected to
     156             :      pause root history changes while a snapshot is in progress.
     157             : 
     158             :      Note that attach/cancel of the unrooted frontier does not bump
     159             :      mutation_gen.  Suppose an unrooted child executed transaction T
     160             :      referencing an old rooted blockhash.  Before cancellation, T is
     161             :      unrooted, so the writer skips it.  After cancellation, T is stale,
     162             :      so the writer still skips it.  So the desired snapshot did not
     163             :      change.
     164             : 
     165             :      A note on concurrency ... Accesses to the two generation numbers
     166             :      are race free under C11.  Every store after initialization is
     167             :      atomic and pairs with an atomic load in the snapshot producer tile.
     168             :      The loads in the write locked mutators need no atomicity because
     169             :      every store is made under the write lock so said loads are already
     170             :      ordered properly with respect to the writes.  In contrast, the
     171             :      transaction entries the snapshot walker loads and the txncache
     172             :      stores are plain on both sides, and the blockcache bucket heads are
     173             :      stored plainly by compaction while the walker loads them with an
     174             :      acquire load.  Both are data races under C11.  The walker is
     175             :      nevertheless correct, given that the compiler emits the plain entry
     176             :      accesses as ordinary loads and stores.  FD_HW_MFENCE_ST in
     177             :      fd_txncache_mutation_begin orders the odd store before the entry
     178             :      stores and FD_HW_MFENCE_LD in the walker orders the entry loads
     179             :      before the generation re-check, so an entry load that observed a
     180             :      store made inside a mutation section is followed by a re-check that
     181             :      fails, and whatever was read, torn or otherwise, is discarded. */
     182             :   ulong mutation_gen;
     183             :   ulong root_gen;
     184             : 
     185             :   ulong root_cnt;
     186             :   root_slist_t root_ll[1]; /* A singly linked list of the forks that are roots of fork chains.  The tail is the
     187             :                               most recently added root, the head is the oldest root.  This is used to identify
     188             :                               which forks can be pruned when a new root is added. */
     189             : 
     190             :   ulong seed;
     191             :   ulong magic; /* ==FD_TXNCACHE_SHMEM_MAGIC */
     192             : };
     193             : 
     194             : struct blockcache {
     195             :   fd_txncache_blockcache_shmem_t * shmem;
     196             : 
     197             :   uint * heads;          /* The hash table for the blockhash.  Each entry is a pointer to the head of a linked list of
     198             :                             transactions that reference this blockhash.  As we add transactions to the bucket, the head
     199             :                             pointer is updated to the new item, and the new item is pointed to the previous head. */
     200             :   void * pages;          /* A list of the txnpages containing the transactions for this blockcache, elements of
     201             :                             shmem->txnpage_idx_sz bytes (see fd_txncache_txnpage_idx_ld). */
     202             : 
     203             :   descends_set_t * descends; /* Each fork can descend from other forks in the txncache, and this bit vector contains one
     204             :                                 value for each fork in the txncache.  If this fork descends from some other fork F, then
     205             :                                 the bit at index F in descends[] is set. */
     206             : };
     207             : 
     208             : typedef struct blockcache blockcache_t;
     209             : 
     210             : struct fd_txncache_private {
     211             :   fd_txncache_shmem_t * shmem;
     212             : 
     213             :   fd_txncache_blockcache_shmem_t * blockcache_shmem_pool;
     214             :   blockcache_t * blockcache_pool;
     215             :   blockhash_map_t * blockhash_map;
     216             : 
     217             :   void * txnpages_free;             /* The index in the txnpages array that is free, for each of the free pages.
     218             :                                        Elements are shmem->txnpage_idx_sz bytes, as are scratch_pages below. */
     219             : 
     220             :   fd_txncache_txnpage_t * txnpages; /* The actual storage for the transactions.  The blockcache points to these
     221             :                                        pages when storing transactions.  Transaction are grouped into pages of
     222             :                                        size 16384 to make certain allocation and deallocation operations faster
     223             :                                        (just the pages are acquired/released, rather than each txn). */
     224             : 
     225             :   void * scratch_pages;
     226             :   uint * scratch_heads;
     227             :   fd_txncache_txnpage_t * scratch_txnpage;
     228             : };
     229             : 
     230             : FD_PROTOTYPES_BEGIN
     231             : 
     232             : /* Use these to bracket a write locked change that is visible to the
     233             :    lock-free snapshot chain walker (fd_txncache_writer).  mutation_gen
     234             :    is odd while a change is in progress.  The walker uses an acquire
     235             :    load of the generation before starting its chain reads and a LoadLoad
     236             :    barrier before each re-check. */
     237             : 
     238             : static inline void
     239        8373 : fd_txncache_mutation_begin( fd_txncache_shmem_t * shmem ) {
     240             :   /* Plain load is fine because the write lock holder is the only writer. */
     241        8373 :   ulong gen = shmem->mutation_gen;
     242        8373 :   FD_TEST_CRIT( !(gen&1UL) );
     243        8373 :   __atomic_store_n( &shmem->mutation_gen, gen+1UL, __ATOMIC_RELAXED );
     244             : 
     245             :   /* The walker does not write/publish any shared state visible to the
     246             :      txncache.  So no need for a StoreLoad barrier here.  Just
     247             :      StoreStore. */
     248        8373 :   FD_HW_MFENCE_ST();
     249        8373 : }
     250             : 
     251             : static inline void
     252        8373 : fd_txncache_mutation_end( fd_txncache_shmem_t * shmem ) {
     253             :   /* Plain load is fine because the write lock holder is the only writer. */
     254        8373 :   ulong gen = shmem->mutation_gen;
     255        8373 :   FD_TEST_CRIT( gen&1UL );
     256        8373 :   __atomic_store_n( &shmem->mutation_gen, gen+1UL, __ATOMIC_RELEASE );
     257        8373 : }
     258             : 
     259             : /* fd_txncache_max_txnpages{,_per_blockhash} return the txnpage pool
     260             :    size and the per blockcache page cap for the given parameters.  The
     261             :    result is not bounded; callers compare against
     262             :    FD_TXNCACHE_MAX_TXNPAGES. */
     263             : 
     264             : FD_FN_CONST ulong
     265             : fd_txncache_max_txnpages_per_blockhash( ulong max_active_slots,
     266             :                                         ulong max_txn_per_slot );
     267             : 
     268             : FD_FN_CONST ulong
     269             : fd_txncache_max_txnpages( ulong max_active_slots,
     270             :                           ulong max_txn_per_slot );
     271             : 
     272             : FD_FN_CONST static inline ulong
     273         372 : fd_txncache_bucket_cnt( ulong max_txn_per_slot ) {
     274         372 :   return fd_ulong_max( 1UL, (max_txn_per_slot+7UL)/8UL );
     275         372 : }
     276             : 
     277             : /* Txnpage indices are stored as ushort, which covers production
     278             :    parameters, and widen to uint only when the pool exceeds the ushort
     279             :    range (development.bench sizing), keeping the production footprint
     280             :    unchanged.  The all-ones value of the width is the null index and
     281             :    all-ones minus one is the allocation-in-progress flag, so memset 0xFF
     282             :    nulls an array of either width.  ld/st access an index array of the
     283             :    given element size. */
     284             : 
     285             : FD_FN_CONST static inline ulong
     286         372 : fd_txncache_txnpage_idx_sz( ulong max_txnpages ) {
     287         372 :   return max_txnpages>USHORT_MAX-2UL ? sizeof(uint) : sizeof(ushort);
     288         372 : }
     289             : 
     290             : static inline ulong
     291             : fd_txncache_txnpage_idx_ld( ulong        idx_sz,
     292             :                             void const * idx,
     293    12778011 :                             ulong        i ) {
     294    12778011 :   return idx_sz==sizeof(uint) ? (ulong)((uint const *)idx)[ i ] : (ulong)((ushort const *)idx)[ i ];
     295    12778011 : }
     296             : 
     297             : static inline void
     298             : fd_txncache_txnpage_idx_st( ulong  idx_sz,
     299             :                             void * idx,
     300             :                             ulong  i,
     301     1236999 :                             ulong  val ) {
     302     1236999 :   if( idx_sz==sizeof(uint) ) ((uint *)idx)[ i ] = (uint)val;
     303     1236999 :   else                       ((ushort *)idx)[ i ] = (ushort)val;
     304     1236999 : }
     305             : 
     306             : FD_PROTOTYPES_END
     307             : 
     308             : #endif /* HEADER_fd_src_flamenco_runtime_fd_txncache_private_h */

Generated by: LCOV version 1.14