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

Generated by: LCOV version 1.14