LCOV - code coverage report
Current view: top level - disco/shred - fd_fec_resolver.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 6 8 75.0 %
Date: 2026-03-17 05:46:53 Functions: 0 0 -

          Line data    Source code
       1             : #ifndef HEADER_fd_src_disco_shred_fd_fec_resolver_h
       2             : #define HEADER_fd_src_disco_shred_fd_fec_resolver_h
       3             : #include "fd_fec_set.h"
       4             : #include "../../ballet/bmtree/fd_bmtree.h"
       5             : #include "../../ballet/ed25519/fd_ed25519.h"
       6             : 
       7             : /* This header defines several methods for building and validating FEC
       8             :    sets from received shreds.  It's designed just for use by the shred
       9             :    tile, which is why it's in disco/shred.
      10             : 
      11             :    The primary complication in the interface comes from lifetimes.
      12             :    Buffers returned by the networking layer are typically ephemeral and
      13             :    we need to hold onto the data until we've finished the FEC set, so we
      14             :    need at least one memcpy.  Once we complete the FEC set, the result
      15             :    needs to go out on an mcache/dcache pair and on the network, which
      16             :    also have different lifetime requirements.  The FEC resolver goes out
      17             :    of its way to use memory in a way that doesn't require a second
      18             :    memcpy once the FEC set is complete.  To that end, the FEC resolver
      19             :    makes two promises:
      20             :    1. Once memory has been used for a specific FEC set, it will not be
      21             :       reused for a different FEC set until at least partial_depth other
      22             :       distinct FEC sets have been returned in the out_fec_set field of
      23             :       fd_fec_resolver_add_shred.
      24             :    2. Once a FEC set is complete (specified with COMPLETES), the
      25             :       associated memory will not be reused for a different FEC set until
      26             :       at least complete_depth other distinct FEC sets have been returned
      27             :       from calls to fd_fec_resolver_add_shred that return
      28             :       SHRED_COMPLETES.
      29             : 
      30             :    This is implemented using a freelist with an insertion point in the
      31             :    middle (which can also be seen as two chained freelists):
      32             :                  ------------------------
      33             :                  |   In progress FEC    |--------- if completed -
      34             :                  |    sets (<=depth)    |                       |
      35             :                  |                      |--- if spilled ----    |
      36             :                  ------------------------                  |    |
      37             :                        ^                                   |    |
      38             :                        |                                   V    |
      39             :                    --------                                |    |
      40             :                    |      |   -                            |    V
      41             :                    | Free |    \                           |    |
      42             :                    |      |     >=partial_depth            |    |
      43             :                    | FEC  |     /                          |    |
      44             :                    | sets |    |                           |    |
      45             :                    |      |   _                            |    |
      46             :                    --------                                V    |
      47             :                       ^  ^                                 |    |
      48             :                       |  |------<---------------<----------|    |
      49             :                       |                                         |
      50             :                    --------                                     |
      51             :                    |      | -                                   |
      52             :                    | Comp |  \                                  |
      53             :                    |leted |   complete_depth                    |
      54             :                    |      |  /                                  V
      55             :                    | FEC  |  |                                  |
      56             :                    | sets |  |                                  |
      57             :                    |      |  -                                  |
      58             :                    --------                                     |
      59             :                       ^                                         |
      60             :                       |-------------------<----------------------
      61             : 
      62             :    When a shred arrives for a new FEC set, we pull an entry from the
      63             :    head of the free FEC set queue.  If that would result in more than
      64             :    depth sets in progress, we spill the oldest in progress set, and
      65             :    insert it at the tail of the free set queue.  When we complete a set,
      66             :    we remove it from the in progress set, add it to the tail of the
      67             :    completed FEC set queue, and move one element from the head of the
      68             :    completed queue to the tail of the free queue.
      69             : 
      70             :    Since the completed queue only advances when an FEC set is completed,
      71             :    any complete FEC set stays in that queue for at least complete_depth
      72             :    completions.
      73             : 
      74             :    It might seems like this system is overkill and one combined longer
      75             :    freelist would suffice, but that's incorrect. Consider the following
      76             :    scenario: Suppose we've just completed FEC set A, so we return it
      77             :    with COMPLETES and then we put it at the end of the free list.  Now
      78             :    we receive a shred for a new FEC set, so we take memory from the head
      79             :    of the free list.  That happens many times, but we never receive
      80             :    enough shreds for any FEC set to complete one.  Eventually, we churn
      81             :    through the whole singular freelist with spilling until the memory we
      82             :    used for A gets to the head of the freelist.  Finally we receive
      83             :    enough shreds to complete the FEC set, so we return A with COMPLETES.
      84             :    Thus, from the perspective of a consumer that only cares about
      85             :    completed FEC sets, we've returned the same piece of memory twice in
      86             :    a row.
      87             : 
      88             :    While done_depth is independent of all these, it's worth including a
      89             :    note about it here too.  Once we return with COMPLETES, we don't want
      90             :    to process that FEC set again, which means we need some memory of FEC
      91             :    sets that have been processed.  Obviously, this memory has to be
      92             :    finite, so we retain the FEC sets with the highest (slot, FEC set
      93             :    index).  In normal use, it would be rare to fill this data structure,
      94             :    but in a DoS attack, this policy makes the attack more difficult. */
      95             : 
      96             : /* Forward declare opaque handle.  It has a lot of types we don't
      97             :    necessarily want to bring into the includer */
      98           0 : #define FD_FEC_RESOLVER_ALIGN (128UL)
      99             : struct fd_fec_resolver;
     100             : typedef struct fd_fec_resolver fd_fec_resolver_t;
     101             : 
     102             : 
     103             : /* fd_fec_resolver_sign_fn: used to sign shreds that require a
     104             :    retransmitter signature. */
     105             : typedef void (fd_fec_resolver_sign_fn)( void * ctx, uchar * sig, uchar const * merkle_root );
     106             : 
     107             : FD_PROTOTYPES_BEGIN
     108             : /* fd_fec_resolver_footprint returns the required footprint (in bytes as
     109             :    always) required to create an FEC set resolver that can keep track of
     110             :    `depth` in progress FEC sets, will not reuse FEC sets for at least
     111             :    partial_depth shreds or for at least complete_depth complete FEC sets
     112             :    (see above for more information).  Additionally, the FEC resolver
     113             :    remembers done_depth FEC sets to recognize duplicates vs. new FEC
     114             :    sets.  All depths must positive.
     115             : 
     116             :    fd_fec_resolver_alignment returns the required alignment of a region
     117             :    of memory for it to be used as a FEC resolver. */
     118             : FD_FN_PURE  ulong fd_fec_resolver_footprint( ulong depth, ulong partial_depth, ulong complete_depth, ulong done_depth );
     119             : FD_FN_CONST ulong fd_fec_resolver_align    ( void );
     120             : 
     121             : /* fd_fec_resolver_new formats a region of memory as a FEC resolver.
     122             :    shmem must have the required alignment and footprint.  signer is a
     123             :    function pointer used to sign any shreds that require a retransmitter
     124             :    signature, and sign_ctx is an opaque pointer passed as the first
     125             :    argument to the function.  It is okay to pass NULL for signer, in
     126             :    which case, retransmission signatures will just be zeroed and
     127             :    sign_ctx will be ignored. depth, partial_depth, complete_depth, and
     128             :    done_depth are as defined above and must be positive.  The sum of
     129             :    depth, partial_depth, and complete_depth must be less than UINT_MAX.
     130             :    sets is a pointer to the first of depth+partial_depth+complete_depth
     131             :    FEC sets that this resolver will take ownership of.  The FEC resolver
     132             :    retains a write interest in these FEC sets and the shreds they point
     133             :    to until the resolver is deleted.  These FEC sets and the memory for
     134             :    the shreds they point to are the only values that will be returned in
     135             :    the out_shred and out_fec_set output parameters of add_shred.  The
     136             :    FEC resolver will also reject any shred that seems to be part of a
     137             :    block containing more than max_shred_idx data or parity shreds.
     138             :    Since shred_idx is a uint, it doesn't really make sense to have
     139             :    max_shred_idx > UINT_MAX, and max_shred_idx==0 rejects all shreds.
     140             :    seed is an arbitrary ulong used to seed various data structures.  It
     141             :    should be set to a validator independent value.
     142             : 
     143             :    On success, the FEC resolver will be initialized with an expected
     144             :    shred version of 0, which causes it to reject all shreds, and a
     145             :    slot_old of slot 0, which causes it to ignore no shreds.
     146             : 
     147             :    Returns shmem on success and NULL on failure (logs details). */
     148             : void *
     149             : fd_fec_resolver_new( void                    * shmem,
     150             :                      fd_fec_resolver_sign_fn * signer,
     151             :                      void                    * sign_ctx,
     152             :                      ulong                     depth,
     153             :                      ulong                     partial_depth,
     154             :                      ulong                     complete_depth,
     155             :                      ulong                     done_depth,
     156             :                      fd_fec_set_t            * sets,
     157             :                      ulong                     max_shred_idx,
     158             :                      ulong                     seed );
     159             : 
     160             : fd_fec_resolver_t * fd_fec_resolver_join( void * shmem );
     161             : 
     162             : /* fd_fec_resolver_set_shred_version updates the expected shred version
     163             :    to the value provided in expected_shred_version.  resolver must be a
     164             :    valid local join.  add_shred will reject any future shreds with a
     165             :    shred version other than the new expected shred version.  Setting
     166             :    expected_shred_version to 0 causes the FEC resolver to reject all
     167             :    future shreds.  This function will not immediately trigger the FEC
     168             :    resolver to discard any previously accepted shreds,  but changing the
     169             :    expected shred version will make it impossible to complete any FEC
     170             :    sets that are in progress prior to the call. */
     171             : void
     172             : fd_fec_resolver_set_shred_version( fd_fec_resolver_t * resolver,
     173             :                                    ushort              expected_shred_version );
     174             : 
     175             : /* fd_fec_resolver_set_discard_unexpected_data_complete_shreds updates
     176             :    the activation slot used by the FEC resolver for the
     177             :    discard_unexpected_data_complete_shreds feature. */
     178             : void
     179             : fd_fec_resolver_set_discard_unexpected_data_complete_shreds( fd_fec_resolver_t * resolver,
     180             :                                                              ulong               activation_slot );
     181             : 
     182             : /* fd_fec_resolver_advance_slot_old advances slot_old, discarding any
     183             :    in progress FEC sets with a slot strictly less than slot_old.
     184             :    Additionally, causes the FEC resolver to ignore any future shreds
     185             :    with a slot older than the new value of slot_old.  slot_old increases
     186             :    monotonically, which means that a call to advance_slot_old with a
     187             :    lower value of slot_old is a no-op. */
     188             : void
     189             : fd_fec_resolver_advance_slot_old( fd_fec_resolver_t * resolver,
     190             :                                   ulong               slot_old );
     191             : 
     192             : 
     193             : /* Keep in sync with metrics.xml */
     194         552 : #define FD_FEC_RESOLVER_SHRED_EQUIVOC   (-4)
     195         123 : #define FD_FEC_RESOLVER_SHRED_REJECTED  (-3)
     196         654 : #define FD_FEC_RESOLVER_SHRED_IGNORED   (-2)
     197         249 : #define FD_FEC_RESOLVER_SHRED_DUPLICATE (-1)
     198        7194 : #define FD_FEC_RESOLVER_SHRED_OKAY      ( 0)
     199         219 : #define FD_FEC_RESOLVER_SHRED_COMPLETES ( 1)
     200             : 
     201             : /* Return values + RETVAL_OFF are in [0, RETVAL_CNT) */
     202             : #define FD_FEC_RESOLVER_ADD_SHRED_RETVAL_CNT 6
     203           0 : #define FD_FEC_RESOLVER_ADD_SHRED_RETVAL_OFF 4
     204             : 
     205             : struct fd_fec_resolver_spilled {
     206             :   ulong            slot;
     207             :   uint             fec_set_idx;
     208             :   fd_bmtree_node_t merkle_root[1];
     209             : };
     210             : typedef struct fd_fec_resolver_spilled fd_fec_resolver_spilled_t;
     211             : 
     212             : /* fd_fec_resolver_add_shred notifies the FEC resolver of a newly
     213             :    received shred.  The FEC resolver validates the shred and copies it
     214             :    into its own storage.  resolver is a local join of an FEC resolver.
     215             :    shred is a pointer to the new shred that should be added.  The shred
     216             :    must have passed the shred parsing validation.  shred_sz is the size
     217             :    of the shred in bytes.  If is_repair is non-zero, some validity
     218             :    checks will be omitted, as detailed below.  leader_pubkey must be a
     219             :    pointer to the first byte of the public identity key of the validator
     220             :    that was leader during slot shred->slot.
     221             : 
     222             :    On success ie. SHRED_{OKAY,COMPLETES}, a pointer to the fd_fec_set_t
     223             :    structure representing the FEC set of which the shred is a part will
     224             :    be written to out_fec_set.  Additionally, on success a pointer to a
     225             :    copy of shred will be written to the location pointed to by
     226             :    out_shred.  See the long explanation above about the lifetimes of
     227             :    these pointers.  Finally, on success the merkle root of the shred
     228             :    (reconstructed from the merkle proof) will be written to
     229             :    out_merkle_root.  Unlike out_{fec_set,shred}, caller owns and
     230             :    provides the memory for out_merkle_root.  If the out_merkle_root
     231             :    pointer is NULL, the argument will be ignored and merkle root will
     232             :    not be written.
     233             : 
     234             :    If the shred's Merkle root differs from the Merkle root of a
     235             :    previously received shred with the same values for slot and FEC
     236             :    index, and is_repair is zero, the shred may be rejected with return
     237             :    value SHRED_EQUIVOC.  The FEC resolver has a limited memory, which is
     238             :    why equivocation detection cannot be guaranteed.  Note that these
     239             :    checks are bypassed if is_repair is non-zero.
     240             : 
     241             :    If the shred has the same slot, shred index, and signature as a shred
     242             :    that has already been successfully completed in a FEC by the FEC
     243             :    resolver, or if the shred is for a slot older than slot_old, returns
     244             :    SHRED_IGNORED and does not write to out_{fec_set,shred,merkle_root}.
     245             :    Note that "successfully completed in a FEC by the FEC resolver" above
     246             :    includes shreds that are reconstructed when returning
     247             :    SHRED_COMPLETES, so for example, after returning SHRED_COMPLETES for
     248             :    a FEC set, adding any shred for that FEC set will return
     249             :    SHRED_IGNORED, even if that particular shred hadn't been received.
     250             : 
     251             :    However, if the shred is part of an in progress FEC set but has
     252             :    already been received, FEC resolver returns SHRED_DUPLICATE and
     253             :    populates out_merkle_root if it is non-NULL.
     254             : 
     255             :    If the shred fails validation for any other reason, returns
     256             :    SHRED_REJECTED and does not write to out_{fec_set,shred,merkle_root}.
     257             : 
     258             :    Note that only light validation is performed on a duplicate shred, so
     259             :    a shred that is actually invalid but looks like a duplicate of a
     260             :    previously received valid shred may be considered SHRED_IGNORED
     261             :    instead of SHRED_REJECTED.
     262             : 
     263             :    This function returns SHRED_COMPLETES when the received shred
     264             :    completes the FEC set.  In this case, the function populates any
     265             :    missing shreds in the FEC set stored in out_fec_set.
     266             : 
     267             :    Regardless of success/failure, if an in progress FEC set was evicted
     268             :    from the current map during this add_shred call, and the
     269             :    out_spilled_fec_set pointer is not NULL, the metadata of the evicted
     270             :    FEC set will be written to out_spilled_fec_set.  The FEC set metadata
     271             :    consists of the slot, FEC set index, and the Merkle root of the
     272             :    evicted FEC set.  Similar to out_merkle_root, the caller owns and
     273             :    provides the memory for out_spilled_fec_set.  If
     274             :    out_spilled_fec_set is NULL, the evicted FEC set metadata will not be
     275             :    written even if an in progress FEC set was evicted. */
     276             : 
     277             : int
     278             : fd_fec_resolver_add_shred( fd_fec_resolver_t         * resolver,
     279             :                            fd_shred_t const          * shred,
     280             :                            ulong                       shred_sz,
     281             :                            int                         is_repair,
     282             :                            uchar const               * leader_pubkey,
     283             :                            fd_fec_set_t const      * * out_fec_set,
     284             :                            fd_shred_t const        * * out_shred,
     285             :                            fd_bmtree_node_t          * out_merkle_root,
     286             :                            fd_fec_resolver_spilled_t * out_spilled_fec_set );
     287             : 
     288             : 
     289             : void * fd_fec_resolver_leave( fd_fec_resolver_t * resolver );
     290             : void * fd_fec_resolver_delete( void * shmem );
     291             : 
     292             : FD_PROTOTYPES_END
     293             : #endif /* HEADER_fd_src_disco_shred_fd_fec_resolver_h */

Generated by: LCOV version 1.14