LCOV - code coverage report
Current view: top level - flamenco/runtime/tests - fd_shred_harness.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 257 0.0 %
Date: 2026-10-18 04:43:39 Functions: 0 3 0.0 %

          Line data    Source code
       1             : #include "fd_solfuzz_private.h"
       2             : #include "generated/shred.pb.h"
       3             : 
       4             : #include "../../../ballet/shred/fd_shred.h"
       5             : #include "../../../disco/shred/fd_fec_resolver.h"
       6             : #include "../../../disco/metrics/fd_metrics.h"
       7             : #include "../../../discof/reasm/fd_reasm.h"
       8             : #include "../../../discof/replay/fd_sched.h"
       9             : 
      10             : /* Resolver sizing.  Production sizing lives in the shred tile setup;
      11             :    the values below are much smaller because the harness consumes
      12             :    results synchronously, so partial/complete queue depth has no
      13             :    consumer-lag to absorb.  See fd_fec_resolver.h for the precise
      14             :    semantics of each depth parameter:
      15             :      - DEPTH:          max in-flight FEC sets before spill
      16             :      - PARTIAL_DEPTH:  out_shred pointer-lifetime guarantee
      17             :      - COMPLETE_DEPTH: out_fec_set pointer-lifetime guarantee
      18             :      - DONE_DEPTH:     memory of completed (slot, fec_set_idx)
      19             :                        pairs used for duplicate detection
      20             :      - SLOT_MAX:       max slots tracked by the resolver */
      21             : static ulong const RESOLVER_DEPTH          = 32UL;
      22             : static ulong const RESOLVER_PARTIAL_DEPTH  = 8UL;
      23             : static ulong const RESOLVER_COMPLETE_DEPTH = 8UL;
      24             : static ulong const RESOLVER_DONE_DEPTH     = 256UL;
      25             : static ulong const RESOLVER_SLOT_MAX       = 4096UL;
      26             : 
      27             : /* Reasm + scheduler sizing.  Only loosely tied to the resolver depths
      28             :    above: large enough to cover any reasonable fuzz input while keeping
      29             :    spad footprint bounded. */
      30             : static ulong const REASM_POOL_MAX       = 1024UL;
      31             : 
      32             : /* Scheduler txn/block pool sizes, bounded to the fuzz input so
      33             :    fd_sched_new's per-run free-list init stays cheap. */
      34             : static ulong const SCHED_DEPTH          = 4096UL;
      35             : static ulong const SCHED_BLOCK_CNT_MAX  = 128UL;
      36             : static ulong const SCHED_EXEC_CNT       = 4UL;
      37             : 
      38             : /* Tick-verification parameters passed to fd_sched_block_verify_ticks.
      39             :    ticks_per_slot=64 (tick_height 0, max_tick_height 64) and
      40             :    hashes_per_tick=62500 to match mainnet and solfuzz-agave's reference
      41             :    bank. */
      42             : static ulong const SLOT_MAX_TICK_HEIGHT = 64UL;
      43             : static ulong const SLOT_HASHES_PER_TICK = 62500UL;
      44             : 
      45             : typedef struct {
      46             :   fd_hash_t mr;
      47             :   fd_hash_t cmr;
      48             :   ulong     slot;
      49             :   uint      fec_set_idx;
      50             :   ushort    parent_off;
      51             :   ushort    num_data_shreds;
      52             :   ushort    num_coding_shreds;
      53             :   uint      shred_cnt;
      54             :   ushort    shred_sz[ FD_FEC_SHRED_CNT ];
      55             :   int       data_complete;
      56             :   int       slot_complete;
      57             :   ulong     payload_sz;
      58             :   uchar *   payload;
      59             : } fd_shred_completed_fec_t;
      60             : 
      61             : #define SORT_NAME                sort_completed_fec
      62           0 : #define SORT_KEY_T               fd_shred_completed_fec_t
      63           0 : #define SORT_BEFORE(a,b)         ( (a).slot<(b).slot || ( (a).slot==(b).slot && (a).fec_set_idx<(b).fec_set_idx ) )
      64             : #define SORT_QUICK_SWAP_MINIMIZE 1 /* ~240B key; skip no-op swaps */
      65             : #include "../../../util/tmpl/fd_sort.c"
      66             : 
      67             : /* Return reasm's evicted single-child chain to the pool. */
      68             : static void
      69             : release_evicted_chain( fd_reasm_t *     reasm,
      70           0 :                        fd_reasm_fec_t * evicted ) {
      71           0 :   while( evicted ) {
      72           0 :     fd_reasm_fec_t * next = fd_reasm_child( reasm, evicted );
      73           0 :     fd_reasm_pool_release( reasm, evicted );
      74           0 :     evicted = next;
      75           0 :   }
      76           0 : }
      77             : 
      78             : /* This is a harness simplification that avoids having to use store.
      79             :    Also includes logic from the shred tile to concatenate FEC set
      80             :    payloads into a contiguous buffer. */
      81             : static void
      82             : capture_completed_fec( fd_spad_t *                spad,
      83             :                        fd_fec_set_t const *       set,
      84           0 :                        fd_shred_completed_fec_t * out ) {
      85           0 :   fd_memset( out, 0, sizeof(fd_shred_completed_fec_t) );
      86             : 
      87           0 :   fd_shred_t const * base_data   = fd_shred_parse( set->data_shreds  [ 0 ].b, FD_SHRED_MIN_SZ, FD_SHRED_PROTOCOL_BLK_MAX );
      88           0 :   fd_shred_t const * base_parity = fd_shred_parse( set->parity_shreds[ 0 ].b, FD_SHRED_MAX_SZ, FD_SHRED_PROTOCOL_BLK_MAX );
      89           0 :   FD_TEST( base_data && base_parity );
      90             : 
      91           0 :   ushort data_cnt = base_parity->code.data_cnt;
      92           0 :   ushort code_cnt = base_parity->code.code_cnt;
      93             : 
      94             :   /* Concatenate data-shred payloads into a single buffer. */
      95           0 :   uchar * payload    = fd_spad_alloc( spad, alignof(uchar), (ulong)data_cnt*FD_SHRED_MAX_SZ );
      96           0 :   ulong   payload_sz = 0UL;
      97           0 :   for( ushort i=0U; i<data_cnt; i++ ) {
      98           0 :     fd_shred_t const * shred = fd_shred_parse( set->data_shreds[ i ].b, FD_SHRED_MIN_SZ, FD_SHRED_PROTOCOL_BLK_MAX );
      99           0 :     FD_TEST( shred );
     100           0 :     ulong shred_payload_sz = fd_shred_payload_sz( shred );
     101           0 :     if( FD_LIKELY( shred_payload_sz ) ) {
     102           0 :       memcpy( payload + payload_sz, fd_shred_data_payload( shred ), shred_payload_sz );
     103           0 :       payload_sz += shred_payload_sz;
     104           0 :     }
     105           0 :     FD_TEST( shred_payload_sz<=USHORT_MAX );
     106           0 :     out->shred_sz[ i ] = (ushort)shred_payload_sz;
     107           0 :   }
     108             : 
     109           0 :   fd_shred_t const * last = fd_shred_parse( set->data_shreds[ data_cnt-1U ].b, FD_SHRED_MIN_SZ, FD_SHRED_PROTOCOL_BLK_MAX );
     110           0 :   FD_TEST( last );
     111             : 
     112             :   /* Derive the FEC set's merkle root from a shred's inclusion proof. */
     113           0 :   uchar bmtree_mem[ FD_BMTREE_COMMIT_FOOTPRINT( FD_SHRED_MERKLE_LAYER_CNT ) ] __attribute__((aligned(FD_BMTREE_COMMIT_ALIGN)));
     114           0 :   fd_bmtree_node_t root[1];
     115           0 :   FD_TEST( fd_shred_merkle_root( base_data, bmtree_mem, root ) );
     116           0 :   memcpy( out->mr.hash, root->hash, sizeof(out->mr.hash) );
     117           0 :   out->slot              = base_data->slot;
     118           0 :   out->fec_set_idx       = base_data->fec_set_idx;
     119           0 :   out->parent_off        = base_data->data.parent_off;
     120           0 :   out->num_data_shreds   = data_cnt;
     121           0 :   out->num_coding_shreds = code_cnt;
     122           0 :   out->shred_cnt         = data_cnt;
     123           0 :   out->data_complete     = !!(last->data.flags & FD_SHRED_DATA_FLAG_DATA_COMPLETE);
     124           0 :   out->slot_complete     = !!(last->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE);
     125           0 :   out->payload_sz        = payload_sz;
     126           0 :   out->payload           = payload;
     127             : 
     128             :   /* Shreds are always chained, so the chained root is present. */
     129           0 :   memcpy( out->cmr.hash, (uchar const *)base_data + fd_shred_chain_off( base_data->variant ), FD_SHRED_MERKLE_ROOT_SZ );
     130           0 : }
     131             : 
     132             : ulong
     133             : fd_solfuzz_pb_shred_run( fd_solfuzz_runner_t * runner,
     134             :                          void const *          input_,
     135             :                          void **               output_,
     136             :                          void *                output_buf,
     137           0 :                          ulong                 output_bufsz ) {
     138           0 :   fd_exec_test_shred_parse_context_t const * input = fd_type_pun_const( input_ );
     139           0 :   fd_exec_test_shred_parse_effects_t **      output = fd_type_pun( output_ );
     140             : 
     141             :   /* Initialize output protobuf in the caller-provided buffer.  The
     142             :      fixed-size header allocations (effects struct, per-shred result
     143             :      array, per-FEC result array) must always fit; the caller is
     144             :      responsible for sizing output_buf, and a fit-check failure here
     145             :      is a harness wiring bug.  Variable-size payload writes inside the
     146             :      pop loop are checked separately and treated as soft failures. */
     147           0 :   ulong output_end = (ulong)output_buf + output_bufsz;
     148           0 :   FD_SCRATCH_ALLOC_INIT( l, output_buf );
     149             : 
     150           0 :   fd_exec_test_shred_parse_effects_t * effects =
     151           0 :     FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_shred_parse_effects_t), sizeof(fd_exec_test_shred_parse_effects_t) );
     152           0 :   FD_TEST( _l <= output_end );
     153           0 :   fd_memset( effects, 0, sizeof(*effects) );
     154           0 :   effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_ACCEPTED;
     155             : 
     156           0 :   ulong shred_cnt = (ulong)input->shreds_count;
     157           0 :   effects->shred_results_count = (pb_size_t)shred_cnt;
     158           0 :   effects->shred_results =
     159           0 :     FD_SCRATCH_ALLOC_APPEND( l, alignof(bool), sizeof(bool)*shred_cnt );
     160           0 :   FD_TEST( _l <= output_end );
     161           0 :   fd_memset( effects->shred_results, 0, sizeof(bool)*shred_cnt );
     162             : 
     163           0 :   ulong max_fec_results = shred_cnt;
     164           0 :   effects->fec_set_results =
     165           0 :     FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_fec_set_parse_result_t), sizeof(fd_exec_test_fec_set_parse_result_t)*max_fec_results );
     166           0 :   FD_TEST( _l <= output_end );
     167           0 :   fd_memset( effects->fec_set_results, 0, sizeof(fd_exec_test_fec_set_parse_result_t)*max_fec_results );
     168           0 :   effects->fec_set_results_count = 0U;
     169             : 
     170             :   /* The resolver/sched bump FD_MCNT counters (e.g. reject paths) via a
     171             :      per-thread pointer that is NULL outside a tile.  Register a dummy
     172             :      write-sink to avoid a NULL deref; the values are never read. */
     173           0 :   static FD_TL uchar harness_metrics[ FD_METRICS_FOOTPRINT( 0 ) ] __attribute__((aligned(FD_METRICS_ALIGN)));
     174           0 :   fd_metrics_register( (ulong *)fd_metrics_new( harness_metrics, 0UL ) );
     175             : 
     176             :   /* Build a resolver configured for the fixture's shred version/root
     177             :      context. */
     178           0 :   ulong const resolver_set_cnt = RESOLVER_DEPTH + RESOLVER_PARTIAL_DEPTH + RESOLVER_COMPLETE_DEPTH;
     179             : 
     180           0 :   fd_fec_set_t * resolver_sets =
     181           0 :       fd_spad_alloc( runner->spad, alignof(fd_fec_set_t), sizeof(fd_fec_set_t)*resolver_set_cnt );
     182             : 
     183           0 :   ulong  resolver_footprint = fd_fec_resolver_footprint( RESOLVER_DEPTH,
     184           0 :                                                          RESOLVER_PARTIAL_DEPTH,
     185           0 :                                                          RESOLVER_COMPLETE_DEPTH,
     186           0 :                                                          RESOLVER_DONE_DEPTH );
     187           0 :   void * resolver_mem       = fd_spad_alloc( runner->spad, fd_fec_resolver_align(), resolver_footprint );
     188             : 
     189             :   /* Resolver/reasm/sched construction parameters are all compile-time
     190             :      constants or sourced from the trusted runner spad -- a NULL return
     191             :      from any of these constructors is a harness setup bug. */
     192           0 :   fd_fec_resolver_t * resolver = fd_fec_resolver_join( fd_fec_resolver_new(
     193           0 :       resolver_mem,
     194           0 :       RESOLVER_DEPTH,
     195           0 :       RESOLVER_PARTIAL_DEPTH,
     196           0 :       RESOLVER_COMPLETE_DEPTH,
     197           0 :       RESOLVER_DONE_DEPTH,
     198           0 :       RESOLVER_SLOT_MAX,
     199           0 :       resolver_sets,
     200           0 :       0UL ) );
     201           0 :   FD_TEST( resolver );
     202             : 
     203             :   /* Configure resolver behavior for the fuzz run:
     204             :        - bypass Merkle proof + Ed25519 signature checks
     205             :          (see fd_fec_resolver_set_bypass_verify in fd_fec_resolver.h)
     206             :        - drop shreds for slots strictly older than root_slot */
     207           0 :   fd_fec_resolver_set_shred_version( resolver, (ushort)input->shred_version );
     208           0 :   fd_fec_resolver_set_bypass_verify( resolver, 1 );
     209           0 :   fd_fec_resolver_advance_slot_old ( resolver, input->root_slot );
     210             : 
     211             :   /* Initialize reasm/sched so completed FEC sets can be
     212             :      replay-ingested in order. */
     213           0 :   void * reasm_mem = fd_spad_alloc( runner->spad, fd_reasm_align(), fd_reasm_footprint( REASM_POOL_MAX ) );
     214           0 :   fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( reasm_mem, REASM_POOL_MAX, 0UL ) );
     215           0 :   FD_TEST( reasm );
     216           0 :   int reasm_initialized = 0;
     217             : 
     218           0 :   fd_rng_t rng_mem[1];
     219           0 :   fd_rng_t * rng = fd_rng_join( fd_rng_new( rng_mem, 0U, 0UL ) );
     220           0 :   void * sched_mem = fd_spad_alloc( runner->spad, fd_sched_align(), fd_sched_footprint( SCHED_DEPTH, SCHED_BLOCK_CNT_MAX, FD_SHRED_PROTOCOL_BLK_MAX, FD_MAX_TXN_PER_SLOT ) );
     221           0 :   fd_sched_t * sched = fd_sched_join( fd_sched_new( sched_mem, rng, SCHED_DEPTH, SCHED_BLOCK_CNT_MAX, FD_SHRED_PROTOCOL_BLK_MAX, FD_MAX_TXN_PER_SLOT, SCHED_EXEC_CNT, 0 ) );
     222           0 :   FD_TEST( sched );
     223           0 :   fd_sched_set_bypass_poh_verify( sched, 1 ); /* skip PoH end_hash compare */
     224           0 :   fd_sched_set_bypass_alut_resolution( sched, 1 ); /* skip ALUT resolution (no accdb) */
     225           0 :   fd_sched_block_add_done( sched, 0UL, ULONG_MAX, input->root_slot );
     226           0 :   fd_sched_root_notify( sched, 0UL );
     227           0 :   fd_sched_advance_root( sched, 0UL );
     228             : 
     229             :   /* completed[] caches FEC payloads for sched ingestion.  This is a
     230             :      simplified version of store specifically for the harness.  Instead
     231             :      of querying store for FEC set data, we just store it in this
     232             :      local array. */
     233           0 :   fd_shred_completed_fec_t * completed =
     234           0 :     fd_spad_alloc( runner->spad, alignof(fd_shred_completed_fec_t), sizeof(fd_shred_completed_fec_t)*max_fec_results );
     235           0 :   ulong completed_cnt = 0UL;
     236           0 :   uint next_bank_idx = 1U; /* 0 is the reasm/sched root, so start at 1 */
     237             : 
     238             :   /* Parse each shred, feed resolver, and only continue when an FEC
     239             :      completes. */
     240           0 :   fd_pubkey_t dummy_leader_pubkey = {0};
     241           0 :   for( ulong i=0UL; i<shred_cnt; i++ ) {
     242           0 :     pb_bytes_array_t const * shred_msg = input->shreds[ i ];
     243           0 :     FD_TEST( shred_msg );
     244           0 :     FD_TEST( shred_msg->size>=FD_SHRED_MIN_SZ && shred_msg->size<=FD_SHRED_MAX_SZ );
     245             : 
     246             :     /* Step 1: fd_shred_parse() */
     247           0 :     fd_shred_t const * shred = fd_shred_parse( shred_msg->bytes, shred_msg->size, FD_SHRED_PROTOCOL_BLK_MAX );
     248             : 
     249             :     /* Unchained merkle shreds are dropped in the shred tile. */
     250           0 :     if( !shred || !fd_shred_is_chained( fd_shred_type( shred->variant ) ) ) {
     251           0 :       effects->shred_results[ i ] = false;
     252           0 :       continue;
     253           0 :     }
     254             : 
     255             :     /* Parse-level accept, matching Agave; the resolver verdict below is not folded in. */
     256           0 :     effects->shred_results[ i ] = true;
     257             : 
     258             :     /* Step 2: fd_fec_resolver_add_shred() */
     259           0 :     fd_fec_set_t const * out_fec_set = NULL;
     260           0 :     fd_shred_t const *   out_shred   = NULL;
     261           0 :     fd_bmtree_node_t     out_merkle_root[1];
     262           0 :     int rc = fd_fec_resolver_add_shred(
     263           0 :       resolver,
     264           0 :       shred,
     265           0 :       shred_msg->size,
     266           0 :       FD_SHRED_PROTOCOL_BLK_MAX,
     267           0 :       FD_FEC_RESOLVER_SHRED_SRC_TURBINE,
     268           0 :       dummy_leader_pubkey.uc,
     269           0 :       &out_fec_set,
     270           0 :       &out_shred,
     271           0 :       out_merkle_root,
     272           0 :       NULL
     273           0 :     );
     274           0 :     (void)out_shred;
     275             : 
     276             :     /* We only want to replay the FEC set once it's complete. */
     277           0 :     if( rc!=FD_FEC_RESOLVER_SHRED_COMPLETES ) continue;
     278             : 
     279             :     /* completed[] is sized to shred_cnt, which is a strict upper bound
     280             :        on completion count, so overflow here is a harness sizing bug. */
     281           0 :     FD_TEST( completed_cnt<max_fec_results );
     282             : 
     283             :     /* Construct the completed FEC set record.  The merkle root is derived
     284             :        from the shred's inclusion proof inside capture_completed_fec. */
     285           0 :     fd_shred_completed_fec_t * rec = &completed[ completed_cnt ];
     286           0 :     capture_completed_fec( runner->spad, out_fec_set, rec );
     287             : 
     288             :     /* Buffer the completion; reasm runs after the shred loop in
     289             :        (slot, fec_set_idx) order. */
     290           0 :     completed_cnt++;
     291           0 :   }
     292             : 
     293             :   /* Sort by (slot, fec_set_idx) so set 0 anchors the reasm root
     294             :      before any successor inserts. */
     295           0 :   sort_completed_fec_inplace( completed, completed_cnt );
     296             : 
     297             :   /* One result per completed FEC set, as both validators complete sets
     298             :      whether or not the slot is connected: per slot, the contiguous run
     299             :      from index 0 in which every set chains to the previous one's
     300             :      Merkle root.  Delivery and ingest below only drive the block
     301             :      verdict. */
     302           0 :   for( ulong k=0UL; k<completed_cnt; k++ ) {
     303           0 :     fd_shred_completed_fec_t * rec = &completed[ k ];
     304           0 :     int first_in_slot = ( k==0UL ) || ( completed[ k-1UL ].slot!=rec->slot );
     305           0 :     if( first_in_slot ) {
     306           0 :       if( rec->fec_set_idx!=0U ) { while( k+1UL<completed_cnt && completed[ k+1UL ].slot==rec->slot ) k++; continue; }
     307           0 :     } else {
     308           0 :       fd_shred_completed_fec_t * prev = &completed[ k-1UL ];
     309           0 :       if( rec->fec_set_idx!=prev->fec_set_idx+FD_FEC_SHRED_CNT || memcmp( rec->cmr.hash, prev->mr.hash, sizeof(fd_hash_t) ) ) {
     310           0 :         while( k+1UL<completed_cnt && completed[ k+1UL ].slot==rec->slot ) k++;
     311           0 :         continue;
     312           0 :       }
     313           0 :     }
     314           0 :     FD_TEST( effects->fec_set_results_count<max_fec_results );
     315           0 :     fd_exec_test_fec_set_parse_result_t * out_fec = &effects->fec_set_results[ effects->fec_set_results_count++ ];
     316           0 :     fd_memset( out_fec, 0, sizeof(*out_fec) );
     317           0 :     out_fec->completed         = true;
     318           0 :     out_fec->slot              = rec->slot;
     319           0 :     out_fec->fec_set_index     = rec->fec_set_idx;
     320           0 :     out_fec->parent_offset     = rec->parent_off;
     321           0 :     out_fec->shred_version     = input->shred_version;
     322           0 :     out_fec->num_data_shreds   = rec->num_data_shreds;
     323           0 :     out_fec->num_coding_shreds = rec->num_coding_shreds;
     324           0 :     memcpy( out_fec->merkle_root, rec->mr.hash, FD_SHRED_MERKLE_ROOT_SZ );
     325           0 :     memcpy( out_fec->chained_merkle_root, rec->cmr.hash, FD_SHRED_MERKLE_ROOT_SZ );
     326           0 :     out_fec->payload_hash = fd_solfuzz_hash( rec->payload, rec->payload_sz );
     327           0 :   }
     328             : 
     329             :   /* The root block id is the chained Merkle root of the first completed
     330             :      FEC set that chains to the root slot.  Without one, every completed
     331             :      set is an orphan: the reassembler would hold it and deliver nothing,
     332             :      so there is nothing to insert. */
     333           0 :   for( ulong k=0UL; k<completed_cnt && !reasm_initialized; k++ ) {
     334           0 :     fd_shred_completed_fec_t * rec = &completed[ k ];
     335           0 :     if( rec->fec_set_idx!=0U || rec->slot<rec->parent_off || rec->slot-rec->parent_off!=input->root_slot ) continue;
     336           0 :     fd_reasm_fec_t * root = fd_reasm_init( reasm, &rec->cmr, input->root_slot );
     337           0 :     if( FD_UNLIKELY( !root ) ) {
     338           0 :       effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER;
     339           0 :       break;
     340           0 :     }
     341           0 :     root->bank_idx    = 0UL;
     342           0 :     reasm_initialized = 1;
     343           0 :   }
     344             : 
     345           0 :   for( ulong k=0UL; k<completed_cnt && reasm_initialized; k++ ) {
     346           0 :     fd_shred_completed_fec_t * rec = &completed[ k ];
     347             : 
     348             :     /* Step 3: fd_reasm_insert()
     349             :        Insert completed FEC into reasm and release elements from evicted
     350             :        chains. */
     351           0 :     fd_reasm_fec_t * evicted = NULL;
     352           0 :     if( !fd_reasm_insert( reasm,
     353           0 :                           &rec->mr,
     354           0 :                           &rec->cmr,
     355           0 :                           rec->slot,
     356           0 :                           rec->fec_set_idx,
     357           0 :                           rec->parent_off,
     358           0 :                           rec->num_data_shreds,
     359           0 :                           rec->data_complete,
     360           0 :                           rec->slot_complete,
     361           0 :                           0,
     362           0 :                           NULL,
     363           0 :                           NULL,
     364           0 :                           &evicted ) ) {
     365           0 :       release_evicted_chain( reasm, evicted );
     366           0 :       effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER;
     367           0 :       continue;
     368           0 :     }
     369           0 :     release_evicted_chain( reasm, evicted );
     370             : 
     371             :     /* Step 4: fd_reasm_pop() and fd_fec_set_ingest() while there are
     372             :        completed FEC sets remaining. */
     373           0 :     fd_reasm_fec_t * popped;
     374           0 :     while( FD_LIKELY( (popped = fd_reasm_pop( reasm )) ) ) {
     375             :       /* Query our "store"-adjacent structure (completed) for the FEC
     376             :          set payload */
     377           0 :       fd_shred_completed_fec_t * popped_rec = NULL;
     378           0 :       for( ulong j=0UL; j<completed_cnt; j++ ) {
     379           0 :         if( FD_LIKELY( !memcmp( completed[ j ].mr.hash, popped->key.hash, sizeof(fd_hash_t) ) ) ) {
     380           0 :           popped_rec = &completed[ j ];
     381           0 :           break;
     382           0 :         }
     383           0 :       }
     384           0 :       FD_TEST( popped_rec );
     385             : 
     386             :       /* Match Agave's model of only parsing deshreddable batches.
     387             :          Otherwise, Firedancer's eager per-FEC parsing might reject a
     388             :          block for a bad FEC set that Agave simply doesn't parse at all.
     389             :          A FEC set is deshreddable iff some FEC set at or after it in
     390             :          the same slot carries DATA_COMPLETE.  Complete batches are
     391             :          still fed one FEC set at a time.  Only the final, potentially
     392             :          incomplete batch is held back.  fec_set_results are captured
     393             :          from the completed sets above, independent of the scheduler. */
     394           0 :       int deshreddable = 0;
     395           0 :       for( ulong j=0UL; j<completed_cnt; j++ ) {
     396           0 :         if( completed[ j ].slot==popped_rec->slot && completed[ j ].fec_set_idx>=popped_rec->fec_set_idx && completed[ j ].data_complete ) {
     397           0 :           deshreddable = 1;
     398           0 :           break;
     399           0 :         }
     400           0 :       }
     401           0 :       if( !deshreddable ) continue;
     402             : 
     403             :       /* Bank lineage comes from the reasm tree, like the replay tile.
     404             :          Reasm pops parents before children, so the parent's bank_idx
     405             :          was already recorded by the time we read it here. */
     406           0 :       fd_reasm_fec_t * parent = fd_reasm_parent( reasm, popped );
     407           0 :       FD_TEST( parent );
     408           0 :       uint parent_bank_idx = parent->bank_idx;
     409             : 
     410             :       /* If the FEC set starts a new slot (no bank yet), use a fresh
     411             :          bank index; otherwise reuse the parent's.  Record it on the
     412             :          node so this slot's later FECs inherit it. */
     413           0 :       uint bank_idx = ( popped->fec_set_idx==0U ) ? next_bank_idx++ : parent_bank_idx;
     414           0 :       popped->bank_idx = bank_idx;
     415             : 
     416           0 :       fd_store_fec_t store_fec[1] = {0};
     417           0 :       store_fec->key = popped_rec->mr;
     418           0 :       FD_TEST( popped_rec->payload_sz<=UINT_MAX );
     419           0 :       store_fec->data_sz = (uint)popped_rec->payload_sz;
     420           0 :       memcpy( store_fec->shred_sz, popped_rec->shred_sz, sizeof(store_fec->shred_sz) );
     421             : 
     422           0 :       fd_sched_fec_t sched_fec = {
     423           0 :         .bank_idx          = bank_idx,
     424           0 :         .parent_bank_idx   = parent_bank_idx,
     425           0 :         .slot              = popped_rec->slot,
     426           0 :         .parent_slot       = fd_ulong_if( popped_rec->slot>=popped_rec->parent_off, popped_rec->slot-popped_rec->parent_off, 0UL ),
     427           0 :         .fec               = store_fec,
     428           0 :         .data              = popped_rec->payload,
     429           0 :         .shred_cnt         = popped_rec->shred_cnt,
     430           0 :         .is_last_in_batch  = !!popped_rec->data_complete,
     431           0 :         .is_last_in_block  = !!popped_rec->slot_complete,
     432           0 :         .is_first_in_block = !!(popped_rec->fec_set_idx==0U)
     433           0 :       };
     434             : 
     435             :       /* Drain abandoned blocks; fd_sched_fec_ingest requires an empty
     436             :          ref_q.  The replay tile drains the same queue (to decrement bank
     437             :          refcounts); the harness has no banks, so it just discards. */
     438           0 :       while( fd_sched_pruned_block_next( sched )!=ULONG_MAX ) {}
     439             : 
     440             :       /* Final step: ingest the completed FEC set. */
     441           0 :       FD_TEST( fd_sched_fec_can_ingest( sched, &sched_fec ) );
     442           0 :       if( !fd_sched_fec_ingest( sched, &sched_fec ) ) {
     443           0 :         effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER;
     444           0 :       } else if( FD_LIKELY( bank_idx!=0UL ) ) {
     445             :         /* Harness stops at FEC ingest, so verify this slot's tick
     446             :            window here; skip bank_idx 0 (reasm/sched root).  Only
     447             :            complete batches reach this point, so this matches Agave's
     448             :            model of only verifying deshredded batches. */
     449           0 :         if( fd_sched_block_verify_ticks( sched, bank_idx, 0UL, SLOT_MAX_TICK_HEIGHT, SLOT_HASHES_PER_TICK ) ) {
     450           0 :           effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER;
     451           0 :         }
     452           0 :       }
     453           0 :     }
     454           0 :   }
     455             : 
     456             :   /* Finalize scratch allocation and return encoded effects span. */
     457           0 :   ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
     458           0 :   *output = effects;
     459           0 :   return actual_end - (ulong)output_buf;
     460           0 : }

Generated by: LCOV version 1.14