LCOV - code coverage report
Current view: top level - discof/repair - fd_fec_chainer.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 36 0.0 %
Date: 2025-07-01 05:00:49 Functions: 0 10 0.0 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_discof_repair_fd_fec_chainer_h
       2             : #define HEADER_fd_src_discof_repair_fd_fec_chainer_h
       3             : 
       4             : /* FEC chainer is an API for "chaining" FEC sets as they are received
       5             :    asynchronously out-of-order over the network (via Turbine and
       6             :    Repair).  The chainer both validates and reorders those FEC sets, and
       7             :    delivers them in-order to the calling application.
       8             : 
       9             :    Every FEC set has a parent (the immediately preceding FEC set in the
      10             :    slot or parent slot) and children (immediately succeeding FEC set(s)
      11             :    for the same slot or child slots).  Because of forks, FEC sets can
      12             :    have multiple children, but this will only be true across slots (ie.
      13             :    the parent and child must be different slots).  The chainer treats
      14             :    forks as "concurrent" with no particular order, so the calling
      15             :    application will receive forking FEC sets in the order in which the
      16             :    chainer is able to chain them.
      17             : 
      18             :    There is a protocol violation called equivocation (also known as
      19             :    "duplicates") that breaks the invariant that forks must be across
      20             :    slots.  For example, equivocation can result in observing two or more
      21             :    child FEC sets for a given parent FEC set in the same slot.
      22             :    Equivocation can also result in other anomalies such as keying
      23             :    collisions (this is detailed later in the documentation).  The
      24             :    chainer makes a best-effort attempt to detect and error on
      25             :    equivocation. Examples include checking merkle roots chain correctly
      26             :    and checking FEC sets are unique.  Not all cases of equivocation can
      27             :    be detected by the chainer however, as not all the necessary
      28             :    information is yet available at this stage in the validator pipeline.
      29             :    Ultimately, if there is equivocation, it is the responsibility of the
      30             :    consensus module to handle it. */
      31             : 
      32             : #include "../../ballet/shred/fd_shred.h"
      33             : 
      34             : /* FD_FEC_CHAINER_USE_HANDHOLDING:  Define this to non-zero at compile time
      35             :    to turn on additional runtime checks and logging. */
      36             : 
      37             : #ifndef FD_FEC_CHAINER_USE_HANDHOLDING
      38             : #define FD_FEC_CHAINER_USE_HANDHOLDING 1
      39             : #endif
      40             : 
      41           0 : #define FD_FEC_CHAINER_SUCCESS    ( 0)
      42           0 : #define FD_FEC_CHAINER_ERR_UNIQUE (-1) /* key uniqueness conflict */
      43           0 : #define FD_FEC_CHAINER_ERR_MERKLE (-2) /* chained merkle root conflict */
      44             : 
      45             : /* fd_fec_chainer is a tree-like structure backed by three maps.  At any
      46             :    given point in time, an element (FEC set) in the chainer is in one of
      47             :    three possible positions with respect to the tree: a non-leaf, leaf,
      48             :    or not connected.  This corresponds to the ancestry, frontier, or
      49             :    orphaned maps, respectively.  Therefore, a given element will always
      50             :    be present in exactly one of these maps, depending on where (and
      51             :    whether) it currently is in the tree.
      52             : 
      53             :    KEYING
      54             : 
      55             :    The chainer keys FEC sets by concatenating the slot with fec_set_idx.
      56             :    This uniquely identifies a FEC set in most cases.  It is possible to
      57             :    receive over the network two or more different FEC sets with the same
      58             :    slot and fec_set_idx due to equivocation as mentioned earlier.  In
      59             :    general, the chainer expects the caller to handle equivocation and
      60             :    assumes unique FEC sets will have unique keys (handholding is
      61             :    available to verify this).
      62             : 
      63             :    The map key is an encoding of the slot and fec_set_idx which uniquely
      64             :    keys every FEC set.  The 32 msb of the key are the 32 lsb of the slot
      65             :    and the 32 lsb of the key are the fec_set_idx, except when the FEC
      66             :    set is the last one for the slot, in which case the 32 lsb are set to
      67             :    UINT_MAX. By setting fec_set_idx to UINT_MAX, the chainer can easily
      68             :    query for the last FEC set in any given slot
      69             : 
      70             :    A useful property of the keying scheme above is a FEC set can infer
      71             :    the key of its immediate child by adding data_cnt to its fec_set_idx.
      72             :    For example, a FEC set for slot 0, fec_set_idx 0, data_cnt 32 knows
      73             :    its child key is slot 0, fec_set_idx 32. The last FEC set in the slot
      74             :    is special because the child(s) FEC set will have a different slot
      75             :    number, so we know the fec_set_idx will be zero.
      76             : 
      77             :    There is one exception to this keying scheme.  When the FEC set is
      78             :    the last one in the slot, an extra insertion to the parent_map is
      79             :    done. In the standard procedure, the second to last fec will create
      80             :    the (n-1, n) entry, and the following child slot will create a
      81             :    (UINT_MAX, 0) entry. Thus we insert an extra entry (n, UINT_MAX) in
      82             :    the parent_map to connect the chain. This double insertion is only
      83             :    done for the parent_map - the pool_ele will have an element with key
      84             :    slot | fec_set_idx, not UINT_MAX.
      85             : 
      86             :    Visually, the parent_map / elements looks like this:
      87             : 
      88             :       - Arrows denote a child->parent entry in the parent_map.
      89             : 
      90             :    parent_map                                             |  pool_ele (slot, fec_set_idx, completes)
      91             :    ──────────────────────────────────────────────────────────────────────────────────────────────────
      92             :    (slot, 0)                                              |  (slot, 0,  0)
      93             :        ▲                                                  |
      94             :        |                                                  |
      95             :    (slot, 32)                                             |  (slot, 32, 0)
      96             :        ▲                                                  |
      97             :        |                                                  |
      98             :    (slot, 64) <-- (slot, UINT_MAX)                        |  (slot, 64, 1)
      99             :                         ▲                                 |
     100             :                         |                                 |
     101             :                   (slot + 1, 0)                           |  (slot + 1, 0, 0)
     102             :                         ▲                                 |
     103             :                         |                                 |
     104             :                   (slot + 1, 32)                          |  (slot + 1, 32, 0)
     105             :                         ▲                                 |
     106             :                         |                                 |
     107             :                   (slot + 1, 64) ◄── (slot + 1, UINT_MAX) |  (slot + 1, 64, 1)
     108             :                         ▲                                 |
     109             :                         | ...                             |
     110             : 
     111             :    Thus we will have double entries for the last FEC set in a slot in
     112             :    the parent map, but only one entry in the ancestry/orphan/frontier
     113             :    pool. This means if we want to query for the last FEC set in a slot,
     114             :    we need to query the parent_map twice - once with the fec_set_idx set
     115             :    to UINT_MAX and once with the parent_key of the result.
     116             : 
     117             :    INSERTING
     118             : 
     119             :    When inserting a new FEC set, the chainer first checks whether the
     120             :    parent is a FEC set already in the frontier map.  This indicates that
     121             :    the new FEC set directly chains off the frontier.  If it does, the
     122             :    parent FEC set is removed, and the new FEC set is inserted into the
     123             :    frontier map.  This is the common case because we expect FEC sets to
     124             :    chain linearly the vast majority (ie. not start new forks), so the
     125             :    new FEC set is simply "advancing" the frontier.  The parent FEC set
     126             :    is also added to the ancestry map, so that every leaf can trace back
     127             :    to the root.
     128             : 
     129             :    If the FEC set's parent is not already in the frontier, the chainer
     130             :    checks the ancestry map next.  If the parent is in the ancestry map,
     131             :    the chainer knows that this FEC set is starting a new fork, because
     132             :    it is part of the tree (the ancestry) but not one of the leaves (the
     133             :    frontier).  In this case, the new FEC set is simply inserted into the
     134             :    frontier map, and now the frontier has an additional fork (leaf).
     135             : 
     136             :    Lastly, if the FEC set's parent is not in the ancestry map, the
     137             :    chainer knows that this FEC set is orphaned.  It is inserted into the
     138             :    orphaned map for later retry of tree insertion when its ancestors
     139             :    have been inserted.
     140             : 
     141             :    Here are some more important details on forks. Note a FEC set can
     142             :    only start a new fork when it is across a slot boundary (different
     143             :    slot than its parent).  It is invalid for two FEC sets to chain off
     144             :    the same parent FEC within the same slot - this would imply there are
     145             :    two FECs keyed by the (same slot, fec_set_idx) combination, which as
     146             :    detailed earlier, is equivocation.  Therefore, only the first FEC set
     147             :    in a slot can start a fork from the last FEC set in a parent slot. We
     148             :    know a FEC set is the first one in a slot when the fec_set_idx is 0,
     149             :    and we know it is the last one when the last shred in the FEC set has
     150             :    the SLOT_COMPLETE flag set.
     151             : 
     152             :    QUERYING
     153             : 
     154             :    The chainer can fast O(1) query any FEC set using the key.  As
     155             :    mentioned earlier, any FEC set except the last one in a slot can
     156             :    derive its direct child's key and therefore query for it.
     157             : 
     158             :    For the special case of the first FEC set in a slot, the chainer can
     159             :    derive the parent key by subtracting the parent_off from the slot and
     160             :    querying for (slot, UINT_MAX).
     161             : 
     162             :    CHAINING
     163             : 
     164             :    As mentioned in the top-level documentation, the purpose of the
     165             :    chainer is to chain FEC sets.  On insertion, the chainer will attempt
     166             :    to chain as many FEC sets as possible to the frontier.  The chainer
     167             :    does this by conducting a BFS from the just-inserted FEC set, looking
     168             :    for parents and orphans to traverse.  See `chain` in the .c file for
     169             :    the implementation. */
     170             : 
     171             : typedef struct fd_fec_chainer fd_fec_chainer_t; /* forward decl */
     172             : 
     173             : struct fd_fec_ele {
     174             :   ulong  key;  /* map key */
     175             :   ulong  next; /* reserved for use by fd_pool and fd_map_chain */
     176             : 
     177             :   ulong  slot;
     178             :   uint   fec_set_idx;
     179             :   ushort data_cnt;
     180             :   int    data_complete;
     181             :   int    slot_complete;
     182             :   ushort parent_off;
     183             :   uchar  merkle_root[FD_SHRED_MERKLE_ROOT_SZ];
     184             :   uchar  chained_merkle_root[FD_SHRED_MERKLE_ROOT_SZ];
     185             : };
     186             : typedef struct fd_fec_ele fd_fec_ele_t;
     187             : 
     188             : #define POOL_NAME   fd_fec_pool
     189           0 : #define POOL_T      fd_fec_ele_t
     190             : #include "../../util/tmpl/fd_pool.c"
     191             : 
     192             : #define MAP_NAME    fd_fec_ancestry
     193             : #define MAP_ELE_T   fd_fec_ele_t
     194             : #include "../../util/tmpl/fd_map_chain.c"
     195             : 
     196             : #define MAP_NAME    fd_fec_frontier
     197             : #define MAP_ELE_T   fd_fec_ele_t
     198             : #include "../../util/tmpl/fd_map_chain.c"
     199             : 
     200             : #define MAP_NAME    fd_fec_orphaned
     201             : #define MAP_ELE_T   fd_fec_ele_t
     202             : #include "../../util/tmpl/fd_map_chain.c"
     203             : 
     204             : struct fd_fec_parent {
     205             :   ulong key;
     206             :   ulong parent_key;
     207             : };
     208             : typedef struct fd_fec_parent fd_fec_parent_t;
     209             : 
     210             : /* There are no FEC sets for the genesis block, so (0, 0) represents the
     211             :    NULL map key. */
     212             : 
     213             : #define MAP_NAME     fd_fec_parents
     214           0 : #define MAP_T        fd_fec_parent_t
     215             : #define MAP_MEMOIZE  0
     216             : #include "../../util/tmpl/fd_map_dynamic.c"
     217             : 
     218             : #define FD_FEC_CHILDREN_MAX (64) /* TODO size this more reasonably */
     219             : FD_STATIC_ASSERT( FD_FEC_CHILDREN_MAX % 64 == 0, FD_FEC_CHILDREN_MAX must be a multiple of 64 bits per word );
     220             : 
     221             : #define SET_NAME fd_slot_child_offs
     222             : #define SET_MAX  FD_FEC_CHILDREN_MAX
     223             : #include "../../util/tmpl/fd_set.c"
     224             : 
     225             : /* FIXME consider alternate pooled tree-like representation eg. fd_ghost
     226             :    maybe the ghost generic in tmpl? */
     227             : 
     228             : struct fd_fec_children {
     229             :   ulong                slot;
     230             :   fd_slot_child_offs_t child_offs[FD_FEC_CHILDREN_MAX / 64];
     231             : };
     232             : typedef struct fd_fec_children fd_fec_children_t;
     233             : 
     234             : #define MAP_NAME     fd_fec_children
     235           0 : #define MAP_T        fd_fec_children_t
     236           0 : #define MAP_KEY      slot
     237             : #define MAP_MEMOIZE  0
     238             : #include "../../util/tmpl/fd_map_dynamic.c"
     239             : 
     240             : #define DEQUE_NAME fd_fec_queue
     241           0 : #define DEQUE_T    ulong
     242             : #include "../../util/tmpl/fd_deque_dynamic.c"
     243             : 
     244             : struct fd_fec_out {
     245             :   ulong  slot;
     246             :   ushort parent_off;
     247             :   uint   fec_set_idx;
     248             :   ushort data_cnt;
     249             :   int    data_complete;
     250             :   int    slot_complete;
     251             :   int    err;
     252             : };
     253             : typedef struct fd_fec_out fd_fec_out_t;
     254             : 
     255             : #define DEQUE_NAME fd_fec_out
     256           0 : #define DEQUE_T    fd_fec_out_t
     257             : #include "../../util/tmpl/fd_deque_dynamic.c"
     258             : 
     259             : /* TODO deque probably not needed if reuse ele->next. */
     260             : 
     261             : struct __attribute__((aligned(128UL))) fd_fec_chainer {
     262             :   fd_fec_ancestry_t  * ancestry; /* map of key->fec. non-leaves of FEC tree */
     263             :   fd_fec_frontier_t  * frontier; /* map of key->fec. leaves */
     264             :   fd_fec_orphaned_t  * orphaned; /* map of key->fec. FECs not yet inserted to tree */
     265             :   fd_fec_ele_t       * pool;     /* pool of FEC nodes backing the above maps / tree */
     266             :   fd_fec_parent_t    * parents;  /* map of key->parent_key for fast O(1) querying */
     267             :   fd_fec_children_t  * children; /* map of slot->child_offs for fast O(1) querying */
     268             :   ulong              * queue;    /* queue of FEC keys for BFS chaining */
     269             :   fd_fec_out_t       * out;      /* queue of FEC keys to deliver to application */
     270             : };
     271             : 
     272             : FD_PROTOTYPES_BEGIN
     273             : 
     274             : /* Constructors */
     275             : 
     276             : /* fd_fec_chainer_{align,footprint} return the required alignment and
     277             :    footprint of a memory region suitable for use as chainer with up to
     278             :    fec_max elements and slot_max slots. */
     279             : 
     280             : FD_FN_CONST static inline ulong
     281           0 : fd_fec_chainer_align( void ) {
     282           0 :   return alignof(fd_fec_chainer_t);
     283           0 : }
     284             : 
     285             : FD_FN_CONST static inline ulong
     286           0 : fd_fec_chainer_footprint( ulong fec_max ) {
     287           0 :   int lg_fec_max = fd_ulong_find_msb( fd_ulong_pow2_up( fec_max ) );
     288           0 :   return FD_LAYOUT_FINI(
     289           0 :     FD_LAYOUT_APPEND(
     290           0 :     FD_LAYOUT_APPEND(
     291           0 :     FD_LAYOUT_APPEND(
     292           0 :     FD_LAYOUT_APPEND(
     293           0 :     FD_LAYOUT_APPEND(
     294           0 :     FD_LAYOUT_APPEND(
     295           0 :     FD_LAYOUT_APPEND(
     296           0 :     FD_LAYOUT_APPEND(
     297           0 :     FD_LAYOUT_APPEND(
     298           0 :     FD_LAYOUT_INIT,
     299           0 :       alignof(fd_fec_chainer_t), sizeof(fd_fec_chainer_t)                ),
     300           0 :       fd_fec_ancestry_align(),   fd_fec_ancestry_footprint( fec_max )    ),
     301           0 :       fd_fec_frontier_align(),   fd_fec_frontier_footprint( fec_max )    ),
     302           0 :       fd_fec_orphaned_align(),   fd_fec_orphaned_footprint( fec_max )    ),
     303           0 :       fd_fec_pool_align(),       fd_fec_pool_footprint( fec_max )        ),
     304           0 :       fd_fec_parents_align(),    fd_fec_parents_footprint( lg_fec_max )  ),
     305           0 :       fd_fec_children_align(),   fd_fec_children_footprint( lg_fec_max ) ),
     306           0 :       fd_fec_queue_align(),      fd_fec_queue_footprint( fec_max )       ),
     307           0 :       fd_fec_out_align(),        fd_fec_out_footprint( fec_max )         ),
     308           0 :     fd_fec_chainer_align() );
     309           0 : }
     310             : 
     311             : /* fd_fec_chainer_new formats an unused memory region for use as a
     312             :    chainer.  mem is a non-NULL pointer to this region in the local
     313             :    address space with the required footprint and alignment. */
     314             : 
     315             : void *
     316             : fd_fec_chainer_new( void * shmem, ulong fec_max, ulong seed );
     317             : 
     318             : /* fd_fec_chainer_join joins the caller to the chainer.  chainer points
     319             :    to the first byte of the memory region backing the chainer in the
     320             :    caller's address space.
     321             : 
     322             :    Returns a pointer in the local address space to chainer on
     323             :    success. */
     324             : 
     325             : fd_fec_chainer_t *
     326             : fd_fec_chainer_join( void * chainer );
     327             : 
     328             : /* fd_fec_chainer_leave leaves a current local join.  Returns a pointer
     329             :    to the underlying shared memory region on success and NULL on failure
     330             :    (logs details).  Reasons for failure include chainer is NULL. */
     331             : 
     332             : void *
     333             : fd_fec_chainer_leave( fd_fec_chainer_t * chainer );
     334             : 
     335             : /* fd_fec_chainer_delete unformats a memory region used as a chainer.
     336             :    Assumes only the nobody is joined to the region.  Returns a pointer
     337             :    to the underlying shared memory region or NULL if used obviously in
     338             :    error (e.g. chainer is obviously not a chainer ... logs details).
     339             :    The ownership of the memory region is transferred to the caller. */
     340             : 
     341             : void *
     342             : fd_fec_chainer_delete( void * chainer );
     343             : 
     344             : fd_fec_ele_t *
     345             : fd_fec_chainer_init( fd_fec_chainer_t * chainer, ulong slot, uchar merkle_root[static FD_SHRED_MERKLE_ROOT_SZ] );
     346             : 
     347             : FD_FN_PURE fd_fec_ele_t *
     348             : fd_fec_chainer_query( fd_fec_chainer_t * chainer, ulong slot, uint fec_set_idx );
     349             : 
     350             : /* fd_fec_chainer inserts a new FEC set into chainer.  Returns the newly
     351             :    inserted fd_fec_ele_t, NULL on error.  Inserting this FEC set may
     352             :    result in one or more FEC sets being ready for in-order delivery.
     353             :    Caller can consume these FEC sets via the deque in chainer->out.
     354             : 
     355             :    See top-level documentation for further details on insertion. */
     356             : 
     357             : fd_fec_ele_t *
     358             : fd_fec_chainer_insert( fd_fec_chainer_t * chainer,
     359             :                        ulong              slot,
     360             :                        uint               fec_set_idx,
     361             :                        ushort             data_cnt,
     362             :                        int                data_complete,
     363             :                        int                slot_complete,
     364             :                        ushort             parent_off,
     365             :                        uchar const        merkle_root[static FD_SHRED_MERKLE_ROOT_SZ],
     366             :                        uchar const        chained_merkle_root[static FD_SHRED_MERKLE_ROOT_SZ] );
     367             : 
     368             : FD_PROTOTYPES_END
     369             : 
     370             : #endif /* HEADER_fd_src_discof_repair_fd_fec_chainer_h */

Generated by: LCOV version 1.14