LCOV - code coverage report
Current view: top level - disco/shred - fd_fec_resolver.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 441 491 89.8 %
Date: 2026-05-16 06:43:53 Functions: 9 11 81.8 %

          Line data    Source code
       1             : #include "../../ballet/shred/fd_shred.h"
       2             : #include "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             : typedef union {
       9             :   fd_ed25519_sig_t u;
      10             :   ulong            l;
      11             : } wrapped_sig_t;
      12             : 
      13             : typedef struct __attribute__((packed)) {
      14             :   ulong slot;
      15             :   uint fec_idx;
      16             : } slot_fec_pair_t;
      17             : 
      18             : struct __attribute__((aligned(32UL))) set_ctx {
      19             :   /* The leader's signature of the root of the Merkle tree of the shreds
      20             :      in this FEC set. */
      21             :   wrapped_sig_t         sig;
      22             : 
      23             :   union {
      24             :     /* When allocated, it's in a map_chain by signature and a treap
      25             :        by (shred, FEC set idx).  When it's not allocated, it is either
      26             :        in the free list or the completed list.  Both of those slists use
      27             :        free_next. */
      28             :     struct {
      29             :       uint              map_next;
      30             :       uint              map_prev;
      31             :       uint              treap_parent;
      32             :       uint              treap_left;
      33             :       uint              treap_right;
      34             :       uint              treap_prio;
      35             :     };
      36             :     struct {
      37             :       uint              free_next;
      38             :     };
      39             :   };
      40             : 
      41             :   ulong                 slot;
      42             :   uint                  fec_set_idx;
      43             : 
      44             :   uchar                 data_variant;
      45             :   uchar                 parity_variant;
      46             : 
      47             :   ulong                 total_rx_shred_cnt;
      48             : 
      49             :   fd_fec_set_t *        set;
      50             : 
      51             :   fd_bmtree_node_t      root;
      52             :   /* If this FEC set has resigned shreds, this is our signature of the
      53             :      root of the Merkle tree */
      54             :   wrapped_sig_t         retransmitter_sig;
      55             : 
      56             :   union {
      57             :     fd_bmtree_commit_t  tree[1];
      58             :     uchar               _footprint[ FD_BMTREE_COMMIT_FOOTPRINT( FD_SHRED_MERKLE_LAYER_CNT ) ] __attribute__((aligned(FD_BMTREE_COMMIT_ALIGN)));
      59             :   };
      60             : };
      61             : typedef struct set_ctx set_ctx_t;
      62             : 
      63             : #define MAP_NAME              ctx_map
      64         513 : #define MAP_KEY               sig
      65             : #define MAP_KEY_T             wrapped_sig_t
      66       16743 : #define MAP_IDX_T             uint
      67        3801 : #define MAP_NEXT              map_next
      68        2436 : #define MAP_PREV              map_prev
      69         252 : #define MAP_ELE_T             set_ctx_t
      70        8628 : #define MAP_KEY_EQ(k0,k1)    (!memcmp( (k0)->u, (k1)->u, FD_ED25519_SIG_SZ ))
      71        8736 : #define MAP_KEY_HASH(key,s)  (fd_ulong_hash( (key)->l ^ (s) ))
      72             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
      73             : #include "../../util/tmpl/fd_map_chain.c"
      74             : 
      75             : 
      76             : #define SLIST_NAME  ctx_list
      77             : #define SLIST_ELE_T set_ctx_t
      78         486 : #define SLIST_IDX_T uint
      79        1107 : #define SLIST_NEXT  free_next
      80             : #include "../../util/tmpl/fd_slist.c"
      81             : 
      82             : 
      83             : static inline int
      84             : slot_fec_pair_compare( slot_fec_pair_t const * q,
      85          48 :                        set_ctx_t       const * e ) {
      86             :   /* It seems like
      87             :      return (int)( q->slot!=e->slot ?
      88             :                    q->slot    - e->slot :
      89             :                    q->fec_idx - e->fec_set_idx );
      90             :      should work, but I am concerned about overflow since this is all
      91             :      attacker controlled input. */
      92          48 :   if( FD_LIKELY( q->slot   !=e->slot        ) ) return fd_int_if( q->slot   <e->slot,        -1, 1 );
      93          48 :   if( FD_LIKELY( q->fec_idx!=e->fec_set_idx ) ) return fd_int_if( q->fec_idx<e->fec_set_idx, -1, 1 );
      94           0 :   return 0;
      95          48 : }
      96             : 
      97             : #define TREAP_NAME       ctx_treap
      98             : #define TREAP_T          set_ctx_t
      99        1083 : #define TREAP_IDX_T      uint
     100         582 : #define TREAP_PARENT     treap_parent
     101         588 : #define TREAP_LEFT       treap_left
     102         618 : #define TREAP_RIGHT      treap_right
     103         483 : #define TREAP_PRIO       treap_prio
     104          69 : #define TREAP_LT(e0,e1)  (((e0)->slot < (e1)->slot) | ( ((e0)->slot==(e1)->slot) & ((e0)->fec_set_idx < (e1)->fec_set_idx)))
     105             : #define TREAP_QUERY_T    slot_fec_pair_t const *
     106          48 : #define TREAP_CMP(q,e)   slot_fec_pair_compare( (q), (e) )
     107             : #include "../../util/tmpl/fd_treap.c"
     108             : 
     109             : 
     110             : 
     111             : /* Once we're done with a FEC set, it goes into a map_chain and heap,
     112             :    both keyed by (slot, FEC set idx). */
     113             : 
     114             : struct done_ele {
     115             :   slot_fec_pair_t key;
     116             :   uint            heap_left; /* also used by pool when not allocated */
     117             :   uint            heap_right;
     118             :   uint            map_next;
     119             :   uint            map_prev;
     120             :   /* In order to save space in the done_map and make this struct 32
     121             :      bytes, we store a 32 bit validator-specific hash of the shred
     122             :      signature.  If a malicious leader equivocates and produces two FEC
     123             :      sets which have the same hash for us, a task which takes a decent
     124             :      but doable amount of effort, the only impact is that we would
     125             :      reject the shreds with SHRED_IGNORED instead of SHRED_EQUIOC, which
     126             :      is not a big deal.  It's documented that SHRED_EQUIVOC detection is
     127             :      on a best-effort basis. */
     128             :   uint           sig_hash;
     129             : };
     130             : typedef struct done_ele done_ele_t;
     131             : 
     132             : #define MAP_NAME              done_map
     133         300 : #define MAP_KEY               key
     134             : #define MAP_KEY_T             slot_fec_pair_t
     135        2268 : #define MAP_IDX_T             uint
     136        1161 : #define MAP_NEXT              map_next
     137         642 : #define MAP_PREV              map_prev
     138         141 : #define MAP_ELE_T             done_ele_t
     139        1080 : #define MAP_KEY_EQ(k0,k1)     ( ((k0)->slot==(k1)->slot) & ((k0)->fec_idx==(k1)->fec_idx) )
     140        1308 : #define MAP_KEY_HASH(key,s)  ((fd_ulong_hash( (key)->slot ^ (s) ) ^ fd_uint_hash( (key)->fec_idx ^ (uint)(s>>19) )))
     141             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
     142             : #include "../../util/tmpl/fd_map_chain.c"
     143             : 
     144             : #define HEAP_NAME             done_heap
     145         360 : #define HEAP_IDX_T            uint
     146         774 : #define HEAP_LEFT             heap_left
     147         774 : #define HEAP_RIGHT            heap_right
     148             : #define HEAP_T                done_ele_t
     149         414 : #define HEAP_LT(e0,e1)       (((e0)->key.slot < (e1)->key.slot) | \
     150         414 :                             ( ((e0)->key.slot==(e1)->key.slot) & ((e0)->key.fec_idx < (e1)->key.fec_idx)))
     151             : #include "../../util/tmpl/fd_heap.c"
     152             : 
     153             : #define POOL_NAME             done_pool
     154          87 : #define POOL_T                done_ele_t
     155             : #define POOL_IDX_T            uint
     156         465 : #define POOL_NEXT             heap_left
     157             : #include "../../util/tmpl/fd_pool.c"
     158             : 
     159             : struct __attribute__((aligned(FD_FEC_RESOLVER_ALIGN))) fd_fec_resolver {
     160             :   /* depth stores the number of FEC sets this resolver can track
     161             :      simultaneously.  done_depth stores the depth of the done tcache,
     162             :      i.e. the number of done FEC set keys that this resolver remembers.
     163             :      partial_depth stores the minimum size of the free FEC set list.
     164             :      completed_depth stores the size of the completed FEC set list. */
     165             :   ulong depth;
     166             :   ulong partial_depth;
     167             :   ulong complete_depth;
     168             :   ulong done_depth;
     169             : 
     170             :   /* expected_shred_version: discard all shreds with a shred version
     171             :      other than the specified value */
     172             :   ushort expected_shred_version;
     173             : 
     174             :   /* ctx_pool: A flat array (not an fd_pool) of the set_ctx_t
     175             :      structures used to back ctx_map, ctx_treap, and the ctx
     176             :      freelists. */
     177             :   set_ctx_t * ctx_pool;
     178             : 
     179             :   /* ctx_map: A map (using fd_map_chain) from signatures to
     180             :      the context object with its relevant data for in progress FEC sets.
     181             :      This map contains at most `depth` elements at any time. */
     182             :   ctx_map_t * ctx_map;
     183             : 
     184             :   /* ctx_treap: A treap (using fd_treap) of the context objects for in
     185             :      progress FEC sets.  They are sorted by (slot, FEC index) from
     186             :      smallest to largest.  In the case of equivocation, multiple
     187             :      elements with the same key may be present, with no particular
     188             :      ordering between them. */
     189             :   ctx_treap_t ctx_treap[1];
     190             : 
     191             :   /* free_list and complete_list are slists (using fd_slist)
     192             :      of FEC set contexts that are not in ctx_map.  See the long comment
     193             :      in the header for why there are two.  In order to satisfy the
     194             :      invariants, technically we only need to store the FEC set memory,
     195             :      not the full context, but it's not that big of a difference
     196             :      (especially if partial_depth and complete_depth are small), and it
     197             :      simplifies memory management.
     198             : 
     199             :      Invariant: at every entry and exit to fd_fec_resolver_add_shred:
     200             :      - free_list has between partial_depth and partial_depth+depth
     201             :        elements.
     202             :      - complete_list has complete_depth elements
     203             :        (all these counts are inclusive). */
     204             :   ctx_list_t  free_list[1];
     205             :   ctx_list_t  complete_list[1];
     206             : 
     207             :   /* free_list_cnt: The number of items in free_list. */
     208             :   ulong free_list_cnt;
     209             : 
     210             :   /* done_pool: A pool (this time using fd_pool) of the done_ele_t
     211             :      elements that back done_map and done_heap.  Invariant: each element
     212             :      is either (i) released and in the pool, or (ii) in both the
     213             :      done_map and done_heap. */
     214             :   done_ele_t * done_pool;
     215             : 
     216             :   /* done_map: A map (using fd_map_chain) mapping (slot, fec_idx) to an
     217             :      element of done_pool.  Even in the presence of equivocation, a
     218             :      specific (slot, fec_idx) tuple occurs at most once in the map,
     219             :      and it's arbitrary which version is represented by sig_hash.  In
     220             :      the presence of equivocation, the right shreds are probably being
     221             :      delivered using repair, which will bypass reading the sig_hash
     222             :      field, so it doesn't really matter. */
     223             :   done_map_t * done_map;
     224             : 
     225             :   /* done_heap: A min heap (using fd_heap) based on (slot, fec_idx) used
     226             :      to stop tracking done elements older than slot_old, and for
     227             :      eviction in the unlikely case that we run out of elements in the
     228             :      done_map. */
     229             :   done_heap_t done_heap[1];
     230             : 
     231             :   /* signer is used to sign shreds that require a retransmitter
     232             :      signature.  sign_ctx is provided as the first argument to the
     233             :      function. */
     234             :   fd_fec_resolver_sign_fn * signer;
     235             :   void                    * sign_ctx;
     236             : 
     237             :   /* max_shred_idx is the exclusive upper bound for shred indices.  We
     238             :      need to reject any shred with an index >= max_shred_idx, but we
     239             :      also want to reject anything that is part of an FEC set where the
     240             :      highest index of a shred in the FEC set will be >= max_shred_idx.
     241             :      */
     242             :   ulong max_shred_idx;
     243             : 
     244             :   /* slot_old: slot_old is the lowest slot for which shreds will be
     245             :      accepted.  That is any shred with slot<slot_old is rejected by
     246             :      add_shred with INGORED.  slot_old can only increase. */
     247             :   ulong slot_old;
     248             : 
     249             :   /* seed: done_map uses seed to compute a 32-bute hash of the FEC set's
     250             :      signature. */
     251             :   ulong seed;
     252             : 
     253             :   /* discard_unexpected_data_complete_shreds: activation slot for
     254             :      discard_unexpected_data_complete_shreds.
     255             : 
     256             :      This feature rejects data shreds with the DATA_COMPLETE flag set
     257             :      at the wrong position within an FEC set (i.e. not the last data
     258             :      shred). */
     259             :   ulong discard_unexpected_data_complete_shreds;
     260             : 
     261             :   /* sha512 and reedsol are used for calculations while adding a shred.
     262             :      Their state outside a call to add_shred is indeterminate. */
     263             :   fd_sha512_t   sha512[1];
     264             :   fd_reedsol_t  reedsol[1];
     265             : 
     266             :   /* The footprint for the objects follows the struct and is in the same
     267             :      order as the pointers, namely:
     268             :        ctx_pool
     269             :        ctx_map
     270             :        done_pool
     271             :        done_map */
     272             : };
     273             : 
     274             : typedef struct fd_fec_resolver fd_fec_resolver_t;
     275             : 
     276             : FD_FN_PURE ulong
     277             : fd_fec_resolver_footprint( ulong depth,
     278             :                            ulong partial_depth,
     279             :                            ulong complete_depth,
     280           3 :                            ulong done_depth ) {
     281           3 :   if( FD_UNLIKELY( (depth==0UL) | (partial_depth==0UL) | (complete_depth==0UL) | (done_depth==0UL) ) ) return 0UL;
     282           3 :   if( FD_UNLIKELY( (depth>UINT_MAX) | (partial_depth>UINT_MAX) | (complete_depth>UINT_MAX)         ) ) return 0UL;
     283             : 
     284           3 :   ulong depth_sum = depth + partial_depth + complete_depth;
     285           3 :   if( FD_UNLIKELY( depth_sum>=UINT_MAX ) ) return 0UL;
     286             : 
     287           3 :   ulong ctx_chain_cnt  = ctx_map_chain_cnt_est ( depth      );
     288           3 :   ulong done_chain_cnt = done_map_chain_cnt_est( done_depth );
     289             : 
     290           3 :   ulong layout = FD_LAYOUT_INIT;
     291           3 :   layout = FD_LAYOUT_APPEND( layout, FD_FEC_RESOLVER_ALIGN,  sizeof(fd_fec_resolver_t)             );
     292           3 :   layout = FD_LAYOUT_APPEND( layout, alignof(set_ctx_t),     sizeof(set_ctx_t)*depth_sum           );
     293           3 :   layout = FD_LAYOUT_APPEND( layout, ctx_map_align(),        ctx_map_footprint  ( ctx_chain_cnt  ) );
     294           3 :   layout = FD_LAYOUT_APPEND( layout, done_pool_align(),      done_pool_footprint( done_depth     ) );
     295           3 :   layout = FD_LAYOUT_APPEND( layout, done_map_align(),       done_map_footprint ( done_chain_cnt ) );
     296             : 
     297           3 :   return FD_LAYOUT_FINI( layout, FD_FEC_RESOLVER_ALIGN );
     298           3 : }
     299             : 
     300           0 : FD_FN_CONST ulong fd_fec_resolver_align( void ) { return FD_FEC_RESOLVER_ALIGN; }
     301             : 
     302             : 
     303             : void *
     304             : fd_fec_resolver_new( void                    * shmem,
     305             :                      fd_fec_resolver_sign_fn * signer,
     306             :                      void                    * sign_ctx,
     307             :                      ulong                     depth,
     308             :                      ulong                     partial_depth,
     309             :                      ulong                     complete_depth,
     310             :                      ulong                     done_depth,
     311             :                      fd_fec_set_t            * sets,
     312             :                      ulong                     max_shred_idx,
     313          33 :                      ulong                     seed ) {
     314          33 :   if( FD_UNLIKELY( (depth==0UL) | (partial_depth==0UL) | (complete_depth==0UL) | (done_depth==0UL) ) ) return NULL;
     315          33 :   if( FD_UNLIKELY( (depth>UINT_MAX) | (partial_depth>UINT_MAX) | (complete_depth>UINT_MAX)         ) ) return NULL;
     316             : 
     317          33 :   ulong depth_sum = depth + partial_depth + complete_depth;
     318          33 :   if( FD_UNLIKELY( depth_sum>=UINT_MAX ) ) return NULL;
     319             : 
     320          33 :   ulong ctx_chain_cnt  = ctx_map_chain_cnt_est ( depth      );
     321          33 :   ulong done_chain_cnt = done_map_chain_cnt_est( done_depth );
     322             : 
     323             :                                                               /* round( 2^64 * ... */
     324          33 :   ulong seed0 = fd_ulong_hash( seed +  7640891576956012809UL );  /* sqrt(2)-1 */
     325          33 :   ulong seed1 = fd_ulong_hash( seed + 13503953896175478587UL );  /* sqrt(3)-1 */
     326          33 :   ulong seed2 = fd_ulong_hash( seed +  4354685564936845356UL );  /* sqrt(5)-2 */
     327          33 :   ulong seed3 = fd_ulong_hash( seed + 11912009170470909682UL );  /* sqrt(7)-2 */
     328             : 
     329          33 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     330          33 :   void * self        = FD_SCRATCH_ALLOC_APPEND( l, FD_FEC_RESOLVER_ALIGN,  sizeof(fd_fec_resolver_t)                 );
     331          33 :   void * _ctx_pool   = FD_SCRATCH_ALLOC_APPEND( l, alignof(set_ctx_t),     sizeof(set_ctx_t)*depth_sum               );
     332          33 :   void * _ctx_map    = FD_SCRATCH_ALLOC_APPEND( l, ctx_map_align(),        ctx_map_footprint  ( ctx_chain_cnt  ) );
     333          33 :   void * _done_pool  = FD_SCRATCH_ALLOC_APPEND( l, done_pool_align(),      done_pool_footprint( done_depth         ) );
     334          33 :   void * _done_map   = FD_SCRATCH_ALLOC_APPEND( l, done_map_align(),       done_map_footprint ( done_chain_cnt ) );
     335          33 :   FD_SCRATCH_ALLOC_FINI( l, FD_FEC_RESOLVER_ALIGN );
     336             : 
     337          33 :   fd_fec_resolver_t * resolver = (fd_fec_resolver_t *)self;
     338          33 :   void * _ctx_treap     = resolver->ctx_treap;
     339          33 :   void * _free_list     = resolver->free_list;
     340          33 :   void * _complete_list = resolver->complete_list;
     341          33 :   void * _done_heap     = resolver->done_heap;
     342             : 
     343          33 :   if( FD_UNLIKELY( !ctx_map_new  ( _ctx_map, ctx_chain_cnt, seed0   ) ) ) { FD_LOG_WARNING(( "ctx_map_new fail"   )); return NULL; }
     344          33 :   if( FD_UNLIKELY( !ctx_treap_new( _ctx_treap, depth_sum            ) ) ) { FD_LOG_WARNING(( "ctx_treap_new fail" )); return NULL; }
     345          33 :   if( FD_UNLIKELY( !ctx_list_new ( _free_list                       ) ) ) { FD_LOG_WARNING(( "ctx_list_new fail"  )); return NULL; }
     346          33 :   if( FD_UNLIKELY( !ctx_list_new ( _complete_list                   ) ) ) { FD_LOG_WARNING(( "ctx_list_new fail"  )); return NULL; }
     347          33 :   if( FD_UNLIKELY( !done_pool_new( _done_pool, done_depth           ) ) ) { FD_LOG_WARNING(( "done_pool_new fail" )); return NULL; }
     348          33 :   if( FD_UNLIKELY( !done_map_new ( _done_map, done_chain_cnt, seed1 ) ) ) { FD_LOG_WARNING(( "done_map_new fail"  )); return NULL; }
     349          33 :   if( FD_UNLIKELY( !done_heap_new( _done_heap, done_depth           ) ) ) { FD_LOG_WARNING(( "done_heap_new fail" )); return NULL; }
     350             : 
     351          33 :   set_ctx_t * ctx_pool = (set_ctx_t *)_ctx_pool;
     352          33 :   fd_memset( ctx_pool, '\0', sizeof(set_ctx_t)*depth_sum );
     353         186 :   for( ulong i=0UL; i<depth_sum; i++ ) ctx_pool[i].set = sets + i;
     354          33 :   ctx_treap_seed( ctx_pool, depth_sum, seed2 );
     355             : 
     356             :   /* Initialize all the lists */
     357          33 :   ctx_list_t * free_list     = ctx_list_join( _free_list     );    FD_TEST( free_list    ==resolver->free_list     );
     358          33 :   ctx_list_t * complete_list = ctx_list_join( _complete_list );    FD_TEST( complete_list==resolver->complete_list );
     359             : 
     360         141 :   for( ulong i=0UL;                 i<depth+partial_depth; i++ ) { ctx_list_idx_push_tail( free_list,     i, ctx_pool ); }
     361          78 :   for( ulong i=depth+partial_depth; i<depth_sum;           i++ ) { ctx_list_idx_push_tail( complete_list, i, ctx_pool ); }
     362          33 :   ctx_list_leave( complete_list );
     363          33 :   ctx_list_leave( free_list     );
     364             : 
     365          33 :   fd_sha512_new( resolver->sha512 );
     366             : 
     367          33 :   resolver->depth                                   = depth;
     368          33 :   resolver->partial_depth                           = partial_depth;
     369          33 :   resolver->complete_depth                          = complete_depth;
     370          33 :   resolver->done_depth                              = done_depth;
     371          33 :   resolver->expected_shred_version                  = 0;
     372          33 :   resolver->free_list_cnt                           = depth+partial_depth;
     373          33 :   resolver->signer                                  = signer;
     374          33 :   resolver->sign_ctx                                = sign_ctx;
     375          33 :   resolver->max_shred_idx                           = max_shred_idx;
     376          33 :   resolver->slot_old                                = 0UL;
     377          33 :   resolver->seed                                    = seed3;
     378          33 :   resolver->discard_unexpected_data_complete_shreds = ULONG_MAX;
     379          33 :   return shmem;
     380          33 : }
     381             : 
     382             : fd_fec_resolver_t *
     383          33 : fd_fec_resolver_join( void * shmem ) {
     384          33 :   fd_fec_resolver_t * resolver = (fd_fec_resolver_t *)shmem;
     385          33 :   ulong depth          = resolver->depth;
     386          33 :   ulong partial_depth  = resolver->partial_depth;
     387          33 :   ulong complete_depth = resolver->complete_depth;
     388          33 :   ulong done_depth     = resolver->done_depth;
     389             : 
     390          33 :   ulong depth_sum = depth + partial_depth + complete_depth;
     391          33 :   if( FD_UNLIKELY( depth_sum>=UINT_MAX ) ) return NULL;
     392             : 
     393          33 :   ulong ctx_chain_cnt  = ctx_map_chain_cnt_est ( depth      );
     394          33 :   ulong done_chain_cnt = done_map_chain_cnt_est( done_depth );
     395             : 
     396          33 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     397          33 :   /*     self     */   FD_SCRATCH_ALLOC_APPEND( l, FD_FEC_RESOLVER_ALIGN,  sizeof(fd_fec_resolver_t)             );
     398          33 :   void * _ctx_pool   = FD_SCRATCH_ALLOC_APPEND( l, alignof(set_ctx_t),     sizeof(set_ctx_t)*depth_sum           );
     399          33 :   void * _ctx_map    = FD_SCRATCH_ALLOC_APPEND( l, ctx_map_align(),        ctx_map_footprint  ( ctx_chain_cnt  ) );
     400          33 :   void * _done_pool  = FD_SCRATCH_ALLOC_APPEND( l, done_pool_align(),      done_pool_footprint( done_depth     ) );
     401          33 :   void * _done_map   = FD_SCRATCH_ALLOC_APPEND( l, done_map_align(),       done_map_footprint ( done_chain_cnt ) );
     402          33 :   FD_SCRATCH_ALLOC_FINI( l, FD_FEC_RESOLVER_ALIGN );
     403             : 
     404          33 :   resolver->ctx_pool  = (set_ctx_t *)_ctx_pool;
     405          33 :   resolver->ctx_map   = ctx_map_join  ( _ctx_map   );  if( FD_UNLIKELY( !resolver->ctx_map       ) ) return NULL;
     406          33 :   resolver->done_pool = done_pool_join( _done_pool );  if( FD_UNLIKELY( !resolver->done_pool     ) ) return NULL;
     407          33 :   resolver->done_map  = done_map_join ( _done_map  );  if( FD_UNLIKELY( !resolver->done_map      ) ) return NULL;
     408          33 :   if( FD_UNLIKELY(      ctx_treap_join( resolver->ctx_treap     )!=      resolver->ctx_treap     ) ) return NULL;
     409          33 :   if( FD_UNLIKELY(      ctx_list_join ( resolver->free_list     )!=      resolver->free_list     ) ) return NULL;
     410          33 :   if( FD_UNLIKELY(      ctx_list_join ( resolver->complete_list )!=      resolver->complete_list ) ) return NULL;
     411          33 :   if( FD_UNLIKELY(      done_heap_join( resolver->done_heap     )!=      resolver->done_heap     ) ) return NULL;
     412          33 :   if( FD_UNLIKELY(      fd_sha512_join( resolver->sha512        )!=      resolver->sha512        ) ) return NULL;
     413             : 
     414          33 :   return resolver;
     415          33 : }
     416             : 
     417             : void
     418             : fd_fec_resolver_set_shred_version( fd_fec_resolver_t * resolver,
     419          33 :                                    ushort              expected_shred_version ) {
     420          33 :   resolver->expected_shred_version = expected_shred_version;
     421          33 : }
     422             : 
     423             : void
     424             : fd_fec_resolver_set_discard_unexpected_data_complete_shreds( fd_fec_resolver_t * resolver,
     425           0 :                                                              ulong               activation_slot ) {
     426           0 :   resolver->discard_unexpected_data_complete_shreds = activation_slot;
     427           0 : }
     428             : 
     429             : void
     430             : fd_fec_resolver_advance_slot_old( fd_fec_resolver_t * resolver,
     431           3 :                                   ulong               slot_old ) {
     432           3 :   if( FD_UNLIKELY( slot_old <= resolver->slot_old ) ) return;
     433           3 :   resolver->slot_old = slot_old;
     434             : 
     435             :   /* Remove from done map */
     436           3 :   done_heap_t * done_heap = resolver->done_heap;
     437           3 :   done_map_t  * done_map  = resolver->done_map;
     438           3 :   done_ele_t  * done_pool = resolver->done_pool;
     439             : 
     440           6 :   while( done_heap_ele_cnt( done_heap ) ) {
     441           3 :     done_ele_t * min_ele = done_heap_ele_peek_min( done_heap, done_pool );
     442           3 :     if( FD_UNLIKELY( min_ele->key.slot>=slot_old ) ) break;
     443           3 :     done_map_ele_remove_fast( done_map,  min_ele, done_pool );
     444           3 :     done_heap_idx_remove_min( done_heap,          done_pool );
     445           3 :     done_pool_ele_release   ( done_pool, min_ele            );
     446           3 :   }
     447             : 
     448             :   /* Remove from in progress map */
     449           3 :   ctx_map_t   * ctx_map       = resolver->ctx_map;
     450           3 :   ctx_treap_t * ctx_treap     = resolver->ctx_treap;
     451           3 :   set_ctx_t   * ctx_pool      = resolver->ctx_pool;
     452           3 :   ctx_list_t  * free_list     = resolver->free_list;
     453             : 
     454           3 :   ctx_treap_fwd_iter_t next;
     455           6 :   for( ctx_treap_fwd_iter_t iter=ctx_treap_fwd_iter_init( ctx_treap, ctx_pool ); !ctx_treap_fwd_iter_done( iter ); iter=next ) {
     456           3 :     next = ctx_treap_fwd_iter_next( iter, ctx_pool );
     457           3 :     set_ctx_t * min_ele = ctx_treap_fwd_iter_ele( iter, ctx_pool );
     458           3 :     if( FD_UNLIKELY( min_ele->slot>=slot_old ) ) break;
     459             : 
     460           3 :     ctx_treap_ele_remove   ( ctx_treap, min_ele, ctx_pool );
     461           3 :     ctx_map_ele_remove_fast( ctx_map,   min_ele, ctx_pool );
     462           3 :     ctx_list_ele_push_head ( free_list, min_ele, ctx_pool );
     463           3 :     resolver->free_list_cnt++;
     464           3 :   }
     465             : 
     466           3 : }
     467             : 
     468             : 
     469             : int
     470             : fd_fec_resolver_add_shred( fd_fec_resolver_t         * resolver,
     471             :                            fd_shred_t const          * shred,
     472             :                            ulong                       shred_sz,
     473             :                            int                         is_repair,
     474             :                            uchar const               * leader_pubkey,
     475             :                            fd_fec_set_t const      * * out_fec_set,
     476             :                            fd_shred_t const        * * out_shred,
     477             :                            fd_bmtree_node_t          * out_merkle_root,
     478        8439 :                            fd_fec_resolver_spilled_t * out_spilled      ) {
     479             :   /* Unpack variables */
     480        8439 :   ulong partial_depth = resolver->partial_depth;
     481             : 
     482        8439 :   ctx_list_t  * free_list     = resolver->free_list;
     483        8439 :   ctx_list_t  * complete_list = resolver->complete_list;
     484        8439 :   ctx_map_t   * ctx_map       = resolver->ctx_map;
     485        8439 :   ctx_treap_t * ctx_treap     = resolver->ctx_treap;
     486        8439 :   set_ctx_t   * ctx_pool      = resolver->ctx_pool;
     487        8439 :   done_map_t  * done_map      = resolver->done_map;
     488        8439 :   done_ele_t  * done_pool     = resolver->done_pool;
     489        8439 :   done_heap_t * done_heap     = resolver->done_heap;
     490             : 
     491        8439 :   fd_reedsol_t * reedsol       = resolver->reedsol;
     492        8439 :   fd_sha512_t  * sha512        = resolver->sha512;
     493             : 
     494             :   /* Invariants:
     495             :       * each set_ctx_t is in exactly one of ctx_map, freelist, or
     496             :         complete_list */
     497             : 
     498             :   /* Is this shred for a slot we've already rooted or otherwise don't
     499             :      care about? */
     500        8439 :   if( FD_UNLIKELY( shred->slot<resolver->slot_old ) ) return FD_FEC_RESOLVER_SHRED_IGNORED;
     501             : 
     502             :   /* Do a bunch of quick validity checks */
     503        8337 :   if( FD_UNLIKELY( shred->version!=resolver->expected_shred_version            ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     504        8334 :   if( FD_UNLIKELY( shred_sz<fd_shred_sz( shred )                               ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     505        8334 :   if( FD_UNLIKELY( shred->idx>=resolver->max_shred_idx                         ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     506        8322 :   if( FD_UNLIKELY( shred->fec_set_idx>resolver->max_shred_idx-FD_FEC_SHRED_CNT ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     507        8319 :   if( FD_UNLIKELY( shred->idx-shred->fec_set_idx>=FD_FEC_SHRED_CNT             ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     508        8319 :   if( FD_UNLIKELY( shred->fec_set_idx%FD_FEC_SHRED_CNT!=0UL                    ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     509             : 
     510        8226 :   uchar variant    = shred->variant;
     511        8226 :   uchar shred_type = fd_shred_type( variant );
     512             : 
     513        8226 :   int is_data_shred = fd_shred_is_data( shred_type );
     514             : 
     515        8226 :   if( !is_data_shred ) { /* Roughly 50/50 branch */
     516        4350 :     if( FD_UNLIKELY( (shred->code.data_cnt!=FD_FEC_SHRED_CNT) | (shred->code.code_cnt!=FD_FEC_SHRED_CNT) ) )
     517           3 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     518        4347 :     if( FD_UNLIKELY( shred->code.idx>=FD_FEC_SHRED_CNT            ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     519        4347 :     if( FD_UNLIKELY( shred->code.idx!=shred->idx%FD_FEC_SHRED_CNT ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     520        4347 :   } else {
     521             :     /* if it has slot complete, it must be the last one in the FEC. */
     522        3876 :     if( FD_UNLIKELY( (shred->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE) && ((1UL+shred->idx) % FD_FEC_SHRED_CNT) ) ) {
     523           0 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     524           0 :     }
     525             : 
     526             :     /* discard_unexpected_data_complete_shreds:
     527             :        if it has data complete, it must be the last data shred in the FEC set
     528             :        https://github.com/anza-xyz/agave/blob/v4.0.0-beta.1/ledger/src/shred.rs#L817-L825 */
     529        3876 :     if( FD_UNLIKELY( ( shred->slot >= resolver->discard_unexpected_data_complete_shreds ) &&
     530        3876 :                      ( shred->data.flags & FD_SHRED_DATA_FLAG_DATA_COMPLETE )             &&
     531        3876 :                      ( shred->idx != ( shred->fec_set_idx + ( FD_FEC_SHRED_CNT - 1UL ) ) ) ) ) {
     532           0 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     533           0 :     }
     534        3876 :   }
     535             : 
     536        8223 :   if( FD_UNLIKELY( (shred_type==FD_SHRED_TYPE_LEGACY_DATA) | (shred_type==FD_SHRED_TYPE_LEGACY_CODE) ) ) {
     537             :     /* Reject any legacy shreds */
     538           0 :     return FD_FEC_RESOLVER_SHRED_REJECTED;
     539           0 :   }
     540             : 
     541             : 
     542        8223 :   wrapped_sig_t const * w_sig = (wrapped_sig_t const *)shred->signature;
     543             : 
     544             :   /* Is this FEC set in progress? */
     545        8223 :   set_ctx_t * ctx = ctx_map_ele_query( ctx_map, w_sig, NULL, ctx_pool );
     546             : 
     547             :   /* If it's not in progress and it's repair, we will allocate a context
     548             :      for it, assuming all the other checks pass.  If it's from Turbine,
     549             :      we'll be a little more sceptical about it: if we've already seen a
     550             :      FEC set for that same (slot, FEC set idx) pair, then we won't take
     551             :      it. */
     552        8223 :   if( FD_UNLIKELY( (ctx==NULL) & (!is_repair) ) ) {
     553             :     /* Most likely, it's just done. */
     554         789 :     slot_fec_pair_t slot_fec_pair[1] = {{ .slot = shred->slot, .fec_idx = shred->fec_set_idx }};
     555         789 :     done_ele_t * done_ele = done_map_ele_query( done_map, slot_fec_pair, NULL, done_pool );
     556         789 :     if( FD_LIKELY( done_ele ) ) {
     557         552 :       ulong sig_hash = fd_hash( resolver->seed, w_sig, sizeof(wrapped_sig_t) );
     558         552 :       return fd_int_if( (uint)sig_hash==done_ele->sig_hash, FD_FEC_RESOLVER_SHRED_IGNORED, FD_FEC_RESOLVER_SHRED_EQUIVOC );
     559         552 :     }
     560             : 
     561             :     /* If it's not done, then check for the unlikely case we have it
     562             :        in progress with a different signature. */
     563         237 :     if( FD_UNLIKELY( ctx_treap_ele_query_const( ctx_treap, slot_fec_pair, ctx_pool ) ) ) return FD_FEC_RESOLVER_SHRED_EQUIVOC;
     564         237 :   }
     565             : 
     566             :   /* If we've made it here, then we'll keep this shred as long as
     567             :      it is valid. */
     568             : 
     569        7671 :   fd_bmtree_node_t leaf[1];
     570             : 
     571             :   /* For the purposes of the shred header, tree_depth means the number
     572             :      of nodes, counting the leaf but excluding the root.  For bmtree,
     573             :      depth means the number of layers, which counts both. */
     574        7671 :   ulong tree_depth           = fd_shred_merkle_cnt( variant ); /* In [0, 15] */
     575        7671 :   ulong reedsol_protected_sz = 1115UL + FD_SHRED_DATA_HEADER_SZ - FD_SHRED_SIGNATURE_SZ - FD_SHRED_MERKLE_NODE_SZ*tree_depth
     576        7671 :                                       - FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type )
     577        7671 :                                       - FD_SHRED_SIGNATURE_SZ  *fd_shred_is_resigned( shred_type); /* In [743, 1139] conservatively*/
     578        7671 :   ulong data_merkle_protected_sz   = reedsol_protected_sz + FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained( shred_type );
     579        7671 :   ulong parity_merkle_protected_sz = reedsol_protected_sz + FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained( shred_type )
     580        7671 :                                                           + FD_SHRED_CODE_HEADER_SZ - FD_ED25519_SIG_SZ;
     581        7671 :   ulong merkle_protected_sz  = fd_ulong_if( is_data_shred, data_merkle_protected_sz, parity_merkle_protected_sz );
     582             : 
     583        7671 :   fd_bmtree_hash_leaf( leaf, (uchar const *)shred + sizeof(fd_ed25519_sig_t), merkle_protected_sz, FD_BMTREE_LONG_PREFIX_SZ );
     584             : 
     585             :   /* in_type_idx is between [0, code.data_cnt) or [0, code.code_cnt),
     586             :      where data_cnt <= FD_FEC_SHRED_CNT and code_cnt <= FD_FEC_SHRED_CNT
     587             :      On the other hand, shred_idx, goes from [0, code.data_cnt +
     588             :      code.code_cnt), with all the data shreds having
     589             :      shred_idx < code.data_cnt and all the parity shreds having
     590             :      shred_idx >= code.data_cnt. */
     591        7671 :   ulong in_type_idx = fd_ulong_if( is_data_shred, shred->idx - shred->fec_set_idx, shred->code.idx );
     592        7671 :   ulong shred_idx   = fd_ulong_if( is_data_shred, in_type_idx, in_type_idx + shred->code.data_cnt  );
     593             : 
     594        7671 :   if( FD_UNLIKELY( ( shred->fec_set_idx % FD_FEC_SHRED_CNT ) != 0UL ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     595        7671 :   if( FD_UNLIKELY( in_type_idx >= FD_FEC_SHRED_CNT ) )                  return FD_FEC_RESOLVER_SHRED_REJECTED;
     596             : 
     597             :   /* This, combined with the check on shred->code.data_cnt implies that
     598             :      shred_idx is in [0, 2*FD_FEC_SHRED_CNT). */
     599             : 
     600        7671 :   if( FD_UNLIKELY( tree_depth!=FD_SHRED_MERKLE_LAYER_CNT-1UL ) ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     601             : 
     602        7671 :   if( FD_UNLIKELY( !ctx ) ) { /* This is the first shred in the FEC set */
     603             : 
     604         264 :     if( FD_UNLIKELY( resolver->free_list_cnt<=partial_depth ) ) {
     605             :       /* Packet loss is really high and we have a lot of in-progress FEC
     606             :          sets that we haven't been able to finish.  Evict the context
     607             :          with the highest (slot, FEC idx).  This is the one that is the
     608             :          farthest away from what we're currently replaying, which means
     609             :          we have the longest time to request it via repair if we
     610             :          actually need it.  This also handles the case where a leader
     611             :          sends some shreds from their slots that are far in the future
     612             :          in this epoch. */
     613          30 :       set_ctx_t * victim_ctx = ctx_treap_rev_iter_ele( ctx_treap_rev_iter_init( ctx_treap, ctx_pool ), ctx_pool );
     614             : 
     615          30 :       if( FD_LIKELY( out_spilled ) ) {
     616           6 :         out_spilled->slot         = victim_ctx->slot;
     617           6 :         out_spilled->fec_set_idx  = victim_ctx->fec_set_idx;
     618           6 :         *out_spilled->merkle_root = victim_ctx->root;
     619           6 :       }
     620             : 
     621          30 :       fd_fec_set_t * set = victim_ctx->set;
     622             : 
     623             :       /* TODO: remove this log */
     624          30 :       FD_LOG_INFO(( "Spilled from fec_resolver in-progress map %lu %u, data_shreds_rcvd %x, parity_shreds_rcvd %x", victim_ctx->slot, victim_ctx->fec_set_idx, set->data_shred_rcvd, set->parity_shred_rcvd  ));
     625             : 
     626             :       /* Remove from treap and map, then add to free_list */
     627          30 :       ctx_treap_ele_remove   ( ctx_treap, victim_ctx, ctx_pool );
     628          30 :       ctx_map_ele_remove_fast( ctx_map,   victim_ctx, ctx_pool );
     629             : 
     630          30 :       ctx_list_ele_push_tail ( free_list, victim_ctx, ctx_pool );
     631          30 :       resolver->free_list_cnt++;
     632             : 
     633          30 :       FD_MCNT_INC( SHRED, FEC_SET_SPILLED, 1UL );
     634          30 :     }
     635             :     /* Now we know |free_list|>partial_depth */
     636             : 
     637         264 :     ctx = ctx_list_ele_pop_head( free_list, ctx_pool );
     638         264 :     resolver->free_list_cnt--;
     639             : 
     640             :     /* Now we need to derive the root of the Merkle tree and verify the
     641             :        signature to prevent a DOS attack just by sending lots of invalid
     642             :        shreds. */
     643         264 :     fd_bmtree_commit_t * tree;
     644         264 :     tree = fd_bmtree_commit_init( ctx->_footprint, FD_SHRED_MERKLE_NODE_SZ, FD_BMTREE_LONG_PREFIX_SZ, FD_SHRED_MERKLE_LAYER_CNT );
     645         264 :     FD_TEST( tree==ctx->tree );
     646             : 
     647         264 :     fd_bmtree_node_t _root[1];
     648         264 :     fd_shred_merkle_t const * proof = fd_shred_merkle_nodes( shred );
     649         264 :     int rv = fd_bmtree_commitp_insert_with_proof( tree, shred_idx, leaf, (uchar const *)proof, tree_depth, _root );
     650         264 :     if( FD_UNLIKELY( !rv ) ) {
     651           0 :       ctx_list_ele_push_head( free_list, ctx, ctx_pool );
     652           0 :       resolver->free_list_cnt++;
     653           0 :       FD_MCNT_INC( SHRED, SHRED_REJECTED_INITIAL, 1UL );
     654           0 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     655           0 :     }
     656             : 
     657         264 :     if( FD_UNLIKELY( FD_ED25519_SUCCESS != fd_ed25519_verify( _root->hash, 32UL, shred->signature, leader_pubkey, sha512 ) ) ) {
     658           0 :       ctx_list_ele_push_head( free_list, ctx, ctx_pool );
     659           0 :       resolver->free_list_cnt++;
     660           0 :       FD_MCNT_INC( SHRED, SHRED_REJECTED_INITIAL, 1UL );
     661           0 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     662           0 :     }
     663             : 
     664             :     /* This seems like a legitimate FEC set, so we populate the rest of
     665             :        the fields, then add it to the map and treap. */
     666         264 :     ctx->sig                = *w_sig;
     667         264 :     ctx->slot               = shred->slot;
     668         264 :     ctx->fec_set_idx        = shred->fec_set_idx;
     669         264 :     ctx->data_variant       = fd_uchar_if(  is_data_shred, variant, fd_shred_variant( fd_shred_swap_type( shred_type ), (uchar)tree_depth ) );
     670         264 :     ctx->parity_variant     = fd_uchar_if( !is_data_shred, variant, fd_shred_variant( fd_shred_swap_type( shred_type ), (uchar)tree_depth ) );
     671         264 :     ctx->total_rx_shred_cnt = 0UL;
     672         264 :     ctx->root               = *_root;
     673             : 
     674         264 :     if( FD_UNLIKELY( fd_shred_is_resigned( shred_type ) & !!(resolver->signer) ) ) {
     675           3 :       resolver->signer( resolver->sign_ctx, ctx->retransmitter_sig.u, _root->hash );
     676         261 :     } else {
     677         261 :       fd_memset( ctx->retransmitter_sig.u, 0, 64UL );
     678         261 :     }
     679             : 
     680             :     /* Reset the FEC set */
     681         264 :     ctx->set->data_shred_rcvd   = 0U;
     682         264 :     ctx->set->parity_shred_rcvd = 0U;
     683             : 
     684         264 :     ctx_map_ele_insert  ( ctx_map,   ctx, ctx_pool );
     685         264 :     ctx_treap_ele_insert( ctx_treap, ctx, ctx_pool );
     686             : 
     687             :     /* Copy the merkle root into the output arg. */
     688         264 :     if( FD_LIKELY( out_merkle_root ) ) memcpy( out_merkle_root, ctx->root.hash, sizeof(fd_bmtree_node_t) );
     689             : 
     690        7407 :   } else {
     691             :     /* This is not the first shred in the set */
     692             : 
     693             :     /* Verify that the shred's slot and fec_set_idx match the context
     694             :        established by the first shred. */
     695        7407 :     if( FD_UNLIKELY( shred->slot!=ctx->slot || shred->fec_set_idx!=ctx->fec_set_idx ) ) {
     696           0 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     697           0 :     }
     698             : 
     699             :     /* First ensure that all the shreds in the FEC set have consistent
     700             :        variants.  They all must have the same tree_depth and the same
     701             :        chained/not chained, resigned/not resigned bits. */
     702        7407 :     if( FD_UNLIKELY( variant!=fd_uchar_if( is_data_shred, ctx->data_variant, ctx->parity_variant ) ) ) {
     703           0 :       return FD_FEC_RESOLVER_SHRED_REJECTED;
     704           0 :     }
     705             : 
     706        7407 :     fd_shred_merkle_t const * proof = fd_shred_merkle_nodes( shred );
     707        7407 :     int rv = fd_bmtree_commitp_insert_with_proof( ctx->tree, shred_idx, leaf, (uchar const *)proof, tree_depth, out_merkle_root );
     708        7407 :     if( !rv ) return FD_FEC_RESOLVER_SHRED_REJECTED;
     709             : 
     710             :     /* Check to make sure this is not a duplicate */
     711        7398 :     int shred_dup = !!(fd_uint_if( is_data_shred, ctx->set->data_shred_rcvd, ctx->set->parity_shred_rcvd ) & (1U << in_type_idx));
     712        7398 :     if( FD_UNLIKELY( shred_dup ) ) {
     713         249 :       *out_shred = is_data_shred ? ctx->set->data_shreds[ in_type_idx ].s : ctx->set->parity_shreds[ in_type_idx ].s;
     714         249 :       return FD_FEC_RESOLVER_SHRED_DUPLICATE;
     715         249 :     }
     716        7398 :   }
     717             : 
     718             :   /* At this point, the shred has passed Merkle validation and is new.
     719             :      We also know that ctx is a pointer to the set_ctx_t where this
     720             :      shred belongs. */
     721             : 
     722             :   /* Copy the shred to memory the FEC resolver owns */
     723        7413 :   uchar * dst = is_data_shred ? ctx->set->data_shreds[ in_type_idx ].b : ctx->set->parity_shreds[ in_type_idx ].b;
     724        7413 :   fd_memcpy( dst, shred, fd_shred_sz( shred ) );
     725             : 
     726             :   /* If the shred needs a retransmitter signature, set it */
     727        7413 :   if( FD_UNLIKELY( fd_shred_is_resigned( shred_type ) ) ) {
     728         672 :     memcpy( dst + fd_shred_retransmitter_sig_off( (fd_shred_t *)dst ), ctx->retransmitter_sig.u, 64UL );
     729         672 :   }
     730             : 
     731        7413 :   ctx->set->data_shred_rcvd   |= (uint)(!!is_data_shred)<<in_type_idx;
     732        7413 :   ctx->set->parity_shred_rcvd |= (uint)( !is_data_shred)<<in_type_idx;
     733        7413 :   ctx->total_rx_shred_cnt++;
     734             : 
     735        7413 :   *out_shred = (fd_shred_t const *)dst;
     736             : 
     737             :   /* Do we have enough to begin reconstruction? */
     738        7413 :   if( FD_LIKELY( ctx->total_rx_shred_cnt < FD_FEC_SHRED_CNT ) ) return FD_FEC_RESOLVER_SHRED_OKAY;
     739             : 
     740             :   /* At this point, the FEC set is either valid or permanently invalid,
     741             :      so we can consider it done either way. */
     742             : 
     743         219 :   done_ele_t * done = NULL;
     744         219 :   if( FD_UNLIKELY( !done_pool_free( done_pool ) ) ) {
     745             :     /* Done map is full, so we'll forget about the oldest slot */
     746         138 :     ulong worst_idx = done_heap_idx_peek_min( done_heap );
     747         138 :     FD_TEST( worst_idx!=done_heap_idx_null() ); /* Done pool can't be empty and full at the same time */
     748         138 :     done_heap_idx_remove_min( done_heap,            done_pool );
     749         138 :     done_map_idx_remove_fast( done_map,  worst_idx, done_pool );
     750         138 :     done_pool_idx_release( done_pool, worst_idx );
     751             :     /* Now it's not empty */
     752         138 :   }
     753             : 
     754             :   /* If it's already in the done map, we don't need to re-insert it.
     755             :      It's not very clear what we should do if the sig_hashes differ, but
     756             :      this can only happen the second insert was a repair shred, and in
     757             :      that case, it gets bypassed anyway, so it doesn't really matter.
     758             :      We'll just keep the existing value in that case. */
     759         219 :   slot_fec_pair_t done_key[1] = {{ .slot = ctx->slot, .fec_idx = ctx->fec_set_idx }};
     760         219 :   if( FD_LIKELY( !done_map_ele_query( done_map, done_key, NULL, done_pool ) ) ) {
     761         219 :     done = done_pool_ele_acquire( done_pool );
     762             : 
     763         219 :     done->key.slot    = ctx->slot;
     764         219 :     done->key.fec_idx = ctx->fec_set_idx;
     765         219 :     done->sig_hash    = (uint)fd_hash( resolver->seed, w_sig, sizeof(wrapped_sig_t) );
     766             : 
     767         219 :     done_heap_ele_insert( done_heap, done, done_pool );
     768         219 :     done_map_ele_insert ( done_map,  done, done_pool );
     769         219 :   }
     770             : 
     771             : 
     772         219 :   ctx_map_ele_remove_fast( ctx_map,   ctx, ctx_pool );
     773         219 :   ctx_treap_ele_remove   ( ctx_treap, ctx, ctx_pool );
     774             :   /* At this point, ctx is not in any of the data structures, so we need
     775             :      to be sure to add it to one of the lists before exiting. */
     776             : 
     777         219 :   fd_fec_set_t       * set  = ctx->set;
     778         219 :   fd_bmtree_commit_t * tree = ctx->tree;
     779             : 
     780         219 :   reedsol = fd_reedsol_recover_init( (void*)reedsol, reedsol_protected_sz );
     781        7227 :   for( ulong i=0UL; i<FD_FEC_SHRED_CNT; i++ ) {
     782        7008 :     uchar * rs_payload = set->data_shreds[ i ].b + sizeof(fd_ed25519_sig_t);
     783        7008 :     if( set->data_shred_rcvd&(1U<<i) ) fd_reedsol_recover_add_rcvd_shred  ( reedsol, 1, rs_payload );
     784        3615 :     else                               fd_reedsol_recover_add_erased_shred( reedsol, 1, rs_payload );
     785        7008 :   }
     786        7227 :   for( ulong i=0UL; i<FD_FEC_SHRED_CNT; i++ ) {
     787        7008 :     uchar * rs_payload = set->parity_shreds[ i ].b + FD_SHRED_CODE_HEADER_SZ;
     788        7008 :     if( set->parity_shred_rcvd&(1U<<i) ) fd_reedsol_recover_add_rcvd_shred  ( reedsol, 0, rs_payload );
     789        3393 :     else                                 fd_reedsol_recover_add_erased_shred( reedsol, 0, rs_payload );
     790        7008 :   }
     791             : 
     792         219 :   if( FD_UNLIKELY( FD_REEDSOL_SUCCESS != fd_reedsol_recover_fini( reedsol ) ) ) {
     793             :     /* A few lines up, we already checked to make sure it wasn't the
     794             :        insufficient case, so it must be the inconsistent case.  That
     795             :        means the leader signed a shred with invalid Reed-Solomon FEC
     796             :        set.  This shouldn't happen in practice, but we need to handle it
     797             :        for the malicious leader case.  This should probably be a
     798             :        slash-able offense. */
     799           0 :     ctx_list_ele_push_tail( free_list, ctx, ctx_pool );
     800           0 :     resolver->free_list_cnt++;
     801           0 :     FD_MCNT_INC( SHRED, FEC_REJECTED_FATAL, 1UL );
     802           0 :     return FD_FEC_RESOLVER_SHRED_REJECTED;
     803           0 :   }
     804             : 
     805         219 :   uchar const * chained_root = fd_ptr_if( fd_shred_is_chained( shred_type ), (uchar *)shred+fd_shred_chain_off( variant ), NULL );
     806             : 
     807             :   /* Iterate over recovered shreds, add them to the Merkle tree,
     808             :      populate headers and signatures. */
     809        7227 :   for( ulong i=0UL; i<FD_FEC_SHRED_CNT; i++ ) {
     810        7008 :     if( !(set->data_shred_rcvd&(1U<<i)) ) {
     811        3615 :       fd_memcpy( set->data_shreds[i].b, shred, sizeof(fd_ed25519_sig_t) );
     812        3615 :       if( FD_LIKELY( fd_shred_is_chained( shred_type ) ) ) {
     813        3615 :         fd_memcpy( set->data_shreds[i].b+fd_shred_chain_off( ctx->data_variant ), chained_root, FD_SHRED_MERKLE_ROOT_SZ );
     814        3615 :       }
     815        3615 :       fd_bmtree_hash_leaf( leaf, set->data_shreds[i].b+sizeof(fd_ed25519_sig_t), data_merkle_protected_sz, FD_BMTREE_LONG_PREFIX_SZ );
     816        3615 :       if( FD_UNLIKELY( !fd_bmtree_commitp_insert_with_proof( tree, i, leaf, NULL, 0, NULL ) ) ) {
     817           0 :         ctx_list_ele_push_tail( free_list, ctx, ctx_pool );
     818           0 :         resolver->free_list_cnt++;
     819           0 :         FD_MCNT_INC( SHRED, FEC_REJECTED_FATAL, 1UL );
     820           0 :         return FD_FEC_RESOLVER_SHRED_REJECTED;
     821           0 :       }
     822        3615 :     }
     823        7008 :   }
     824             : 
     825        7227 :   for( ulong i=0UL; i<FD_FEC_SHRED_CNT; i++ ) {
     826        7008 :     if( !(set->parity_shred_rcvd&(1U<<i)) ) {
     827        3393 :       fd_shred_t * p_shred = set->parity_shreds[i].s; /* We can't parse because we haven't populated the header */
     828        3393 :       fd_memcpy( p_shred->signature, shred->signature, sizeof(fd_ed25519_sig_t) );
     829        3393 :       p_shred->variant       = ctx->parity_variant;
     830        3393 :       p_shred->slot          = shred->slot;
     831        3393 :       p_shred->idx           = (uint)(i + ctx->fec_set_idx);
     832        3393 :       p_shred->version       = shred->version;
     833        3393 :       p_shred->fec_set_idx   = (uint)ctx->fec_set_idx;
     834        3393 :       p_shred->code.data_cnt = (ushort)FD_FEC_SHRED_CNT;
     835        3393 :       p_shred->code.code_cnt = (ushort)FD_FEC_SHRED_CNT;
     836        3393 :       p_shred->code.idx      = (ushort)i;
     837             : 
     838        3393 :       if( FD_LIKELY( fd_shred_is_chained( shred_type ) ) ) {
     839        3393 :         fd_memcpy( set->parity_shreds[i].b+fd_shred_chain_off( ctx->parity_variant ), chained_root, FD_SHRED_MERKLE_ROOT_SZ );
     840        3393 :       }
     841             : 
     842        3393 :       fd_bmtree_hash_leaf( leaf, set->parity_shreds[i].b+sizeof(fd_ed25519_sig_t), parity_merkle_protected_sz, FD_BMTREE_LONG_PREFIX_SZ );
     843        3393 :       if( FD_UNLIKELY( !fd_bmtree_commitp_insert_with_proof( tree, FD_FEC_SHRED_CNT + i, leaf, NULL, 0, NULL ) ) ) {
     844           0 :         ctx_list_ele_push_tail( free_list, ctx, ctx_pool );
     845           0 :         resolver->free_list_cnt++;
     846           0 :         FD_MCNT_INC( SHRED, FEC_REJECTED_FATAL, 1UL );
     847           0 :         return FD_FEC_RESOLVER_SHRED_REJECTED;
     848           0 :       }
     849        3393 :     }
     850        7008 :   }
     851             : 
     852             :   /* Check that the whole Merkle tree is consistent. */
     853         219 :   if( FD_UNLIKELY( !fd_bmtree_commitp_fini( tree, FD_FEC_SHRED_CNT + FD_FEC_SHRED_CNT ) ) ) {
     854           0 :     ctx_list_ele_push_tail( free_list, ctx, ctx_pool );
     855           0 :     resolver->free_list_cnt++;
     856           0 :     FD_MCNT_INC( SHRED, FEC_REJECTED_FATAL, 1UL );
     857           0 :     return FD_FEC_RESOLVER_SHRED_REJECTED;
     858           0 :   }
     859             : 
     860             :   /* Check that all the fields that are supposed to be consistent across
     861             :      an FEC set actually are. */
     862         219 :   fd_shred_t const * base_data_shred   = fd_shred_parse( set->data_shreds  [ 0 ].b, FD_SHRED_MIN_SZ );
     863         219 :   fd_shred_t const * base_parity_shred = fd_shred_parse( set->parity_shreds[ 0 ].b, FD_SHRED_MAX_SZ );
     864         219 :   int reject = (!base_data_shred) | (!base_parity_shred);
     865             : 
     866             :   /* Check idx of base shreds */
     867         219 :   reject = reject || ((base_data_shred->idx!=ctx->fec_set_idx) | (base_parity_shred->idx!=ctx->fec_set_idx) |
     868         219 :                       (base_data_shred->data.flags & FD_SHRED_DATA_FLAG_DATA_COMPLETE));
     869             : 
     870        7008 :   for( ulong i=1UL; (!reject) & (i<FD_FEC_SHRED_CNT); i++ ) {
     871             :     /* Technically, we only need to re-parse the ones we recovered with
     872             :        Reedsol, but parsing is pretty cheap and the rest of the
     873             :        validation we need to do on all of them. */
     874        6789 :     fd_shred_t const * parsed = fd_shred_parse( set->data_shreds[ i ].b, FD_SHRED_MIN_SZ );
     875        6789 :     if( FD_UNLIKELY( !parsed ) ) { reject = 1; break; }
     876        6789 :     reject |= parsed->variant         != base_data_shred->variant;
     877        6789 :     reject |= parsed->slot            != base_data_shred->slot;
     878        6789 :     reject |= parsed->version         != base_data_shred->version;
     879        6789 :     reject |= parsed->fec_set_idx     != base_data_shred->fec_set_idx;
     880        6789 :     reject |= parsed->data.parent_off != base_data_shred->data.parent_off;
     881        6789 :     reject |= parsed->idx             != (uint)(ctx->fec_set_idx+i);
     882        6789 :     reject |= (i!=FD_FEC_SHRED_CNT-1UL) && (parsed->data.flags & FD_SHRED_DATA_FLAG_DATA_COMPLETE);
     883             : 
     884        6789 :     reject |= fd_shred_is_chained( fd_shred_type( parsed->variant ) ) &&
     885        6789 :                 !fd_memeq( (uchar *)parsed         +fd_shred_chain_off( parsed->variant          ),
     886        6789 :                            (uchar *)base_data_shred+fd_shred_chain_off( base_data_shred->variant ), FD_SHRED_MERKLE_ROOT_SZ );
     887        6789 :   }
     888             : 
     889        7227 :   for( ulong i=0UL; (!reject) & (i<FD_FEC_SHRED_CNT); i++ ) {
     890        7008 :     fd_shred_t const * parsed = fd_shred_parse( set->parity_shreds[ i ].b, FD_SHRED_MAX_SZ );
     891        7008 :     if( FD_UNLIKELY( !parsed ) ) { reject = 1; break; }
     892        7008 :     reject |= fd_shred_type( parsed->variant )       != fd_shred_swap_type( fd_shred_type( base_data_shred->variant ) );
     893        7008 :     reject |= fd_shred_merkle_cnt( parsed->variant ) != fd_shred_merkle_cnt( base_data_shred->variant );
     894        7008 :     reject |= parsed->slot                           != base_data_shred->slot;
     895        7008 :     reject |= parsed->version                        != base_data_shred->version;
     896        7008 :     reject |= parsed->fec_set_idx                    != base_data_shred->fec_set_idx;
     897        7008 :     reject |= parsed->idx                            != (uint)(ctx->fec_set_idx+i);
     898        7008 :     reject |= parsed->code.data_cnt                  != base_parity_shred->code.data_cnt;
     899        7008 :     reject |= parsed->code.code_cnt                  != base_parity_shred->code.code_cnt;
     900        7008 :     reject |= parsed->code.idx                       != (ushort)i;
     901             : 
     902        7008 :     reject |= fd_shred_is_chained( fd_shred_type( parsed->variant ) ) &&
     903        7008 :                 !fd_memeq( (uchar *)parsed         +fd_shred_chain_off( parsed->variant          ),
     904        7008 :                            (uchar *)base_data_shred+fd_shred_chain_off( base_data_shred->variant ), FD_SHRED_MERKLE_ROOT_SZ );
     905        7008 :   }
     906         219 :   if( FD_UNLIKELY( reject ) ) {
     907           0 :     ctx_list_ele_push_tail( free_list, ctx, ctx_pool );
     908           0 :     resolver->free_list_cnt++;
     909           0 :     FD_MCNT_INC( SHRED, FEC_REJECTED_FATAL, 1UL );
     910           0 :     return FD_FEC_RESOLVER_SHRED_REJECTED;
     911           0 :   }
     912             : 
     913             :   /* Populate missing Merkle proofs */
     914        7227 :   for( ulong i=0UL; i<FD_FEC_SHRED_CNT; i++   ) if( !( set->data_shred_rcvd&(1U<<i) ) )
     915        3615 :     fd_bmtree_get_proof( tree, set->data_shreds[i].b   + fd_shred_merkle_off( set->data_shreds[i].s   ), i );
     916             : 
     917        7227 :   for( ulong i=0UL; i<FD_FEC_SHRED_CNT; i++ ) if( !( set->parity_shred_rcvd&(1U<<i) ) )
     918        3393 :     fd_bmtree_get_proof( tree, set->parity_shreds[i].b + fd_shred_merkle_off( set->parity_shreds[i].s ), FD_FEC_SHRED_CNT+i );
     919             : 
     920             :   /* Set the retransmitter signature for shreds that need one */
     921         219 :   if( FD_UNLIKELY( fd_shred_is_resigned( shred_type ) ) ) {
     922         693 :     for( ulong i=0UL; i<FD_FEC_SHRED_CNT; i++   ) if( !( set->data_shred_rcvd&(1U<<i) ) )
     923         372 :       memcpy( set->data_shreds[i].b   + fd_shred_retransmitter_sig_off( set->data_shreds[i].s   ), ctx->retransmitter_sig.u, 64UL );
     924             : 
     925         693 :     for( ulong i=0UL; i<FD_FEC_SHRED_CNT; i++ ) if( !( set->parity_shred_rcvd&(1U<<i) ) )
     926         300 :       memcpy( set->parity_shreds[i].b + fd_shred_retransmitter_sig_off( set->parity_shreds[i].s ), ctx->retransmitter_sig.u, 64UL );
     927          21 :   }
     928             : 
     929             :   /* Finally... A valid FEC set.  Forward it along. */
     930         219 :   ctx_list_ele_push_tail( complete_list, ctx, ctx_pool );
     931         219 :   ctx_list_idx_push_tail( free_list, ctx_list_idx_pop_head( complete_list, ctx_pool ), ctx_pool );
     932         219 :   resolver->free_list_cnt++;
     933             : 
     934         219 :   *out_fec_set = set;
     935             : 
     936         219 :   return FD_FEC_RESOLVER_SHRED_COMPLETES;
     937         219 : }
     938             : 
     939             : 
     940          21 : void * fd_fec_resolver_leave( fd_fec_resolver_t * resolver ) {
     941          21 :   fd_sha512_leave( resolver->sha512        );
     942          21 :   done_heap_leave( resolver->done_heap     );
     943          21 :   ctx_list_leave ( resolver->complete_list );
     944          21 :   ctx_list_leave ( resolver->free_list     );
     945          21 :   ctx_treap_leave( resolver->ctx_treap     );
     946          21 :   done_map_leave ( resolver->done_map      );
     947          21 :   done_pool_leave( resolver->done_pool     );
     948          21 :   ctx_map_leave  ( resolver->ctx_map       );
     949             : 
     950          21 :   return (void *)resolver;
     951          21 : }
     952             : 
     953          21 : void * fd_fec_resolver_delete( void * shmem ) {
     954          21 :   fd_fec_resolver_t * resolver = (fd_fec_resolver_t *)shmem;
     955          21 :   ulong depth          = resolver->depth;
     956          21 :   ulong partial_depth  = resolver->partial_depth;
     957          21 :   ulong complete_depth = resolver->complete_depth;
     958          21 :   ulong done_depth     = resolver->done_depth;
     959             : 
     960          21 :   ulong depth_sum      = depth + partial_depth + complete_depth;
     961          21 :   ulong ctx_chain_cnt  = ctx_map_chain_cnt_est ( depth      );
     962          21 :   ulong done_chain_cnt = done_map_chain_cnt_est( done_depth );
     963             : 
     964          21 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     965          21 :   /*     self      */  FD_SCRATCH_ALLOC_APPEND( l, FD_FEC_RESOLVER_ALIGN,  sizeof(fd_fec_resolver_t)                 );
     966          21 :   /*     _ctx_pool */  FD_SCRATCH_ALLOC_APPEND( l, alignof(set_ctx_t),     sizeof(set_ctx_t)*depth_sum               );
     967          21 :   void * _ctx_map    = FD_SCRATCH_ALLOC_APPEND( l, ctx_map_align(),        ctx_map_footprint  ( ctx_chain_cnt  ) );
     968          21 :   void * _done_pool  = FD_SCRATCH_ALLOC_APPEND( l, done_pool_align(),      done_pool_footprint( done_depth         ) );
     969          21 :   void * _done_map   = FD_SCRATCH_ALLOC_APPEND( l, done_map_align(),       done_map_footprint ( done_chain_cnt ) );
     970          21 :   FD_SCRATCH_ALLOC_FINI( l, FD_FEC_RESOLVER_ALIGN );
     971             : 
     972          21 :   fd_sha512_delete( resolver->sha512        );
     973          21 :   done_heap_delete( resolver->done_heap     );
     974          21 :   done_map_delete ( _done_map               );
     975          21 :   done_pool_delete( _done_pool              );
     976          21 :   ctx_list_delete ( resolver->complete_list );
     977          21 :   ctx_list_delete ( resolver->free_list     );
     978          21 :   ctx_treap_delete( resolver->ctx_treap     );
     979          21 :   ctx_map_delete  ( _ctx_map                );
     980             : 
     981          21 :   return shmem;
     982          21 : }

Generated by: LCOV version 1.14