LCOV - code coverage report
Current view: top level - disco/store - fd_store.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 54 69 78.3 %
Date: 2025-08-05 05:04:49 Functions: 25 253 9.9 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_discof_repair_fd_store_h
       2             : #define HEADER_fd_src_discof_repair_fd_store_h
       3             : 
       4             : /* fd_store is a high-performance in-memory storage engine for shreds as
       5             :    they are received from the network.
       6             : 
       7             :    The elements in the store themselves are not shreds, but FEC set
       8             :    payloads.  Briefly, FEC sets are groupings of shreds that encode the
       9             :    same data but provide redundancy and security.  While Firedancer
      10             :    receives individual shreds over Turbine / Repair (the relevant Solana
      11             :    protocols), a lot of Firedancer's validation of those shreds can only
      12             :    be done at the FEC set boundary.  Also, there are potential future
      13             :    protocol changes to encode all shreds in a FEC set so that they can
      14             :    only be decoded once the entire FEC set is received ("all-coding").
      15             : 
      16             :    Therefore, the FEC set becomes the logical unit for the store vs.
      17             :    individual shreds.  Replay will only ever attempt replay of a FEC set
      18             :    so there is no need to store at the granularity of individual shreds.
      19             :    The store is essentially a mapping of merkle root->FEC set payload,
      20             :    in which the merkle root was produced by feeding every shred in a FEC
      21             :    set into a merkle tree.  This uniquely identifies a FEC set, because
      22             :    if any of the shreds change, the merkle root changes.
      23             : 
      24             :    Shreds are coalesced and inserted into the store as bytes.  The max
      25             :    bytes per FEC set is currently variable-length but capped at 63985.
      26             :    In the future this will be fixed to 31840 bytes, when FEC sets are
      27             :    enforced to always be 32 shreds.
      28             : 
      29             :    The shared memory used by a store instance is within a workspace such
      30             :    that it is also persistent and remotely inspectable.  Store is
      31             :    designed to be used inter-process (allowing concurrent joins from
      32             :    multiple tiles), relocated in memory (via wksp operations), and
      33             :    accessed concurrently (managing conflicts with a lock).
      34             : 
      35             :    EQUIVOCATION
      36             : 
      37             :    There is a protocol violation called equivocation (also known as
      38             :    "duplicates") that results in two or more blocks for the same slot.
      39             :    The actual conflict occurs at the shred level: a leader produces two
      40             :    or more shreds for the same slot at the same index, with different
      41             :    data payloads.  The result will be two different FEC sets for the
      42             :    same "logical" FEC set based on (slot, fec_set_idx).
      43             : 
      44             :    Unfortunately a naive keying scheme such as (slot, fec_set_idx) is
      45             :    insufficient as a result of equivocation.  As mentioned earlier,
      46             :    Store instead uses the merkle root as the key for its FEC set
      47             :    elements, at the cost of some keying performance and complexity.
      48             : 
      49             :    ARCHITECTURE
      50             : 
      51             :    In the Firedancer topology, Shred tile writes to store and Replay
      52             :    tile reads from store.  Replay tile only reads from the store after
      53             :    Repair tile (which is downstream of Shred) has notified Replay that a
      54             :    FEC set is ready.  Shred's writes are append-only and Replay is
      55             :    responsible for publishing once it is done consuming (signaled by a
      56             :    new Tower root).
      57             : 
      58             :    Shred (writes) -> Repair (notif) -> Replay (reads, publishes)
      59             : 
      60             :    ORDERING
      61             : 
      62             :    In the above architecture, Repair delivers FEC sets to Replay in
      63             :    partial order.  Any given fork will be delivered in-order, but
      64             :    concurrent forks can be delivered in arbitrary order.  Another way to
      65             :    phrase this is a parent FEC set will always be delivered before the
      66             :    child (see fd_fec_chainer).
      67             : 
      68             :    CONCURRENCY
      69             : 
      70             :    It is possible to design Store access in a way that enables parallel
      71             :    writes and minimizes lock contention between readers and writers.
      72             :    Store contains a fd_rwlock (read-write lock), but the name is a bit
      73             :    of a misnomer because writes can actually be concurrent and the only
      74             :    operation that will actually need the write lock is publish, which
      75             :    will be done by Replay.  It is more appropriate to describe as an
      76             :    exclusive-shared access lock.
      77             : 
      78             :    In this design, writers (Shred tiles) hold the shared lock during
      79             :    their access.  The reader (Replay tile) also holds the shared lock
      80             :    during its access, and given both Shred and Replay will be taking out
      81             :    shared locks, they will not contend.
      82             : 
      83             :    For parallel writes, the Store's hash function is carefully designed
      84             :    to partition the keyspace so that the same Shred tile always writes
      85             :    to the same map slots.  This ensures map collisions always happen on
      86             :    the same Shred tile and cannot happen across tiles.  Specifically, if
      87             :    two different FEC sets hash to the same slot, it is guaranteed that
      88             :    to be the same Shred tile processing both those FEC sets.  This
      89             :    prevents a data race in which multiple Shred tiles (each with a
      90             :    handle to the shared lock) write to the same map slot.
      91             : 
      92             :    The hash function is defined as follows:
      93             :    ```
      94             :    #define MAP_KEY_HASH(key,seed) ((ulong)key->mr.ul[0]%seed + (key)->part*seed)
      95             :    ```
      96             :    where `key` is a key type that includes the merkle root (32 bytes)
      97             :    and the partition index (4 bytes) that is equivalent to the Shred
      98             :    tile index doing the insertion.  seed, on initialization, is the
      99             :    number of chains/buckets in the map_chain divided by the number of
     100             :    partitions.  In effect, seed is the size of each partition.  For
     101             :    example, if the map_chain is sized to 1024, and there are 4 shred
     102             :    tiles, then the seed is 1024/4 = 256.  Then the map key hash can
     103             :    bound the chain index of each partition as such: shred tile 0 will
     104             :    write to chains 0-255, shred tile 1 will write to chains 256-511,
     105             :    shred tile 2 will write to chains 512-767, and shred tile 3 will
     106             :    write to chains 768-1023, without overlap.  The merkle root is a 32
     107             :    byte SHA-256 hash, so we can expect a fairly uniform distribution of
     108             :    hash values even after truncating to the first 8 bytes, without
     109             :    needing to introduce more randomness.  Thus we can repurpose the
     110             :    `seed` argument to be the number of partitions.
     111             : 
     112             :    Essentially, this allows for limited single-producer single-consumer
     113             :    (SPSC) concurrency, where the producer is a given Shred tile and the
     114             :    consumer is Replay tile.  The SPSC concurrency is limited in that the
     115             :    Store should 1. only be read by Replay after Repair has notified
     116             :    Replay it is time to read (ie. Shred has finished writing), and 2. be
     117             :    written to by Shred(s) in append-only fashion, so Shred never
     118             :    modifies or removes from the map.  Store is backed by fd_map_chain,
     119             :    which is not thread-safe generally, but does support this particular
     120             :    SPSC concurrency model in cases where the consumer is guaranteed to
     121             :    be lagging the producer.
     122             : 
     123             :    Analyzing fd_map_chain in gory detail, in the case of a map collision
     124             :    where Replay tile is reading an element and Shred tile writes a new
     125             :    element to the same map slot, that new element is prepended to the
     126             :    hash chain within that slot (which modifies what the head of the
     127             :    chain points to as well as the now-previous head in the hash chain's
     128             :    `.next` field, but does not touch application data).  With fencing
     129             :    enabled (FD_MAP_INSERT_FENCING), it is guaranteed the consumer either
     130             :    reads the head before or after the update.  If it reads before, that
     131             :    is safe, it would just check the key (if no match, iterate down the
     132             :    chain etc.)  If it reads after, it is also safe because the new
     133             :    element is guaranteed to be before the old element in the chain, so
     134             :    it would just do one more iteration.  Note the consumer should always
     135             :    use fd_store_query_const to ensure the underlying fd_map_chain is not
     136             :    modified during querying.
     137             : 
     138             :    The exception to the above is publishing.  Publishing requires
     139             :    exclusive access because it involves removing from fd_map_chain,
     140             :    which is not safe for shared access.  So the Replay tile should take
     141             :    out the exclusive lock.  Publishing happens at most once per slot, so
     142             :    it is a relatively infrequent Store access compared to FEC queries
     143             :    and inserts (which is good because it is also the most expensive). */
     144             : 
     145             : #include "../../flamenco/fd_rwlock.h"
     146             : #include "../../flamenco/types/fd_types_custom.h"
     147             : 
     148             : /* FD_STORE_USE_HANDHOLDING:  Define this to non-zero at compile time
     149             :    to turn on additional runtime checks and logging. */
     150             : 
     151             : #ifndef FD_STORE_USE_HANDHOLDING
     152             : #define FD_STORE_USE_HANDHOLDING 1
     153             : #endif
     154             : 
     155             : /* FD_STORE_ALIGN specifies the alignment needed for store.  ALIGN is
     156             :    double x86 cache line to mitigate various kinds of false sharing (eg.
     157             :    ACLPF adjacent cache line prefetch). */
     158             : 
     159             : #define FD_STORE_ALIGN (128UL)
     160             : 
     161             : /* FD_STORE_MAGIC is a magic number for detecting store corruption. */
     162             : 
     163           9 : #define FD_STORE_MAGIC (0xf17eda2ce75702e0UL) /* firedancer store version 0 */
     164             : 
     165             : /* FD_STORE_DATA_MAX defines a constant for the maximum size of a FEC
     166             :    set payload.  The value is computed from the maximum number
     167             :    of shreds in a FEC set * the payload bytes per shred.
     168             : 
     169             :    67 shreds per FEC set * 955 payloads per shred = 63985 bytes max. */
     170             : 
     171             : #define FD_STORE_DATA_MAX (63985UL) /* TODO fixed-32 */
     172             : 
     173             : /* fd_store_fec describes a store element (FEC set).  The pointer fields
     174             :    implement a left-child, right-sibling n-ary tree. */
     175             : 
     176             : struct __attribute__((packed)) fd_store_key {
     177             :    fd_hash_t mr;
     178             :    ulong     part; /* partition index of the inserter */
     179             : };
     180             : typedef struct fd_store_key fd_store_key_t;
     181             : 
     182             : struct __attribute__((aligned(128UL))) fd_store_fec {
     183             : 
     184             :   /* Keys */
     185             : 
     186             :   fd_store_key_t key;  /* map key, merkle root of the FEC set + a partition index*/
     187             :   fd_hash_t cmr; /* parent's map key, chained merkle root of the FEC set */
     188             : 
     189             :   /* Pointers */
     190             : 
     191             :   ulong next;    /* reserved for internal use by fd_pool, fd_map_chain, orphan list */
     192             :   ulong parent;  /* pool idx of the parent */
     193             :   ulong child;   /* pool idx of the left-child */
     194             :   ulong sibling; /* pool idx of the right-sibling */
     195             : 
     196             :   /* Data */
     197             : 
     198             :   ulong data_sz;                 /* TODO fixed-32. sz of the FEC set payload, guaranteed < FD_STORE_DATA_MAX */
     199             :   uchar data[FD_STORE_DATA_MAX]; /* FEC set payload = coalesced data shreds (byte array) */
     200             : };
     201             : typedef struct fd_store_fec fd_store_fec_t;
     202             : 
     203             : #define POOL_NAME  fd_store_pool
     204         498 : #define POOL_ELE_T fd_store_fec_t
     205             : #include "../../util/tmpl/fd_pool_para.c"
     206             : 
     207             : #define MAP_NAME               fd_store_map
     208             : #define MAP_ELE_T              fd_store_fec_t
     209             : #define MAP_KEY_T              fd_store_key_t
     210          51 : #define MAP_KEY                key
     211         537 : #define MAP_KEY_EQ(k0,k1)      (!memcmp((k0),(k1), sizeof(fd_hash_t)))
     212         471 : #define MAP_KEY_HASH(key,seed) ((ulong)key->mr.ul[0]%seed + (key)->part*seed) /* See documentation above for the hash function */
     213             : #define MAP_INSERT_FENCE       1
     214             : #include "../../util/tmpl/fd_map_chain.c"
     215             : 
     216             : struct fd_store {
     217             :   ulong magic;       /* ==FD_STORE_MAGIC */
     218             :   ulong fec_max;     /* max number of FEC sets that can be stored */
     219             :   ulong part_cnt;    /* number of partitions, also the number of writers */
     220             :   ulong root;        /* pool idx of the root */
     221             :   ulong slot0;       /* FIXME this hack is needed until the block_id is in the bank (manifest) */
     222             :   ulong store_gaddr; /* wksp gaddr of store in the backing wksp, non-zero gaddr */
     223             :   ulong map_gaddr;   /* wksp gaddr of map of fd_store_key->fd_store_fec */
     224             :   ulong pool_mem_gaddr; /* wksp gaddr of shmem_t object in pool_para */
     225             :   ulong pool_ele_gaddr; /* wksp gaddr of first ele_t object in pool_para */
     226             :   fd_rwlock_t lock; /* rwlock for concurrent access */
     227             : };
     228             : typedef struct fd_store fd_store_t;
     229             : 
     230             : FD_PROTOTYPES_BEGIN
     231             : 
     232             : /* Constructors */
     233             : 
     234             : /* fd_store_{align,footprint} return the required alignment and
     235             :    footprint of a memory region suitable for use as store with up to
     236             :    fec_max elements. */
     237             : 
     238             : FD_FN_CONST static inline ulong
     239         102 : fd_store_align( void ) {
     240         102 :   return alignof(fd_store_t);
     241         102 : }
     242             : 
     243             : FD_FN_CONST static inline ulong
     244          18 : fd_store_footprint( ulong fec_max ) {
     245          18 :   return FD_LAYOUT_FINI(
     246          18 :     FD_LAYOUT_APPEND(
     247          18 :     FD_LAYOUT_APPEND(
     248          18 :     FD_LAYOUT_APPEND(
     249          18 :     FD_LAYOUT_APPEND(
     250          18 :     FD_LAYOUT_INIT,
     251          18 :       alignof(fd_store_t),     sizeof(fd_store_t)                ),
     252          18 :       fd_store_map_align(),    fd_store_map_footprint( fec_max ) ),
     253          18 :       fd_store_pool_align(),   fd_store_pool_footprint()         ),
     254          18 :       alignof(fd_store_fec_t), sizeof(fd_store_fec_t)*fec_max    ),
     255          18 :     fd_store_align() );
     256          18 : }
     257             : 
     258             : /* fd_store_new formats an unused memory region for use as a store.
     259             :    mem is a non-NULL pointer to this region in the local address space
     260             :    with the required footprint and alignment. */
     261             : 
     262             : void *
     263             : fd_store_new( void * shmem, ulong fec_max, ulong part_cnt );
     264             : 
     265             : /* fd_store_join joins the caller to the store.  store points to the
     266             :    first byte of the memory region backing the store in the caller's
     267             :    address space.
     268             : 
     269             :    Returns a pointer in the local address space to store on success. */
     270             : 
     271             : fd_store_t *
     272             : fd_store_join( void * store );
     273             : 
     274             : /* fd_store_leave leaves a current local join.  Returns a pointer to the
     275             :    underlying shared memory region on success and NULL on failure (logs
     276             :    details).  Reasons for failure include store is NULL. */
     277             : 
     278             : void *
     279             : fd_store_leave( fd_store_t const * store );
     280             : 
     281             : /* fd_store_delete unformats a memory region used as a store.
     282             :    Assumes only the nobody is joined to the region.  Returns a
     283             :    pointer to the underlying shared memory region or NULL if used
     284             :    obviously in error (e.g. store is obviously not a store ... logs
     285             :    details).  The ownership of the memory region is transferred to the
     286             :    caller. */
     287             : 
     288             : void *
     289             : fd_store_delete( void * store );
     290             : 
     291             : /* Accessors */
     292             : 
     293             : /* fd_store_wksp returns the local join to the wksp backing the store.
     294             :    The lifetime of the returned pointer is at least as long as the
     295             :    lifetime of the local join.  Assumes store is a current local
     296             :    join. */
     297             : 
     298             : FD_FN_PURE static inline fd_wksp_t *
     299        1698 : fd_store_wksp( fd_store_t const * store ) {
     300        1698 :   return (fd_wksp_t *)( ( (ulong)store ) - store->store_gaddr );
     301        1698 : }
     302             : 
     303             : /* fd_store_pool returns a local join to the pool_t object of the store. */
     304         651 : FD_FN_PURE static inline fd_store_pool_t fd_store_pool_const( fd_store_t const * store ) {
     305         651 :    return (fd_store_pool_t){ .pool = fd_wksp_laddr_fast( fd_store_wksp( store ), store->pool_mem_gaddr ),
     306         651 :                              .ele =  fd_wksp_laddr_fast( fd_store_wksp( store ), store->pool_ele_gaddr ),
     307         651 :                              .ele_max = store->fec_max };
     308         651 : }
     309             : 
     310             : /* fd_store_{map,map_const,pool,fec0,fec0_const,root,root_const} returns a pointer in the
     311             :    caller's address space to the corresponding store field.  const
     312             :    versions for each are also provided. */
     313             : 
     314         162 : FD_FN_PURE static inline fd_store_map_t       * fd_store_map       ( fd_store_t       * store ) { return fd_wksp_laddr_fast( fd_store_wksp( store ), store->map_gaddr  ); }
     315         234 : FD_FN_PURE static inline fd_store_map_t const * fd_store_map_const ( fd_store_t const * store ) { return fd_wksp_laddr_fast( fd_store_wksp( store ), store->map_gaddr  ); }
     316         372 : FD_FN_PURE static inline fd_store_pool_t        fd_store_pool      ( fd_store_t       * store ) { return fd_store_pool_const( store );                                    }
     317             : 
     318         159 : FD_FN_PURE static inline fd_store_fec_t       * fd_store_fec0      ( fd_store_t       * store ) { fd_store_pool_t pool = fd_store_pool      ( store ); return pool.ele;                                     }
     319         234 : FD_FN_PURE static inline fd_store_fec_t const * fd_store_fec0_const( fd_store_t const * store ) { fd_store_pool_t pool = fd_store_pool_const( store ); return pool.ele;                                     }
     320          12 : FD_FN_PURE static inline fd_store_fec_t       * fd_store_root      ( fd_store_t       * store ) { fd_store_pool_t pool = fd_store_pool      ( store ); return fd_store_pool_ele      ( &pool, store->root); }
     321           3 : FD_FN_PURE static inline fd_store_fec_t const * fd_store_root_const( fd_store_t const * store ) { fd_store_pool_t pool = fd_store_pool_const( store ); return fd_store_pool_ele_const( &pool, store->root); }
     322             : 
     323             : /* fd_store_{parent,child,sibling} returns a pointer in the caller's
     324             :    address space to the corresponding {parent,left-child,right-sibling}
     325             :    of fec.  Assumes store is a current local join and fec is a valid
     326             :    pointer to a pool element inside store.  const versions for each are
     327             :    also provided. */
     328             : 
     329           0 : FD_FN_PURE static inline fd_store_fec_t       * fd_store_parent       ( fd_store_t       * store, fd_store_fec_t const * fec ) { fd_store_pool_t pool = fd_store_pool      ( store ); return fd_store_pool_ele      ( &pool, fec->parent  ); }
     330           0 : FD_FN_PURE static inline fd_store_fec_t const * fd_store_parent_const ( fd_store_t const * store, fd_store_fec_t const * fec ) { fd_store_pool_t pool = fd_store_pool_const( store ); return fd_store_pool_ele_const( &pool, fec->parent  ); }
     331           0 : FD_FN_PURE static inline fd_store_fec_t       * fd_store_child        ( fd_store_t       * store, fd_store_fec_t const * fec ) { fd_store_pool_t pool = fd_store_pool      ( store ); return fd_store_pool_ele      ( &pool, fec->child   ); }
     332           0 : FD_FN_PURE static inline fd_store_fec_t const * fd_store_child_const  ( fd_store_t const * store, fd_store_fec_t const * fec ) { fd_store_pool_t pool = fd_store_pool_const( store ); return fd_store_pool_ele_const( &pool, fec->child   ); }
     333           0 : FD_FN_PURE static inline fd_store_fec_t       * fd_store_sibling      ( fd_store_t       * store, fd_store_fec_t const * fec ) { fd_store_pool_t pool = fd_store_pool      ( store ); return fd_store_pool_ele      ( &pool, fec->sibling ); }
     334           0 : FD_FN_PURE static inline fd_store_fec_t const * fd_store_sibling_const( fd_store_t const * store, fd_store_fec_t const * fec ) { fd_store_pool_t pool = fd_store_pool_const( store ); return fd_store_pool_ele_const( &pool, fec->sibling ); }
     335             : 
     336             : /* fd_store_{shacq, shrel, exacq, exrel} acquires or releases the shared
     337             :    or exclusive lock. */
     338             : 
     339           0 : FD_FN_PURE static inline void fd_store_shacq( fd_store_t * store ) { fd_rwlock_read   ( &store->lock ); }
     340           0 : FD_FN_PURE static inline void fd_store_shrel( fd_store_t * store ) { fd_rwlock_unread ( &store->lock ); }
     341           0 : FD_FN_PURE static inline void fd_store_exacq( fd_store_t * store ) { fd_rwlock_write  ( &store->lock ); }
     342           0 : FD_FN_PURE static inline void fd_store_exrel( fd_store_t * store ) { fd_rwlock_unwrite( &store->lock ); }
     343             : 
     344             : /* Wrappers for acquiring and releasing the lock with timing, for lock
     345             :    contention metrics. */
     346             : 
     347           0 : #define FD_STORE_SHACQ_TIMED(store, shacq_start, shacq_end) shacq_start = fd_tickcount(); fd_store_shacq( store ); shacq_end = fd_tickcount();
     348           0 : #define FD_STORE_SHREL_TIMED(store, shrel_end)              shrel_end   = fd_tickcount(); fd_store_shrel( store );
     349           0 : #define FD_STORE_EXACQ_TIMED(store, exacq_start, exacq_end) exacq_start = fd_tickcount(); fd_store_exacq( store ); exacq_end = fd_tickcount();
     350           0 : #define FD_STORE_EXREL_TIMED(store, exrel_end)              exrel_end   = fd_tickcount(); fd_store_exrel( store );
     351             : 
     352             : /* fd_store_{query,query_const} queries the FEC set keyed by merkle.
     353             :    Returns a pointer to the fd_store_fec_t if found, NULL otherwise.
     354             : 
     355             :    Both the const and non-const versions are concurrency safe; as in
     356             :    they avoid using the non-const map_ele_query that reorders the chain.
     357             :    fd_store_query gets around this by calling map idx_query_const, which
     358             :    does not reorder the chain, and then indexes directly into the pool
     359             :    and returns a non-const pointer to the element of interest.
     360             : 
     361             :    Assumes caller has acquired the shared lock via fd_store_shacq.
     362             : 
     363             :    IMPORTANT SAFETY TIP!  Caller should only call fd_store_shrel when
     364             :    they no longer retain interest in the returned pointer. */
     365             : 
     366             : FD_FN_PURE static inline fd_store_fec_t *
     367          90 : fd_store_query( fd_store_t * store, fd_hash_t * merkle_root ) {
     368          90 :    fd_store_key_t  key  = { .mr = *merkle_root, .part = UINT_MAX };
     369          90 :    fd_store_pool_t pool = fd_store_pool( store );
     370          96 :    for( uint i = 0; i < store->part_cnt; i++ ) {
     371          96 :       key.part = i;
     372          96 :       ulong idx = fd_store_map_idx_query_const( fd_store_map( store ), &key, ULONG_MAX, fd_store_fec0( store ) );
     373          96 :       if( idx != ULONG_MAX ) return fd_store_pool_ele( &pool, idx );
     374          96 :    }
     375           0 :    return NULL;
     376          90 : }
     377             : 
     378             : FD_FN_PURE static inline fd_store_fec_t const *
     379         177 : fd_store_query_const( fd_store_t const * store, fd_hash_t * merkle_root ) {
     380         177 :    fd_store_key_t key = { .mr = *merkle_root, .part = UINT_MAX };
     381         285 :    for( uint i = 0; i < store->part_cnt; i++ ) {
     382         234 :       key.part = i;
     383         234 :       fd_store_fec_t const * fec = fd_store_map_ele_query_const( fd_store_map_const( store ), &key, NULL, fd_store_fec0_const( store ) );
     384         234 :       if( fec ) return fec;
     385         234 :    }
     386          51 :    return NULL;
     387         177 : }
     388             : 
     389             : /* Operations */
     390             : 
     391             : /* fd_store_insert inserts a new FEC set keyed by merkle.  Returns the
     392             :    newly inserted fd_store_fec_t.  Each fd_store_fec_t can hold at most
     393             :    FD_STORE_DATA_MAX bytes of data, and caller is responsible for
     394             :    copying into the region.
     395             : 
     396             :    Assumes store is a current local join and has space for another
     397             :    element.  Does additional checks when handholding is enabled and
     398             :    fails insertion (returning NULL) if checks fail.  If this is the
     399             :    first element being inserted into store, the store root will be set
     400             :    to this newly inserted element.
     401             : 
     402             :    Assumes caller has acquired either the shared or exclusive lock via
     403             :    fd_store_shacq or fd_store_exacq.  See top-level documentation for
     404             :    why this operation may only require a shared lock vs. exclusive.
     405             : 
     406             :    IMPORTANT SAFETY TIP!  Caller should only call fd_store_shrel or
     407             :    fd_store_exrel when they no longer retain interest in the returned
     408             :    pointer. */
     409             : 
     410             : fd_store_fec_t *
     411             : fd_store_insert( fd_store_t * store,
     412             :                  ulong        part_idx,
     413             :                  fd_hash_t  * merkle_root );
     414             : 
     415             : /* fd_store_link queries for and links the child keyed by merkle_root to
     416             :    parent keyed by chained_merkle_root.  Returns a pointer to the child.
     417             :    Assumes merkle_root and chained_merkle_root are both non-NULL and key
     418             :    elements currently in the store.
     419             : 
     420             :    Assumes caller has acquired the shared lock via fd_store_shacq.
     421             : 
     422             :    IMPORTANT SAFETY TIP!  Caller should only call fd_store_shrel when
     423             :    they no longer retain interest in the returned pointer. */
     424             : 
     425             : fd_store_fec_t *
     426             : fd_store_link( fd_store_t * store,
     427             :                fd_hash_t  * merkle_root,
     428             :                fd_hash_t  * chained_merkle_root );
     429             : 
     430             : /* fd_store_publish publishes merkle_root as the new store root, pruning
     431             :    all elements across branches that do not descend from the new root.
     432             :    Returns a pointer to the new root.  Assumes merkle_root is in the
     433             :    store and connected to the root (if handholding is enabled does
     434             :    additional checks and returns NULL on error).  Note pruning can
     435             :    result in store elements greater than the new root slot being
     436             :    removed.  These are elements that become orphaned as a result of the
     437             :    common ancestor with the new root being removed (the entire branch
     438             :    ie. fork is pruned).
     439             : 
     440             :    For example, in the tree (preorder) [0 1 2 4 3 5 6] publishing 2 will
     441             :    result in [0 1] being removed given they are ancestors of 2, and
     442             :    removing 1 will leave [3 5 6] orphaned and also removed.
     443             : 
     444             :    Assumes caller has acquired the exclusive lock via fd_store_exacq.
     445             : 
     446             :    IMPORTANT SAFETY TIP!  Caller should only call fd_store_exrel when
     447             :    they no longer retain interest in the returned pointer. */
     448             : 
     449             : fd_store_fec_t *
     450             : fd_store_publish( fd_store_t * store,
     451             :                   fd_hash_t  * merkle_root );
     452             : 
     453             : /* fd_store_clear clears the store.  All elements are removed from the
     454             :    map and released back into the pool.  Does not zero-out fields. */
     455             : 
     456             : fd_store_t *
     457             : fd_store_clear( fd_store_t * store );
     458             : 
     459             : /* TODO fd_store_verify */
     460             : 
     461             : /* fd_store_print pretty-prints a formatted store as a tree structure.
     462             :    Printing begins from the store root and each node is the FEC set key
     463             :    (merkle root hash). */
     464             : 
     465             : int
     466             : fd_store_verify( fd_store_t * store );
     467             : 
     468             : void
     469             : fd_store_print( fd_store_t const * store );
     470             : 
     471             : FD_PROTOTYPES_END
     472             : 
     473             : #endif /* HEADER_fd_src_discof_repair_fd_store_h */

Generated by: LCOV version 1.14