LCOV - code coverage report
Current view: top level - disco/shred - fd_fec_resolver.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 425 507 83.8 %
Date: 2026-02-13 06:06:24 Functions: 10 13 76.9 %

          Line data    Source code
       1             : #include "../../ballet/shred/fd_shred.h"
       2             : #include "../../ballet/shred/fd_fec_set.h"
       3             : #include "../../ballet/sha512/fd_sha512.h"
       4             : #include "../../ballet/reedsol/fd_reedsol.h"
       5             : #include "../metrics/fd_metrics.h"
       6             : #include "fd_fec_resolver.h"
       7             : 
       8             : 
       9             : typedef union {
      10             :   fd_ed25519_sig_t u;
      11             :   ulong            l;
      12             : } wrapped_sig_t;
      13             : 
      14             : struct set_ctx;
      15             : typedef struct set_ctx set_ctx_t;
      16             : 
      17             : struct __attribute__((aligned(32UL))) set_ctx {
      18             :   wrapped_sig_t         sig;
      19             :   fd_fec_set_t *        set;
      20             :   fd_bmtree_commit_t  * tree;
      21             :   fd_bmtree_node_t      root;
      22             :   set_ctx_t *           prev;
      23             :   set_ctx_t *           next;
      24             :   ulong                 total_rx_shred_cnt;
      25             :   ulong                 fec_set_idx;
      26             :   /* The shred index of the first parity shred in this FEC set */
      27             :   ulong                 parity_idx0;
      28             :   uchar                 data_variant;
      29             :   uchar                 parity_variant;
      30             :   /* If this FEC set has resigned shreds, this is our signature of the
      31             :      root of the Merkle tree */
      32             :   wrapped_sig_t         retransmitter_sig;
      33             : };
      34             : typedef struct set_ctx set_ctx_t;
      35             : 
      36             : #define DEQUE_NAME freelist
      37         594 : #define DEQUE_T    fd_fec_set_t *
      38             : #include "../../util/tmpl/fd_deque_dynamic.c"
      39             : 
      40             : #define DEQUE_NAME bmtrlist
      41         321 : #define DEQUE_T    void *
      42             : #include "../../util/tmpl/fd_deque_dynamic.c"
      43             : 
      44             : static const wrapped_sig_t null_signature = {{0}};
      45             : 
      46       28008 : #define MAP_KEY               sig
      47       19812 : #define MAP_KEY_T             wrapped_sig_t
      48        8196 : #define MAP_KEY_NULL          null_signature
      49       48285 : #define MAP_KEY_EQUAL(k0,k1)  (!memcmp( (k0).u, (k1).u, FD_ED25519_SIG_SZ ))
      50       29442 : #define MAP_KEY_INVAL(k)      MAP_KEY_EQUAL( k, MAP_KEY_NULL )
      51             : #define MAP_KEY_EQUAL_IS_SLOW 1
      52       19425 : #define MAP_KEY_HASH(key,s)   ((MAP_HASH_T)fd_ulong_hash( key.l ^ (s) ))
      53             : #define MAP_MEMOIZE           0
      54             : #define MAP_NAME              ctx_map
      55       20256 : #define MAP_T                 set_ctx_t
      56             : /* The prev and next fields of set_ctx_t thread a linked list through
      57             :    the map.  The map can move elements around during a deletion though,
      58             :    so we need to update the links when it does.  Thankfully it gives a
      59             :    perfect hook for doing so. */
      60           6 : #define MAP_MOVE(d,s)   do { \
      61           6 :                           set_ctx_t * _d = &(d); \
      62           6 :                           set_ctx_t * _s = &(s); \
      63           6 :                           _s->prev->next = _d;   \
      64           6 :                           _s->next->prev = _d;   \
      65           6 :                           *_d = *_s;             \
      66           6 :                         } while( 0 )
      67             : #include "../../util/tmpl/fd_map_dynamic.c"
      68             : 
      69             : 
      70             : struct __attribute__((aligned(FD_FEC_RESOLVER_ALIGN))) fd_fec_resolver {
      71             :   /* depth stores the number of FEC sets this resolver can track
      72             :      simultaneously.  done_depth stores the depth of the done tcache,
      73             :      i.e. the number of done FEC set keys that this resolver remembers.
      74             :      partial_depth stores the minimum size of the free FEC set list.
      75             :      completed_depth stores the size of the completed FEC set list. */
      76             :   ulong depth;
      77             :   ulong partial_depth;
      78             :   ulong complete_depth;
      79             :   ulong done_depth;
      80             : 
      81             :   /* expected_shred_version: discard all shreds with a shred version
      82             :      other than the specified value */
      83             :   ushort expected_shred_version;
      84             : 
      85             :   /* curr_map: A map (using fd_map_dynamic) from tags of signatures to
      86             :      the context object with its relevant data.  This map contains at
      87             :      most `depth` elements at any time, but to improve query performance,
      88             :      we size it at 2*depth. */
      89             :   set_ctx_t * curr_map;
      90             : 
      91             :   /* curr_ll_sentinel: The elements of curr_map also make
      92             :      essentially a circular doubly linked list using the next and prev
      93             :      fields.  To simplify the logic, we use a sentinel node that's
      94             :      stored here instead of in the map.  Thus, the head (newest) and the
      95             :      tail (oldest) of the linked list are the next and prev pointers of
      96             :      this context (respectively).  The other fields aren't used. */
      97             :   set_ctx_t curr_ll_sentinel[1];
      98             : 
      99             :   /* done: stores signatures of FEC sets that have recently been
     100             :      completed.  This is like a tcache, but with a non-ulong key and
     101             :      using a linked list instead of a ring buffer. Any new packets
     102             :      matching tags in this set can be ignored.  Since the data structure
     103             :      we need (map with linked list) is very similar to for curr_map, we
     104             :      just use the same fd_map_dynamic instantiation.  Only fields sig,
     105             :      prev, and next are used. */
     106             :   set_ctx_t * done_map;
     107             : 
     108             :   /* done_ll_sentinel: Analogous to curr_ll_sentinel, but for the done
     109             :      map instead of the current map. */
     110             :   set_ctx_t   done_ll_sentinel[1];
     111             : 
     112             :   /* free_list and complete_list are deques (using fd_deque_dynamic)
     113             :      that FEC sets that are not in contexts in curr_map.  Similarly,
     114             :      bmtree_free_list stores footprints for the bmtree objects that are
     115             :      not in contexts in curr_map.  These lists point to objects of
     116             :      indeterminate state and need to be cleared/reset when popped off.
     117             :      Invariant: at every entry and exit to fd_fec_resolver_add_shred:
     118             :      - free_list has between partial_depth and partial_depth+depth
     119             :        elements.
     120             :      - complete_list has complete_depth elements
     121             :      - bmtree_free_list has between 0 and depth elements
     122             :      (all these counts are inclusive). */
     123             :   fd_fec_set_t * * free_list;
     124             :   fd_fec_set_t * * complete_list;
     125             :   void         * * bmtree_free_list;
     126             : 
     127             :   /* signer is used to sign shreds that require a retransmitter
     128             :      signature.  sign_ctx is provided as the first argument to the
     129             :      function. */
     130             :   fd_fec_resolver_sign_fn * signer;
     131             :   void                    * sign_ctx;
     132             : 
     133             :   /* max_shred_idx is the exclusive upper bound for shred indices.  We
     134             :      need to reject any shred with an index >= max_shred_idx, but we
     135             :      also want to reject anything that is part of an FEC set where the
     136             :      highest index of a shred in the FEC set will be >= max_shred_idx.
     137             :      */
     138             :   ulong max_shred_idx;
     139             : 
     140             :   /* sha512 and reedsol are used for calculations while adding a shred.
     141             :      Their state outside a call to add_shred is indeterminate. */
     142             :   fd_sha512_t   sha512[1];
     143             :   fd_reedsol_t  reedsol[1];
     144             : 
     145             :   /* The footprint for the objects follows the struct and is in the same
     146             :      order as the pointers, namely:
     147             :        curr_map map
     148             :        done_map map
     149             :        free_list deque
     150             :        complete_list deque
     151             :        bmtree_free_list deque
     152             :        Actual footprint for bmtrees */
     153             : };
     154             : 
     155             : typedef struct fd_fec_resolver fd_fec_resolver_t;
     156             : 
     157             : FD_FN_PURE ulong
     158             : fd_fec_resolver_footprint( ulong depth,
     159             :                            ulong partial_depth,
     160             :                            ulong complete_depth,
     161           3 :                            ulong done_depth ) {
     162           3 :   if( FD_UNLIKELY( (depth==0UL) | (partial_depth==0UL) | (complete_depth==0UL) | (done_depth==0UL) ) ) return 0UL;
     163           3 :   if( FD_UNLIKELY( (depth>=(1UL<<62)-1UL) | (done_depth>=(1UL<<62)-1UL ) ) ) return 0UL; /* prevent overflow */
     164             : 
     165           3 :   int lg_curr_map_cnt = fd_ulong_find_msb( depth      + 1UL ) + 2; /* See fd_tcache.h for the logic */
     166           3 :   int lg_done_map_cnt = fd_ulong_find_msb( done_depth + 1UL ) + 2; /*  ... behind the + 2. */
     167             : 
     168           3 :   ulong footprint_per_bmtree = fd_bmtree_commit_footprint( FD_SHRED_MERKLE_LAYER_CNT );
     169             : 
     170           3 :   ulong layout = FD_LAYOUT_INIT;
     171           3 :   layout = FD_LAYOUT_APPEND( layout, FD_FEC_RESOLVER_ALIGN,  sizeof(fd_fec_resolver_t)                      );
     172           3 :   layout = FD_LAYOUT_APPEND( layout, ctx_map_align(),        ctx_map_footprint( lg_curr_map_cnt )           );
     173           3 :   layout = FD_LAYOUT_APPEND( layout, ctx_map_align(),        ctx_map_footprint( lg_done_map_cnt )           );
     174           3 :   layout = FD_LAYOUT_APPEND( layout, freelist_align(),       freelist_footprint( depth+partial_depth+1UL )  );
     175           3 :   layout = FD_LAYOUT_APPEND( layout, freelist_align(),       freelist_footprint( complete_depth+1UL  )      );
     176           3 :   layout = FD_LAYOUT_APPEND( layout, bmtrlist_align(),       bmtrlist_footprint( depth+1UL )                );
     177           3 :   layout = FD_LAYOUT_APPEND( layout, FD_BMTREE_COMMIT_ALIGN, depth*footprint_per_bmtree                     );
     178             : 
     179           3 :   return FD_LAYOUT_FINI( layout, FD_FEC_RESOLVER_ALIGN );
     180           3 : }
     181             : 
     182           0 : FD_FN_CONST ulong fd_fec_resolver_align( void ) { return FD_FEC_RESOLVER_ALIGN; }
     183             : 
     184             : 
     185             : void *
     186             : fd_fec_resolver_new( void                    * shmem,
     187             :                      fd_fec_resolver_sign_fn * signer,
     188             :                      void                    * sign_ctx,
     189             :                      ulong                     depth,
     190             :                      ulong                     partial_depth,
     191             :                      ulong                     complete_depth,
     192             :                      ulong                     done_depth,
     193             :                      fd_fec_set_t            * sets,
     194         210 :                      ulong                     max_shred_idx ) {
     195         210 :   if( FD_UNLIKELY( (depth==0UL) | (partial_depth==0UL) | (complete_depth==0UL) | (done_depth==0UL) ) ) return NULL;
     196         210 :   if( FD_UNLIKELY( (depth>=(1UL<<62)-1UL) | (done_depth>=(1UL<<62)-1UL ) ) ) return NULL;
     197             : 
     198         210 :   int lg_curr_map_cnt = fd_ulong_find_msb( depth      + 1UL ) + 2;
     199         210 :   int lg_done_map_cnt = fd_ulong_find_msb( done_depth + 1UL ) + 2;
     200             : 
     201         210 :   ulong footprint_per_bmtree = fd_bmtree_commit_footprint( FD_SHRED_MERKLE_LAYER_CNT );
     202             : 
     203         210 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     204         210 :   void * self        = FD_SCRATCH_ALLOC_APPEND( l, FD_FEC_RESOLVER_ALIGN,  sizeof(fd_fec_resolver_t)                       );
     205         210 :   void * curr        = FD_SCRATCH_ALLOC_APPEND( l, ctx_map_align(),        ctx_map_footprint( lg_curr_map_cnt )            );
     206         210 :   void * done        = FD_SCRATCH_ALLOC_APPEND( l, ctx_map_align(),        ctx_map_footprint( lg_done_map_cnt )            );
     207         210 :   void * free        = FD_SCRATCH_ALLOC_APPEND( l, freelist_align(),       freelist_footprint( depth+partial_depth+1UL )   );
     208         210 :   void * cmplst      = FD_SCRATCH_ALLOC_APPEND( l, freelist_align(),       freelist_footprint( complete_depth+1UL  )       );
     209         210 :   void * bmfree      = FD_SCRATCH_ALLOC_APPEND( l, bmtrlist_align(),       bmtrlist_footprint( depth+1UL )                 );
     210         210 :   void * bmfootprint = FD_SCRATCH_ALLOC_APPEND( l, FD_BMTREE_COMMIT_ALIGN, depth*footprint_per_bmtree                      );
     211         210 :   FD_SCRATCH_ALLOC_FINI( l, FD_FEC_RESOLVER_ALIGN );
     212             : 
     213         210 :   fd_fec_resolver_t * resolver = (fd_fec_resolver_t *)self;
     214             : 
     215         210 :   if( FD_UNLIKELY( !ctx_map_new  ( curr,   lg_curr_map_cnt, 0UL    )) ) { FD_LOG_WARNING(( "curr map_new failed" )); return NULL; }
     216         210 :   if( FD_UNLIKELY( !ctx_map_new  ( done,   lg_done_map_cnt, 0UL    )) ) { FD_LOG_WARNING(( "done map_new failed" )); return NULL; }
     217         210 :   if( FD_UNLIKELY( !freelist_new ( free,   depth+partial_depth+1UL )) ) { FD_LOG_WARNING(( "freelist_new failed" )); return NULL; }
     218         210 :   if( FD_UNLIKELY( !freelist_new ( cmplst, complete_depth+1UL      )) ) { FD_LOG_WARNING(( "freelist_new failed" )); return NULL; }
     219         210 :   if( FD_UNLIKELY( !bmtrlist_new ( bmfree, depth                   )) ) { FD_LOG_WARNING(( "bmtrlist_new failed" )); return NULL; }
     220         210 :   if( FD_UNLIKELY( !fd_sha512_new( (void *)resolver->sha512        )) ) { FD_LOG_WARNING(( "sha512_new failed"   )); return NULL; }
     221             : 
     222             :   /* Initialize all the lists */
     223         210 :   fd_fec_set_t * * free_list     = freelist_join( free   );
     224         210 :   fd_fec_set_t * * complete_list = freelist_join( cmplst );
     225         840 :   for( ulong i=0UL;                 i<depth+partial_depth;                i++ ) { freelist_push_tail( free_list,     sets+i ); }
     226         420 :   for( ulong i=depth+partial_depth; i<depth+partial_depth+complete_depth; i++ ) { freelist_push_tail( complete_list, sets+i ); }
     227         210 :   freelist_leave( complete_list );
     228         210 :   freelist_leave( free_list     );
     229             : 
     230         210 :   void * * bmtree_list = bmtrlist_join( bmfree );
     231         630 :   for( ulong i=0UL; i<depth; i++ ) { bmtrlist_push_tail( bmtree_list, (uchar *)bmfootprint + i*footprint_per_bmtree ); }
     232         210 :   bmtrlist_leave( bmtree_list );
     233             : 
     234         210 :   resolver->curr_ll_sentinel->prev = resolver->curr_ll_sentinel;
     235         210 :   resolver->curr_ll_sentinel->next = resolver->curr_ll_sentinel;
     236         210 :   resolver->done_ll_sentinel->prev = resolver->done_ll_sentinel;
     237         210 :   resolver->done_ll_sentinel->next = resolver->done_ll_sentinel;
     238             : 
     239         210 :   resolver->depth                  = depth;
     240         210 :   resolver->partial_depth          = partial_depth;
     241         210 :   resolver->complete_depth         = complete_depth;
     242         210 :   resolver->done_depth             = done_depth;
     243         210 :   resolver->expected_shred_version = 0;
     244         210 :   resolver->signer                 = signer;
     245         210 :   resolver->sign_ctx               = sign_ctx;
     246         210 :   resolver->max_shred_idx          = max_shred_idx;
     247         210 :   return shmem;
     248         210 : }
     249             : 
     250             : fd_fec_resolver_t *
     251         210 : fd_fec_resolver_join( void * shmem ) {
     252         210 :   fd_fec_resolver_t * resolver = (fd_fec_resolver_t *)shmem;
     253         210 :   ulong depth          = resolver->depth;
     254         210 :   ulong partial_depth  = resolver->partial_depth;
     255         210 :   ulong complete_depth = resolver->complete_depth;
     256         210 :   ulong done_depth     = resolver->done_depth;
     257             : 
     258         210 :   int lg_curr_map_cnt = fd_ulong_find_msb( depth      + 1UL ) + 2;
     259         210 :   int lg_done_map_cnt = fd_ulong_find_msb( done_depth + 1UL ) + 2;
     260             : 
     261         210 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     262         210 :   /*     self       */ FD_SCRATCH_ALLOC_APPEND( l, FD_FEC_RESOLVER_ALIGN,  sizeof(fd_fec_resolver_t)                     );
     263         210 :   void * curr        = FD_SCRATCH_ALLOC_APPEND( l, ctx_map_align(),        ctx_map_footprint( lg_curr_map_cnt )          );
     264         210 :   void * done        = FD_SCRATCH_ALLOC_APPEND( l, ctx_map_align(),        ctx_map_footprint( lg_done_map_cnt )          );
     265         210 :   void * free        = FD_SCRATCH_ALLOC_APPEND( l, freelist_align(),       freelist_footprint( depth+partial_depth+1UL ) );
     266         210 :   void * cmplst      = FD_SCRATCH_ALLOC_APPEND( l, freelist_align(),       freelist_footprint( complete_depth+1UL  )     );
     267         210 :   void * bmfree      = FD_SCRATCH_ALLOC_APPEND( l, bmtrlist_align(),       bmtrlist_footprint( depth+1UL )               );
     268         210 :   FD_SCRATCH_ALLOC_FINI( l, FD_FEC_RESOLVER_ALIGN );
     269             : 
     270         210 :   resolver->curr_map         = ctx_map_join  ( curr   ); if( FD_UNLIKELY( !resolver->curr_map         ) ) return NULL;
     271         210 :   resolver->done_map         = ctx_map_join  ( done   ); if( FD_UNLIKELY( !resolver->done_map         ) ) return NULL;
     272         210 :   resolver->free_list        = freelist_join ( free   ); if( FD_UNLIKELY( !resolver->free_list        ) ) return NULL;
     273         210 :   resolver->complete_list    = freelist_join ( cmplst ); if( FD_UNLIKELY( !resolver->complete_list    ) ) return NULL;
     274         210 :   resolver->bmtree_free_list = bmtrlist_join ( bmfree ); if( FD_UNLIKELY( !resolver->bmtree_free_list ) ) return NULL;
     275         210 :   if( FD_UNLIKELY( !fd_sha512_join( resolver->sha512 ) ) ) return NULL;
     276             : 
     277         210 :   return resolver;
     278         210 : }
     279             : 
     280             : void
     281             : fd_fec_resolver_set_shred_version( fd_fec_resolver_t * resolver,
     282         210 :                                    ushort              expected_shred_version ) {
     283         210 :   resolver->expected_shred_version = expected_shred_version;
     284         210 : }
     285             : 
     286             : /* Two helper functions for working with the linked lists that are
     287             :    threaded through maps.  Use them as follows:
     288             :       ctx_ll_insert( <sentinel corresponding to map>, ctx_map_insert( <map>, key ) );
     289             :       ctx_map_remove( <map>, ctx_ll_remove( <node to remove> ) );
     290             : 
     291             :   */
     292             : /* Removes r from the linked list */
     293             : static set_ctx_t *
     294         366 : ctx_ll_remove( set_ctx_t * r ) {
     295         366 :   r->next->prev = r->prev;
     296         366 :   r->prev->next = r->next;
     297         366 :   r->next = NULL;
     298         366 :   r->prev = NULL;
     299         366 :   return r;
     300         366 : }
     301             : 
     302             : /* Inserts c immediately after p.  Returns c. */
     303             : static set_ctx_t *
     304         594 : ctx_ll_insert( set_ctx_t * p, set_ctx_t * c ) {
     305         594 :   c->next = p->next;
     306         594 :   c->prev = p;
     307         594 :   p->next->prev = c;
     308         594 :   p->next       = c;
     309         594 :   return c;
     310         594 : }
     311             : 
     312             : int
     313             : fd_fec_resolver_add_shred( fd_fec_resolver_t         * resolver,
     314             :                            fd_shred_t const          * shred,
     315             :                            ulong                       shred_sz,
     316             :                            uchar const               * leader_pubkey,
     317             :                            fd_fec_set_t const      * * out_fec_set,
     318             :                            fd_shred_t const        * * out_shred,
     319             :                            fd_bmtree_node_t          * out_merkle_root,
     320             :                            fd_fec_resolver_spilled_t * out_spilled,
     321        9618 :                            int                         enforce_fixed_fec ) {
     322             :   /* Unpack variables */
     323        9618 :   ulong partial_depth = resolver->partial_depth;
     324        9618 :   ulong done_depth    = resolver->done_depth;
     325             : 
     326        9618 :   fd_fec_set_t * * free_list        = resolver->free_list;
     327        9618 :   fd_fec_set_t * * complete_list    = resolver->complete_list;
     328        9618 :   void         * * bmtree_free_list = resolver->bmtree_free_list;
     329        9618 :   set_ctx_t    *   curr_map         = resolver->curr_map;
     330        9618 :   set_ctx_t    *   done_map         = resolver->done_map;
     331             : 
     332        9618 :   fd_reedsol_t * reedsol       = resolver->reedsol;
     333        9618 :   fd_sha512_t  * sha512        = resolver->sha512;
     334             : 
     335        9618 :   set_ctx_t    * curr_ll_sentinel = resolver->curr_ll_sentinel;
     336        9618 :   set_ctx_t    * done_ll_sentinel = resolver->done_ll_sentinel;
     337             : 
     338             :   /* Invariants:
     339             :       * no key is in both the done map and the current map
     340             :       * each set pointer provided to the new function is in exactly one
     341             :           of curr_map, freelist, or complete_list
     342             :       * bmtree_free_list has exactly partial_depth fewer elements than
     343             :           freelist
     344             :    */
     345        9618 :   wrapped_sig_t * w_sig = (wrapped_sig_t *)shred->signature;
     346             : 
     347             :   /* Immediately reject any shred with a 0 signature. */
     348        9618 :   if( FD_UNLIKELY( ctx_map_key_inval( *w_sig ) ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     349             : 
     350             :   /* Are we already done with this FEC set? */
     351        9618 :   int found = !!ctx_map_query( done_map, *w_sig, NULL );
     352             : 
     353        9618 :   if( found )  return FD_FEC_RESOLVER_SHRED_IGNORED; /* With no packet loss, we expect found==1 about 50% of the time */
     354             : 
     355        9180 :   set_ctx_t * ctx = ctx_map_query( curr_map, *w_sig, NULL );
     356             : 
     357        9180 :   fd_bmtree_node_t leaf[1];
     358        9180 :   uchar variant    = shred->variant;
     359        9180 :   uchar shred_type = fd_shred_type( variant );
     360             : 
     361        9180 :   if( FD_UNLIKELY( (shred_type==FD_SHRED_TYPE_LEGACY_DATA) | (shred_type==FD_SHRED_TYPE_LEGACY_CODE) ) ) {
     362             :     /* Reject any legacy shreds */
     363           0 :     return FD_FEC_RESOLVER_SHRED_REJECTED;
     364           0 :   }
     365             : 
     366        9180 :   if( FD_UNLIKELY( shred->version!=resolver->expected_shred_version ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     367        9177 :   if( FD_UNLIKELY( shred_sz<fd_shred_sz( shred )                    ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     368        9177 :   if( FD_UNLIKELY( shred->idx>=resolver->max_shred_idx              ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     369             : 
     370        9165 :   int is_data_shred = fd_shred_is_data( shred_type );
     371             : 
     372        9165 :   if( !is_data_shred ) { /* Roughly 50/50 branch */
     373        4707 :     if( FD_UNLIKELY( (shred->code.data_cnt>FD_REEDSOL_DATA_SHREDS_MAX) | (shred->code.code_cnt>FD_REEDSOL_PARITY_SHREDS_MAX) ) )
     374           3 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     375        4704 :     if( FD_UNLIKELY( (shred->code.data_cnt==0UL) | (shred->code.code_cnt==0UL)                                               ) )
     376           0 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     377        4704 :     if( FD_UNLIKELY( (ulong)shred->fec_set_idx+(ulong)shred->code.data_cnt>=resolver->max_shred_idx                          ) )
     378           3 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     379        4701 :     if( FD_UNLIKELY( (ulong)shred->idx + (ulong)shred->code.code_cnt - (ulong)shred->code.idx>=resolver->max_shred_idx       ) )
     380           3 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     381        4701 :   }
     382             : 
     383             : 
     384             :   /* For the purposes of the shred header, tree_depth means the number
     385             :      of nodes, counting the leaf but excluding the root.  For bmtree,
     386             :      depth means the number of layers, which counts both. */
     387        9156 :   ulong tree_depth           = fd_shred_merkle_cnt( variant ); /* In [0, 15] */
     388        9156 :   ulong reedsol_protected_sz = 1115UL + FD_SHRED_DATA_HEADER_SZ - FD_SHRED_SIGNATURE_SZ - FD_SHRED_MERKLE_NODE_SZ*tree_depth
     389        9156 :                                       - FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type )
     390        9156 :                                       - FD_SHRED_SIGNATURE_SZ  *fd_shred_is_resigned( shred_type); /* In [743, 1139] conservatively*/
     391        9156 :   ulong data_merkle_protected_sz   = reedsol_protected_sz + FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type );
     392        9156 :   ulong parity_merkle_protected_sz = reedsol_protected_sz + FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type )+FD_SHRED_CODE_HEADER_SZ-FD_ED25519_SIG_SZ;
     393        9156 :   ulong merkle_protected_sz  = fd_ulong_if( is_data_shred, data_merkle_protected_sz, parity_merkle_protected_sz );
     394             : 
     395        9156 :   fd_bmtree_hash_leaf( leaf, (uchar const *)shred + sizeof(fd_ed25519_sig_t), merkle_protected_sz, FD_BMTREE_LONG_PREFIX_SZ );
     396             : 
     397             :   /* in_type_idx is between [0, code.data_cnt) or [0, code.code_cnt),
     398             :      where data_cnt <= FD_REEDSOL_DATA_SHREDS_MAX and code_cnt <=
     399             :      FD_REEDSOL_PARITY_SHREDS_MAX.
     400             :      On the other hand, shred_idx, goes from [0, code.data_cnt +
     401             :      code.code_cnt), with all the data shreds having
     402             :      shred_idx < code.data_cnt and all the parity shreds having
     403             :      shred_idx >= code.data_cnt. */
     404        9156 :   ulong in_type_idx = fd_ulong_if( is_data_shred, shred->idx - shred->fec_set_idx, shred->code.idx );
     405        9156 :   ulong shred_idx   = fd_ulong_if( is_data_shred, in_type_idx, in_type_idx + shred->code.data_cnt  );
     406             : 
     407        9156 :   if( enforce_fixed_fec ) {
     408           0 :     if( !is_data_shred ) {
     409           0 :       if( FD_UNLIKELY( (shred->code.data_cnt!=FD_REEDSOL_FEC_SHRED_CNT) | (shred->code.code_cnt!=FD_REEDSOL_FEC_SHRED_CNT) ) )
     410           0 :         return FD_FEC_RESOLVER_SHRED_REJECTED;
     411           0 :     }
     412           0 :     if( FD_UNLIKELY( ( shred->fec_set_idx % FD_REEDSOL_FEC_SHRED_CNT ) != 0UL ) )
     413           0 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     414           0 :     if( FD_UNLIKELY( in_type_idx >= FD_REEDSOL_FEC_SHRED_CNT ) )
     415           0 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     416           0 :     if( FD_UNLIKELY( is_data_shred && (shred->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE) && shred->idx % FD_REEDSOL_FEC_SHRED_CNT != FD_REEDSOL_FEC_SHRED_CNT - 1UL ) )
     417           0 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     418           0 :   }
     419             : 
     420        9156 :   if( FD_UNLIKELY( in_type_idx >= fd_ulong_if( is_data_shred, FD_REEDSOL_DATA_SHREDS_MAX, FD_REEDSOL_PARITY_SHREDS_MAX ) ) )
     421           0 :     return FD_FEC_RESOLVER_SHRED_REJECTED;
     422             :   /* This, combined with the check on shred->code.data_cnt implies that
     423             :      shred_idx is in [0, DATA_SHREDS_MAX+PARITY_SHREDS_MAX). */
     424             : 
     425        9156 :   if( FD_UNLIKELY( tree_depth>FD_SHRED_MERKLE_LAYER_CNT-1UL          ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     426        9156 :   if( FD_UNLIKELY( fd_bmtree_depth( shred_idx+1UL ) > tree_depth+1UL ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     427             : 
     428        9156 :   if( FD_UNLIKELY( !ctx ) ) { /* This is the first shred in the FEC set */
     429             : 
     430         321 :     if( FD_UNLIKELY( freelist_cnt( free_list )<=partial_depth ) ) {
     431             :       /* Packet loss is really high and we have a lot of in-progress FEC
     432             :          sets that we haven't been able to finish.  Take the resources
     433             :          (FEC set and bmtree) from the oldest, and send the oldest FEC
     434             :          set to the back of the free list. */
     435          36 :       set_ctx_t * victim_ctx = resolver->curr_ll_sentinel->prev;
     436             : 
     437             : 
     438          36 :       if( FD_LIKELY( out_spilled ) ) {
     439           6 :         fd_fec_set_t * set = victim_ctx->set;
     440             : 
     441             :         /* Find the highest data shred received in the FEC set */
     442             : 
     443           6 :         ulong max_rcvd_dshred_idx = d_rcvd_last( set->data_shred_rcvd );
     444           6 :         fd_shred_t const * max_shred;
     445           6 :         if( FD_UNLIKELY( max_rcvd_dshred_idx == ~0UL ) ) {
     446           0 :           max_rcvd_dshred_idx = FD_SHRED_BLK_MAX; /* No data shreds received. Use a parity shred to determine the spilled slot and fec_set_idx */
     447           0 :           max_shred = fd_shred_parse( set->parity_shreds[ p_rcvd_first( set->parity_shred_rcvd ) ], FD_SHRED_MAX_SZ );
     448           6 :         } else {
     449           6 :           max_shred = fd_shred_parse( set->data_shreds  [ max_rcvd_dshred_idx ],                    FD_SHRED_MIN_SZ );
     450           6 :         }
     451             : 
     452           6 :         if( FD_LIKELY( out_spilled ) ) {
     453           6 :           out_spilled->slot           = max_shred->slot;
     454           6 :           out_spilled->fec_set_idx    = max_shred->fec_set_idx;
     455           6 :           out_spilled->max_dshred_idx = (uint)max_rcvd_dshred_idx;
     456           6 :         }
     457             : 
     458           6 :         FD_LOG_INFO(("Spilled from fec_resolver in-progress map %lu %u, data_shreds_rcvd %lu, parity_shreds_rcvd %lu, max_d_rcvd_shred_idx %lu", max_shred->slot, max_shred->fec_set_idx, d_rcvd_cnt( set->data_shred_rcvd ), p_rcvd_cnt( set->parity_shred_rcvd ), max_rcvd_dshred_idx ));
     459           6 :       }
     460             : 
     461          36 :       freelist_push_tail( free_list,        victim_ctx->set  );
     462          36 :       bmtrlist_push_tail( bmtree_free_list, victim_ctx->tree );
     463             : 
     464             :       /* Remove from linked list and then from the map */
     465          36 :       ctx_map_remove( curr_map, ctx_ll_remove( victim_ctx ) );
     466             : 
     467          36 :       FD_MCNT_INC( SHRED, FEC_SET_SPILLED, 1UL );
     468          36 :     }
     469             :     /* Now we know |free_list|>partial_depth and |bmtree_free_list|>1 */
     470             : 
     471         321 :     fd_fec_set_t * set_to_use = freelist_pop_head( free_list        );
     472         321 :     void         * bmtree_mem = bmtrlist_pop_head( bmtree_free_list );
     473             : 
     474             :     /* Now we need to derive the root of the Merkle tree and verify the
     475             :        signature to prevent a DOS attack just by sending lots of invalid
     476             :        shreds. */
     477         321 :     fd_bmtree_commit_t * tree;
     478         321 :     tree = fd_bmtree_commit_init( bmtree_mem, FD_SHRED_MERKLE_NODE_SZ, FD_BMTREE_LONG_PREFIX_SZ, FD_SHRED_MERKLE_LAYER_CNT );
     479             : 
     480         321 :     fd_bmtree_node_t _root[1];
     481         321 :     fd_shred_merkle_t const * proof = fd_shred_merkle_nodes( shred );
     482         321 :     int rv = fd_bmtree_commitp_insert_with_proof( tree, shred_idx, leaf, (uchar const *)proof, tree_depth, _root );
     483         321 :     if( FD_UNLIKELY( !rv ) ) {
     484           0 :       freelist_push_head( free_list,        set_to_use );
     485           0 :       bmtrlist_push_head( bmtree_free_list, bmtree_mem );
     486           0 :       FD_MCNT_INC( SHRED, SHRED_REJECTED_INITIAL, 1UL );
     487           0 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     488           0 :     }
     489             : 
     490         321 :     if( FD_UNLIKELY( FD_ED25519_SUCCESS != fd_ed25519_verify( _root->hash, 32UL, shred->signature, leader_pubkey, sha512 ) ) ) {
     491           0 :       freelist_push_head( free_list,        set_to_use );
     492           0 :       bmtrlist_push_head( bmtree_free_list, bmtree_mem );
     493           0 :       FD_MCNT_INC( SHRED, SHRED_REJECTED_INITIAL, 1UL );
     494           0 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     495           0 :     }
     496             : 
     497             :     /* This seems like a legitimate FEC set, so we can reserve some
     498             :        resources for it. */
     499         321 :     ctx = ctx_ll_insert( curr_ll_sentinel, ctx_map_insert( curr_map, *w_sig ) );
     500         321 :     ctx->set  = set_to_use;
     501         321 :     ctx->tree = tree;
     502         321 :     ctx->root = *_root;
     503         321 :     ctx->total_rx_shred_cnt = 0UL;
     504         321 :     ctx->data_variant   = fd_uchar_if(  is_data_shred, variant, fd_shred_variant( fd_shred_swap_type( shred_type ), (uchar)tree_depth ) );
     505         321 :     ctx->parity_variant = fd_uchar_if( !is_data_shred, variant, fd_shred_variant( fd_shred_swap_type( shred_type ), (uchar)tree_depth ) );
     506         321 :     if( enforce_fixed_fec ) {
     507           0 :       ctx->set->data_shred_cnt   = FD_REEDSOL_FEC_SHRED_CNT;
     508           0 :       ctx->set->parity_shred_cnt = FD_REEDSOL_FEC_SHRED_CNT;
     509           0 :       ctx->parity_idx0           = shred->fec_set_idx;
     510           0 :       ctx->fec_set_idx           = shred->fec_set_idx;
     511         321 :     } else {
     512         321 :       ctx->set->data_shred_cnt   = SHRED_CNT_NOT_SET;
     513         321 :       ctx->set->parity_shred_cnt = SHRED_CNT_NOT_SET;
     514         321 :     }
     515             : 
     516         321 :     if( FD_UNLIKELY( fd_shred_is_resigned( shred_type ) & !!(resolver->signer) ) ) {
     517           3 :       resolver->signer( resolver->sign_ctx, ctx->retransmitter_sig.u, _root->hash );
     518         318 :     } else {
     519         318 :       fd_memset( ctx->retransmitter_sig.u, 0, 64UL );
     520         318 :     }
     521             : 
     522             :     /* Reset the FEC set */
     523         321 :     d_rcvd_join( d_rcvd_new( d_rcvd_delete( d_rcvd_leave( ctx->set->data_shred_rcvd   ) ) ) );
     524         321 :     p_rcvd_join( p_rcvd_new( p_rcvd_delete( p_rcvd_leave( ctx->set->parity_shred_rcvd ) ) ) );
     525             : 
     526             :     /* Copy the merkle root into the output arg. */
     527         321 :     if( FD_LIKELY( out_merkle_root ) ) memcpy( out_merkle_root, ctx->root.hash, sizeof(fd_bmtree_node_t) );
     528             : 
     529        8835 :   } else {
     530             :     /* This is not the first shred in the set */
     531             :     /* First, check to make sure this is not a duplicate */
     532        8835 :     int shred_dup = fd_int_if( is_data_shred, d_rcvd_test( ctx->set->data_shred_rcvd,   in_type_idx ),
     533        8835 :                                               p_rcvd_test( ctx->set->parity_shred_rcvd, in_type_idx ) );
     534             : 
     535        8835 :     if( FD_UNLIKELY( shred_dup ) ) return FD_FEC_RESOLVER_SHRED_IGNORED;
     536             : 
     537             :     /* Ensure that all the shreds in the FEC set have consistent
     538             :        variants.  They all must have the same tree_depth and the same
     539             :        chained/not chained, resigned/not resigned bits. */
     540        8622 :     if( FD_UNLIKELY( variant!=fd_uchar_if( is_data_shred, ctx->data_variant, ctx->parity_variant ) ) ) {
     541           0 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     542           0 :     }
     543             : 
     544        8622 :     fd_shred_merkle_t const * proof = fd_shred_merkle_nodes( shred );
     545        8622 :     int rv = fd_bmtree_commitp_insert_with_proof( ctx->tree, shred_idx, leaf, (uchar const *)proof, tree_depth, out_merkle_root );
     546        8622 :     if( !rv ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     547        8622 :   }
     548             : 
     549        8937 :   if( FD_UNLIKELY( (ctx->set->data_shred_cnt==SHRED_CNT_NOT_SET) & (!is_data_shred) ) ) {
     550         294 :     ctx->set->data_shred_cnt   = shred->code.data_cnt;
     551         294 :     ctx->set->parity_shred_cnt = shred->code.code_cnt;
     552         294 :     ctx->parity_idx0           = shred->idx - in_type_idx;
     553         294 :     ctx->fec_set_idx           = shred->fec_set_idx;
     554         294 :   }
     555             : 
     556             :   /* At this point, the shred has passed Merkle validation and is new.
     557             :      We also know that ctx is a pointer to the slot for signature in the
     558             :      current map. */
     559             : 
     560             :   /* Copy the shred to memory the FEC resolver owns */
     561        8937 :   uchar * dst = fd_ptr_if( is_data_shred, ctx->set->data_shreds[ in_type_idx ], ctx->set->parity_shreds[ in_type_idx ] );
     562        8937 :   fd_memcpy( dst, shred, fd_shred_sz( shred ) );
     563             : 
     564             :   /* If the shred needs a retransmitter signature, set it */
     565        8937 :   if( FD_UNLIKELY( fd_shred_is_resigned( shred_type ) ) ) {
     566         729 :     memcpy( dst + fd_shred_retransmitter_sig_off( (fd_shred_t *)dst ), ctx->retransmitter_sig.u, 64UL );
     567         729 :   }
     568             : 
     569        8937 :   d_rcvd_insert_if( ctx->set->data_shred_rcvd,    is_data_shred, in_type_idx );
     570        8937 :   p_rcvd_insert_if( ctx->set->parity_shred_rcvd, !is_data_shred, in_type_idx );
     571        8937 :   ctx->total_rx_shred_cnt++;
     572             : 
     573        8937 :   *out_shred = (fd_shred_t const *)dst;
     574             : 
     575             :   /* Do we have enough to begin reconstruction? */
     576        8937 :   if( FD_LIKELY( ctx->total_rx_shred_cnt < ctx->set->data_shred_cnt ) ) return FD_FEC_RESOLVER_SHRED_OKAY;
     577             : 
     578             :   /* At this point, the FEC set is either valid or permanently invalid,
     579             :      so we can consider it done either way.  First though, since ctx_map_remove
     580             :      can change what's at *ctx, so unpack the values before we do that */
     581         270 :   fd_fec_set_t        * set            = ctx->set;
     582         270 :   fd_bmtree_commit_t  * tree           = ctx->tree;
     583         270 :   ulong                 fec_set_idx    = ctx->fec_set_idx;
     584         270 :   ulong                 parity_idx0    = ctx->parity_idx0;
     585         270 :   wrapped_sig_t         retran_sig     = ctx->retransmitter_sig;
     586         270 :   uchar                 parity_variant = ctx->parity_variant;
     587         270 :   uchar                 data_variant   = ctx->data_variant;
     588             : 
     589         270 :   ctx_ll_insert( done_ll_sentinel, ctx_map_insert( done_map, ctx->sig ) );
     590         270 :   if( FD_UNLIKELY( ctx_map_key_cnt( done_map ) > done_depth ) ) ctx_map_remove( done_map, ctx_ll_remove( done_ll_sentinel->prev ) );
     591             : 
     592         270 :   ctx_map_remove( curr_map, ctx_ll_remove( ctx ) );
     593             : 
     594         270 :   reedsol = fd_reedsol_recover_init( (void*)reedsol, reedsol_protected_sz );
     595        8826 :   for( ulong i=0UL; i<set->data_shred_cnt; i++ ) {
     596        8556 :     uchar * rs_payload = set->data_shreds[ i ] + sizeof(fd_ed25519_sig_t);
     597        8556 :     if( d_rcvd_test( set->data_shred_rcvd, i ) ) fd_reedsol_recover_add_rcvd_shred  ( reedsol, 1, rs_payload );
     598        4416 :     else                                         fd_reedsol_recover_add_erased_shred( reedsol, 1, rs_payload );
     599        8556 :   }
     600        8988 :   for( ulong i=0UL; i<set->parity_shred_cnt; i++ ) {
     601        8718 :     uchar * rs_payload = set->parity_shreds[ i ] + FD_SHRED_CODE_HEADER_SZ;
     602        8718 :     if( p_rcvd_test( set->parity_shred_rcvd, i ) ) fd_reedsol_recover_add_rcvd_shred  ( reedsol, 0, rs_payload );
     603        4254 :     else                                           fd_reedsol_recover_add_erased_shred( reedsol, 0, rs_payload );
     604        8718 :   }
     605             : 
     606         270 :   if( FD_UNLIKELY( FD_REEDSOL_SUCCESS != fd_reedsol_recover_fini( reedsol ) ) ) {
     607             :     /* A few lines up, we already checked to make sure it wasn't the
     608             :        insufficient case, so it must be the inconsistent case.  That
     609             :        means the leader signed a shred with invalid Reed-Solomon FEC
     610             :        set.  This shouldn't happen in practice, but we need to handle it
     611             :        for the malicious leader case.  This should probably be a
     612             :        slash-able offense. */
     613           0 :     freelist_push_tail( free_list,        set  );
     614           0 :     bmtrlist_push_tail( bmtree_free_list, tree );
     615           0 :     FD_MCNT_INC( SHRED, FEC_REJECTED_FATAL, 1UL );
     616           0 :     return FD_FEC_RESOLVER_SHRED_REJECTED;
     617           0 :   }
     618             : 
     619         270 :   uchar const * chained_root = fd_ptr_if( fd_shred_is_chained( shred_type ), (uchar *)shred+fd_shred_chain_off( variant ), NULL );
     620             : 
     621             :   /* Iterate over recovered shreds, add them to the Merkle tree,
     622             :      populate headers and signatures. */
     623        8826 :   for( ulong i=0UL; i<set->data_shred_cnt; i++ ) {
     624        8556 :     if( !d_rcvd_test( set->data_shred_rcvd, i ) ) {
     625        4416 :       fd_memcpy( set->data_shreds[i], shred, sizeof(fd_ed25519_sig_t) );
     626        4416 :       if( FD_LIKELY( fd_shred_is_chained( shred_type ) ) ) {
     627        2997 :         fd_memcpy( set->data_shreds[i]+fd_shred_chain_off( data_variant ), chained_root, FD_SHRED_MERKLE_ROOT_SZ );
     628        2997 :       }
     629        4416 :       fd_bmtree_hash_leaf( leaf, set->data_shreds[i]+sizeof(fd_ed25519_sig_t), data_merkle_protected_sz, FD_BMTREE_LONG_PREFIX_SZ );
     630        4416 :       if( FD_UNLIKELY( !fd_bmtree_commitp_insert_with_proof( tree, i, leaf, NULL, 0, NULL ) ) ) {
     631           0 :         freelist_push_tail( free_list,        set  );
     632           0 :         bmtrlist_push_tail( bmtree_free_list, tree );
     633           0 :         FD_MCNT_INC( SHRED, FEC_REJECTED_FATAL, 1UL );
     634           0 :         return FD_FEC_RESOLVER_SHRED_REJECTED;
     635           0 :       }
     636             : 
     637        4416 :     }
     638        8556 :   }
     639             : 
     640        8988 :   for( ulong i=0UL; i<set->parity_shred_cnt; i++ ) {
     641        8718 :     if( !p_rcvd_test( set->parity_shred_rcvd, i ) ) {
     642        4254 :       fd_shred_t * p_shred = (fd_shred_t *)set->parity_shreds[i]; /* We can't parse because we haven't populated the header */
     643        4254 :       fd_memcpy( p_shred->signature, shred->signature, sizeof(fd_ed25519_sig_t) );
     644        4254 :       p_shred->variant       = parity_variant;
     645        4254 :       p_shred->slot          = shred->slot;
     646        4254 :       p_shred->idx           = (uint)(i + parity_idx0);
     647        4254 :       p_shred->version       = shred->version;
     648        4254 :       p_shred->fec_set_idx   = (uint)fec_set_idx;
     649        4254 :       p_shred->code.data_cnt = (ushort)set->data_shred_cnt;
     650        4254 :       p_shred->code.code_cnt = (ushort)set->parity_shred_cnt;
     651        4254 :       p_shred->code.idx      = (ushort)i;
     652             : 
     653        4254 :       if( FD_LIKELY( fd_shred_is_chained( shred_type ) ) ) {
     654        3162 :         fd_memcpy( set->parity_shreds[i]+fd_shred_chain_off( parity_variant ), chained_root, FD_SHRED_MERKLE_ROOT_SZ );
     655        3162 :       }
     656             : 
     657        4254 :       fd_bmtree_hash_leaf( leaf, set->parity_shreds[i]+ sizeof(fd_ed25519_sig_t), parity_merkle_protected_sz, FD_BMTREE_LONG_PREFIX_SZ );
     658        4254 :       if( FD_UNLIKELY( !fd_bmtree_commitp_insert_with_proof( tree, set->data_shred_cnt + i, leaf, NULL, 0, NULL ) ) ) {
     659           0 :         freelist_push_tail( free_list,        set  );
     660           0 :         bmtrlist_push_tail( bmtree_free_list, tree );
     661           0 :         FD_MCNT_INC( SHRED, FEC_REJECTED_FATAL, 1UL );
     662           0 :         return FD_FEC_RESOLVER_SHRED_REJECTED;
     663           0 :       }
     664        4254 :     }
     665        8718 :   }
     666             : 
     667             :   /* Check that the whole Merkle tree is consistent. */
     668         270 :   if( FD_UNLIKELY( !fd_bmtree_commitp_fini( tree, set->data_shred_cnt + set->parity_shred_cnt ) ) ) {
     669           0 :     freelist_push_tail( free_list,        set  );
     670           0 :     bmtrlist_push_tail( bmtree_free_list, tree );
     671           0 :     FD_MCNT_INC( SHRED, FEC_REJECTED_FATAL, 1UL );
     672           0 :     return FD_FEC_RESOLVER_SHRED_REJECTED;
     673           0 :   }
     674             : 
     675             :   /* Check that all the fields that are supposed to be consistent across
     676             :      an FEC set actually are. */
     677         270 :   fd_shred_t const * base_data_shred   = fd_shred_parse( set->data_shreds  [ 0 ], FD_SHRED_MIN_SZ );
     678         270 :   fd_shred_t const * base_parity_shred = fd_shred_parse( set->parity_shreds[ 0 ], FD_SHRED_MAX_SZ );
     679         270 :   int reject = (!base_data_shred) | (!base_parity_shred);
     680             : 
     681        8556 :   for( ulong i=1UL; (!reject) & (i<set->data_shred_cnt); i++ ) {
     682             :     /* Technically, we only need to re-parse the ones we recovered with
     683             :        Reedsol, but parsing is pretty cheap and the rest of the
     684             :        validation we need to do on all of them. */
     685        8286 :     fd_shred_t const * parsed = fd_shred_parse( set->data_shreds[ i ], FD_SHRED_MIN_SZ );
     686        8286 :     if( FD_UNLIKELY( !parsed ) ) { reject = 1; break; }
     687        8286 :     reject |= parsed->variant         != base_data_shred->variant;
     688        8286 :     reject |= parsed->slot            != base_data_shred->slot;
     689        8286 :     reject |= parsed->version         != base_data_shred->version;
     690        8286 :     reject |= parsed->fec_set_idx     != base_data_shred->fec_set_idx;
     691        8286 :     reject |= parsed->data.parent_off != base_data_shred->data.parent_off;
     692             : 
     693        8286 :     reject |= fd_shred_is_chained( fd_shred_type( parsed->variant ) ) &&
     694        8286 :                 !fd_memeq( (uchar *)parsed         +fd_shred_chain_off( parsed->variant          ),
     695        5817 :                            (uchar *)base_data_shred+fd_shred_chain_off( base_data_shred->variant ), FD_SHRED_MERKLE_ROOT_SZ );
     696        8286 :   }
     697             : 
     698        8988 :   for( ulong i=0UL; (!reject) & (i<set->parity_shred_cnt); i++ ) {
     699        8718 :     fd_shred_t const * parsed = fd_shred_parse( set->parity_shreds[ i ], FD_SHRED_MAX_SZ );
     700        8718 :     if( FD_UNLIKELY( !parsed ) ) { reject = 1; break; }
     701        8718 :     reject |= fd_shred_type( parsed->variant )       != fd_shred_swap_type( fd_shred_type( base_data_shred->variant ) );
     702        8718 :     reject |= fd_shred_merkle_cnt( parsed->variant ) != fd_shred_merkle_cnt( base_data_shred->variant );
     703        8718 :     reject |= parsed->slot                           != base_data_shred->slot;
     704        8718 :     reject |= parsed->version                        != base_data_shred->version;
     705        8718 :     reject |= parsed->fec_set_idx                    != base_data_shred->fec_set_idx;
     706        8718 :     reject |= parsed->code.data_cnt                  != base_parity_shred->code.data_cnt;
     707        8718 :     reject |= parsed->code.code_cnt                  != base_parity_shred->code.code_cnt;
     708        8718 :     reject |= parsed->code.idx                       != (ushort)i;
     709             : 
     710        8718 :     reject |= fd_shred_is_chained( fd_shred_type( parsed->variant ) ) &&
     711        8718 :                 !fd_memeq( (uchar *)parsed         +fd_shred_chain_off( parsed->variant          ),
     712        6174 :                            (uchar *)base_data_shred+fd_shred_chain_off( base_data_shred->variant ), FD_SHRED_MERKLE_ROOT_SZ );
     713        8718 :   }
     714         270 :   if( FD_UNLIKELY( reject ) ) {
     715           0 :     freelist_push_tail( free_list,        set  );
     716           0 :     bmtrlist_push_tail( bmtree_free_list, tree );
     717           0 :     FD_MCNT_INC( SHRED, FEC_REJECTED_FATAL, 1UL );
     718           0 :     return FD_FEC_RESOLVER_SHRED_REJECTED;
     719           0 :   }
     720             : 
     721             :   /* Populate missing Merkle proofs */
     722        8826 :   for( ulong i=0UL; i<set->data_shred_cnt; i++ ) if( !d_rcvd_test( set->data_shred_rcvd, i ) )
     723        4416 :     fd_bmtree_get_proof( tree, set->data_shreds[i]   + fd_shred_merkle_off( (fd_shred_t *)set->data_shreds[i] ), i );
     724             : 
     725        8988 :   for( ulong i=0UL; i<set->parity_shred_cnt; i++ ) if( !p_rcvd_test( set->parity_shred_rcvd, i ) )
     726        4254 :     fd_bmtree_get_proof( tree, set->parity_shreds[i] + fd_shred_merkle_off( (fd_shred_t *)set->parity_shreds[i] ), set->data_shred_cnt+i );
     727             : 
     728             :   /* Set the retransmitter signature for shreds that need one */
     729         270 :   if( FD_UNLIKELY( fd_shred_is_resigned( shred_type ) ) ) {
     730         747 :     for( ulong i=0UL; i<set->data_shred_cnt; i++ ) if( !d_rcvd_test( set->data_shred_rcvd, i ) )
     731         324 :       memcpy( set->data_shreds[i]   + fd_shred_retransmitter_sig_off( (fd_shred_t *)set->data_shreds[i]   ), retran_sig.u, 64UL );
     732             : 
     733         747 :     for( ulong i=0UL; i<set->parity_shred_cnt; i++ ) if( !p_rcvd_test( set->parity_shred_rcvd, i ) )
     734         399 :       memcpy( set->parity_shreds[i] + fd_shred_retransmitter_sig_off( (fd_shred_t *)set->parity_shreds[i] ), retran_sig.u, 64UL );
     735          21 :   }
     736             : 
     737             :   /* Finally... A valid FEC set.  Forward it along. */
     738         270 :   bmtrlist_push_tail( bmtree_free_list, tree );
     739         270 :   freelist_push_tail( complete_list, set );
     740         270 :   freelist_push_tail( free_list, freelist_pop_head( complete_list ) );
     741             : 
     742         270 :   *out_fec_set = set;
     743             : 
     744         270 :   return FD_FEC_RESOLVER_SHRED_COMPLETES;
     745         270 : }
     746             : 
     747             : int
     748             : fd_fec_resolver_done_contains( fd_fec_resolver_t      * resolver,
     749           0 :                                fd_ed25519_sig_t const * signature ) {
     750           0 :   wrapped_sig_t * w_sig = (wrapped_sig_t *)signature;
     751           0 :   if( FD_UNLIKELY( ctx_map_key_inval( *w_sig ) ) ) return 0;
     752           0 :   return !!ctx_map_query( resolver->done_map, *w_sig, NULL );
     753           0 : }
     754             : 
     755             : int
     756             : fd_fec_resolver_shred_query( fd_fec_resolver_t      * resolver,
     757             :                              fd_ed25519_sig_t const * signature,
     758             :                              uint                     shred_idx,
     759           0 :                              uchar                  * out_shred ) {
     760           0 :   wrapped_sig_t * w_sig = (wrapped_sig_t *)signature;
     761           0 :   if( FD_UNLIKELY( ctx_map_key_inval( *w_sig ) ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     762             : 
     763           0 :   set_ctx_t * ctx = ctx_map_query( resolver->curr_map, *w_sig, NULL );
     764           0 :   if( FD_UNLIKELY( !ctx ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     765             : 
     766           0 :   fd_fec_set_t     * set        = ctx->set;
     767           0 :   fd_shred_t const * data_shred = (fd_shred_t const *)fd_type_pun_const( set->data_shreds[ shred_idx ] );
     768             : 
     769           0 :   ulong sz = fd_ulong_min( fd_shred_sz( data_shred ), FD_SHRED_MIN_SZ );
     770           0 :   fd_memcpy( out_shred, data_shred, sz );
     771           0 :   return FD_FEC_RESOLVER_SHRED_OKAY;
     772           0 : }
     773             : 
     774             : /* TODO code is copy-pasted because this function is intended to be
     775             :    removed as soon as an upgrade to the repair protocol to support
     776             :    requesting coding shreds is made available. */
     777             : 
     778             : int
     779             : fd_fec_resolver_force_complete( fd_fec_resolver_t  *  resolver,
     780             :                                 fd_shred_t const   *  last_shred,
     781             :                                 fd_fec_set_t const ** out_fec_set,
     782          12 :                                 fd_bmtree_node_t   *  out_merkle_root ) {
     783             : 
     784             :   /* Error if last_shred is obviously invalid... don't even
     785             :      try to process the associated FEC set. */
     786             : 
     787          12 :   ulong idx_in_set = last_shred->idx - last_shred->fec_set_idx;
     788             : 
     789          12 :   if( FD_UNLIKELY( idx_in_set >= FD_REEDSOL_DATA_SHREDS_MAX ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     790             : 
     791             :   /* Error if can't find the last_shred's FEC set. */
     792             : 
     793          12 :   wrapped_sig_t * w_sig = (wrapped_sig_t *)last_shred->signature;
     794          12 :   if( FD_UNLIKELY( ctx_map_key_inval( *w_sig ) ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     795             : 
     796             :   /* Error if already done. */
     797             : 
     798          12 :   int found = !!ctx_map_query( resolver->done_map, *w_sig, NULL );
     799          12 :   if( found )  return FD_FEC_RESOLVER_SHRED_IGNORED;
     800             : 
     801             :   /* Error if FEC associated with last_shred not found. */
     802             : 
     803          12 :   set_ctx_t * ctx = ctx_map_query( resolver->curr_map, *w_sig, NULL );
     804          12 :   if( FD_UNLIKELY( !ctx ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     805             : 
     806             :   /* Error if already received parity shred (cnts are only knowable from
     807             :      receiving a coding shred). */
     808             : 
     809          12 :   if( FD_UNLIKELY( ctx->set->data_shred_cnt   != SHRED_CNT_NOT_SET ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     810          12 :   if( FD_UNLIKELY( ctx->set->parity_shred_cnt != SHRED_CNT_NOT_SET ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     811             : 
     812             :   /* Error if gaps in receives to last data shred. Implies that the FEC
     813             :      set is still incomplete. */
     814             : 
     815         126 :   for( ulong i=0UL; i<=idx_in_set; i++ ) if( !d_rcvd_test( ctx->set->data_shred_rcvd, i ) ) {
     816           3 :     return FD_FEC_RESOLVER_SHRED_REJECTED;
     817           3 :   }
     818             : 
     819             :   /* Error if last shred is not in fact last shred and FEC resolver has
     820             :      seen a shred with a higher idx. */
     821             : 
     822         117 :   for( ulong i=idx_in_set + 1; i<FD_REEDSOL_DATA_SHREDS_MAX; i++ ) if( d_rcvd_test( ctx->set->data_shred_rcvd, i ) ) {
     823           6 :     return FD_FEC_RESOLVER_SHRED_REJECTED;
     824           6 :   }
     825             : 
     826             :   /* Now we know the caller has provided a FEC set that is not obviously
     827             :      incomplete or invalid, we validate the FEC set itself. */
     828             : 
     829           3 :   fd_shred_t const * base_data_shred = fd_shred_parse( ctx->set->data_shreds[0], FD_SHRED_MIN_SZ );
     830           3 :   int reject = (!base_data_shred);
     831             : 
     832          96 :   for( ulong i=1UL; (!reject) & (i<=idx_in_set); i++ ) {
     833             : 
     834             :     /* casting is safe because these data shreds must have been all rcvd
     835             :        from the network and not recovered. */
     836             : 
     837          93 :     fd_shred_t const * parsed = (fd_shred_t const *)fd_type_pun_const( ctx->set->data_shreds[ i ] );
     838          93 :     if( FD_UNLIKELY( !parsed ) ) { reject = 1; break; }
     839          93 :     reject |= parsed->variant         != base_data_shred->variant;
     840          93 :     reject |= parsed->slot            != base_data_shred->slot;
     841          93 :     reject |= parsed->version         != base_data_shred->version;
     842          93 :     reject |= parsed->fec_set_idx     != base_data_shred->fec_set_idx;
     843          93 :     reject |= parsed->data.parent_off != base_data_shred->data.parent_off;
     844             : 
     845          93 :     reject |= fd_shred_is_chained( fd_shred_type( parsed->variant ) ) &&
     846          93 :                 !fd_memeq( (uchar *)parsed         +fd_shred_chain_off( parsed->variant          ),
     847           0 :                            (uchar *)base_data_shred+fd_shred_chain_off( base_data_shred->variant ), FD_SHRED_MERKLE_ROOT_SZ );
     848          93 :   }
     849             : 
     850             :   /* Populate correct shred cnts for post-completion processing. These
     851             :      are normally populated by the parity shred header, but in the case
     852             :      of force_complete, a parity shred was never received. */
     853             : 
     854           3 :   ctx->set->data_shred_cnt   = idx_in_set + 1UL;
     855           3 :   ctx->set->parity_shred_cnt = 0UL;
     856             : 
     857             :   /* Reject FEC set if it didn't validate and return mem to the pools */
     858           3 :   set_ctx_t * done_ll_sentinel = resolver->done_ll_sentinel;
     859           3 :   set_ctx_t * curr_map         = resolver->curr_map;
     860           3 :   set_ctx_t * done_map         = resolver->done_map;
     861           3 :   ulong       done_depth       = resolver->done_depth;
     862             : 
     863           3 :   if( FD_UNLIKELY( reject ) ) {
     864           0 :     freelist_push_tail( resolver->free_list,        ctx->set  );
     865           0 :     bmtrlist_push_tail( resolver->bmtree_free_list, ctx->tree );
     866           0 :     ctx_map_remove( curr_map, ctx_ll_remove( ctx ));
     867           0 :     FD_MCNT_INC( SHRED, FEC_REJECTED_FATAL, 1UL );
     868           0 :     return FD_FEC_RESOLVER_SHRED_REJECTED;
     869           0 :   }
     870             : 
     871             :   /* Copy out the merkle root. */
     872             : 
     873           3 :   if( FD_LIKELY( out_merkle_root) ) memcpy( out_merkle_root, ctx->root.hash, sizeof(fd_bmtree_node_t) );
     874             : 
     875             :   /* Don't need to populate merkle proofs or retransmitter signatures
     876             :      because it is by definition the full set of rcvd data shreds. */
     877             : 
     878           3 :   fd_fec_set_t        * set  = ctx->set;
     879           3 :   fd_bmtree_commit_t  * tree = ctx->tree;
     880             : 
     881           3 :   ctx_ll_insert( done_ll_sentinel, ctx_map_insert( done_map, ctx->sig ) );
     882           3 :   if( FD_UNLIKELY( ctx_map_key_cnt( done_map ) > done_depth ) ) ctx_map_remove( done_map, ctx_ll_remove( done_ll_sentinel->prev ) );
     883           3 :   ctx_map_remove( curr_map, ctx_ll_remove( ctx ) );
     884             : 
     885           3 :   bmtrlist_push_tail( resolver->bmtree_free_list, tree );
     886           3 :   freelist_push_tail( resolver->complete_list, set );
     887           3 :   freelist_push_tail( resolver->free_list, freelist_pop_head( resolver->complete_list ) );
     888             : 
     889           3 :   *out_fec_set = set;
     890             : 
     891           3 :   return FD_FEC_RESOLVER_SHRED_COMPLETES;
     892           3 : }
     893             : 
     894         114 : void * fd_fec_resolver_leave( fd_fec_resolver_t * resolver ) {
     895         114 :   fd_sha512_leave( resolver->sha512           );
     896         114 :   bmtrlist_leave ( resolver->bmtree_free_list );
     897         114 :   freelist_leave ( resolver->complete_list    );
     898         114 :   freelist_leave ( resolver->free_list        );
     899         114 :   ctx_map_leave  ( resolver->done_map         );
     900         114 :   ctx_map_leave  ( resolver->curr_map         );
     901             : 
     902         114 :   return (void *)resolver;
     903         114 : }
     904             : 
     905         114 : void * fd_fec_resolver_delete( void * shmem ) {
     906         114 :   fd_fec_resolver_t * resolver = (fd_fec_resolver_t *)shmem;
     907         114 :   ulong depth          = resolver->depth;
     908         114 :   ulong partial_depth  = resolver->partial_depth;
     909         114 :   ulong complete_depth = resolver->complete_depth;
     910         114 :   ulong done_depth     = resolver->done_depth;
     911             : 
     912         114 :   int lg_curr_map_cnt = fd_ulong_find_msb( depth      + 1UL ) + 2;
     913         114 :   int lg_done_map_cnt = fd_ulong_find_msb( done_depth + 1UL ) + 2;
     914             : 
     915         114 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     916         114 :   /*     self       */ FD_SCRATCH_ALLOC_APPEND( l, FD_FEC_RESOLVER_ALIGN,  sizeof(fd_fec_resolver_t)                     );
     917         114 :   void * curr        = FD_SCRATCH_ALLOC_APPEND( l, ctx_map_align(),        ctx_map_footprint( lg_curr_map_cnt )          );
     918         114 :   void * done        = FD_SCRATCH_ALLOC_APPEND( l, ctx_map_align(),        ctx_map_footprint( lg_done_map_cnt )          );
     919         114 :   void * free        = FD_SCRATCH_ALLOC_APPEND( l, freelist_align(),       freelist_footprint( depth+partial_depth+1UL ) );
     920         114 :   void * cmplst      = FD_SCRATCH_ALLOC_APPEND( l, freelist_align(),       freelist_footprint( complete_depth+1UL  )     );
     921         114 :   void * bmfree      = FD_SCRATCH_ALLOC_APPEND( l, bmtrlist_align(),       bmtrlist_footprint( depth+1UL )               );
     922         114 :   FD_SCRATCH_ALLOC_FINI( l, FD_FEC_RESOLVER_ALIGN );
     923             : 
     924         114 :   fd_sha512_delete( resolver->sha512 );
     925         114 :   bmtrlist_delete ( bmfree           );
     926         114 :   freelist_delete ( cmplst           );
     927         114 :   freelist_delete ( free             );
     928         114 :   ctx_map_delete  ( done             );
     929         114 :   ctx_map_delete  ( curr             );
     930             : 
     931         114 :   return shmem;
     932         114 : }

Generated by: LCOV version 1.14