LCOV - code coverage report
Current view: top level - disco/shred - fd_shred_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 900 0.0 %
Date: 2026-08-21 04:38:30 Functions: 0 16 0.0 %

          Line data    Source code
       1             : #include "../tiles.h"
       2             : 
       3             : #if FD_HAS_X86
       4             : #include <x86intrin.h>
       5             : #endif
       6             : 
       7             : #include "generated/fd_shred_tile_seccomp.h"
       8             : #include "../../util/pod/fd_pod_format.h"
       9             : #include "fd_shredder.h"
      10             : #include "fd_shred_batch.h"
      11             : #include "fd_shred_dest.h"
      12             : #include "fd_fec_resolver.h"
      13             : #include "fd_stake_ci.h"
      14             : #include "fd_rnonce_ss.h"
      15             : #include "fd_shred_tile.h"
      16             : #include "../store/fd_store.h"
      17             : #include "../keyguard/fd_keyload.h"
      18             : #include "../keyguard/fd_keyguard.h"
      19             : #include "../keyguard/fd_keyguard_client.h"
      20             : #include "../keyguard/fd_keyswitch.h"
      21             : #include "../fd_disco.h"
      22             : #include "../net/fd_net_tile.h"
      23             : #include "../../flamenco/leaders/fd_leaders.h"
      24             : #include "../../util/net/fd_net_headers.h"
      25             : #include "../../flamenco/gossip/fd_gossip_message.h"
      26             : #include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h"
      27             : #include "../../flamenco/runtime/fd_slot_params.h"
      28             : #include "../../discof/tower/fd_tower_slot_rooted.h"
      29             : 
      30             : /* The shred tile handles shreds from two data sources: shreds generated
      31             :    from microblocks from the leader pipeline, and shreds retransmitted
      32             :    from the network.
      33             : 
      34             :    They have rather different semantics, but at the end of the day, they
      35             :    both result in a bunch of shreds and FEC sets that need to be sent to
      36             :    the blockstore and on the network, which is why one tile handles
      37             :    both.
      38             : 
      39             :    We segment the memory for the two types of shreds into two halves of
      40             :    a dcache because they follow somewhat different flow control
      41             :    patterns. For flow control, the normal guarantee we want to provide
      42             :    is that the dcache entry is not overwritten unless the mcache entry
      43             :    has also been overwritten.  The normal way to do this when using both
      44             :    cyclically and with a 1-to-1 mapping is to make the dcache at least
      45             :    `burst` entries bigger than the mcache.
      46             : 
      47             :    In this tile, we use one output mcache (of depth d) with one output
      48             :    dcache (which is logically partitioned into two) for the two sources
      49             :    of data.  The worst case for flow control is when we're only sending
      50             :    with one of the dcache partitions at a time though, so we can
      51             :    consider them separately.
      52             : 
      53             :    Leader pipeline: Every entry triggers s FEC sets to be created, where
      54             :    s is in [0, FD_SHRED_BATCH_FEC_SETS_MAX].  Each FEC set corresponds
      55             :    to 1 dcache entry and 1 mcache entry.  This means we can have d FEC
      56             :    sets exposed while producing FD_SHRED_BATCH_FEC_SETS_MAX more FEC
      57             :    sets, so the leader pipeline section of the dcache needs at least
      58             :    d+FD_SHRED_BATCH_FEC_SETS_MAX entries.
      59             : 
      60             :    From the network: The FEC resolver doesn't use a cyclic order, but it
      61             :    does promise that once it returns an FEC set, it will return at least
      62             :    complete_depth FEC sets before returning it again.  This means we
      63             :    want at most complete_depth-1 FEC sets exposed, so
      64             :    complete_depth=d+1 FEC sets.  The FEC resolver has the
      65             :    ability to keep individual shreds for partial_depth calls, but
      66             :    because in this version of the shred tile, we send each shred to all
      67             :    its destinations as soon as we get it, we don't need that
      68             :    functionality, so we set partial_depth=1.
      69             : 
      70             :    Adding these up and plugging in the current value of
      71             :    BATCH_FEC_SETS_MAX, we get 2*d+6+fec_resolver_depth FEC sets.  The
      72             :    topology code doesn't allow specifying mcache depth and dcache depth
      73             :    independently.  That means we have to lie about the MTU and burst.
      74             :    We say the MTU is double what it actually is, and then the burst is
      75             :    4+fec_resolver_depth/2.  That means we get
      76             :    2*d+2*(4+fec_resolver_depth/2) >= 2*d+6+fec_resolver_depth FEC sets.
      77             : 
      78             :    A note on parallelization.  From the network, shreds are distributed
      79             :    to tiles based on a validator-specific seeded hash of (slot, FEC set
      80             :    index) so all the shreds for a given FEC set (and any equivocating
      81             :    FEC set) are processed by the same tile.  From the leader pipeline,
      82             :    the original implementation used to parallelize by batch of
      83             :    microblocks (so within a block, batches were distributed to different
      84             :    tiles).  To support chained merkle shreds, the current implementation
      85             :    processes all the batches on tile 0 -- this should be a temporary
      86             :    state while Solana moves to a newer shred format that support better
      87             :    parallelization. */
      88             : 
      89             : #define FD_SHRED_TILE_SCRATCH_ALIGN 128UL
      90             : 
      91           0 : #define IN_KIND_CONTACT ( 0UL)
      92           0 : #define IN_KIND_EPOCH   ( 1UL) /* Firedancer */
      93           0 : #define IN_KIND_STAKE   ( 2UL) /* Frankendancer */
      94           0 : #define IN_KIND_POH     ( 3UL)
      95           0 : #define IN_KIND_NET     ( 4UL)
      96           0 : #define IN_KIND_SIGN    ( 5UL)
      97             : #define IN_KIND_REPAIR  ( 6UL)
      98           0 : #define IN_KIND_IPECHO  ( 7UL)
      99           0 : #define IN_KIND_GOSSIP  ( 8UL)
     100           0 : #define IN_KIND_ROOTED  ( 9UL)
     101           0 : #define IN_KIND_ROOTEDH (10UL)
     102             : 
     103           0 : #define NET_OUT_IDX     1
     104           0 : #define SIGN_OUT_IDX    2
     105             : 
     106             : FD_STATIC_ASSERT( sizeof(fd_entry_batch_meta_t)==56UL,      poh_shred_mtu   );
     107             : FD_STATIC_ASSERT( sizeof(fd_fec_set_t)==FD_SHRED_STORE_MTU, shred_store_mtu );
     108             : 
     109           0 : #define FD_SHRED_ADD_SHRED_EXTRA_RETVAL_CNT 2
     110             : 
     111             : /* Number of entries in the block_ids table. Each entry is 32 byte.
     112             :    This table is used to keep track of block ids that we create
     113             :    when we're leader, so that we can access them whenever we need
     114             :    a *parent* block id for a new block. Larger table allows to
     115             :    retrieve older parent block ids. Currently it's set for worst
     116             :    case parent offset of USHORT_MAX (max allowed in a shred),
     117             :    making the total table 2MiB.
     118             :    See also comment on chained_merkle_root. */
     119           0 : #define BLOCK_IDS_TABLE_CNT USHORT_MAX
     120             : 
     121             : /* See note on parallelization above. Currently we process all batches in tile 0. */
     122             : #if 1
     123             : #define SHOULD_PROCESS_THESE_SHREDS ( ctx->round_robin_id==0 )
     124             : #else
     125             : #define SHOULD_PROCESS_THESE_SHREDS ( ctx->batch_cnt%ctx->round_robin_cnt==ctx->round_robin_id )
     126             : #endif
     127             : 
     128             : /* The behavior of the shred tile is slightly different for
     129             :    Frankendancer vs Firedancer.  For example, Frankendancer produces
     130             :    chained merkle shreds, while Firedancer doesn't yet.  We can check
     131             :    at runtime the difference by inspecting the topology. The simplest
     132             :    way is to test if ctx->store is initialized.
     133             : 
     134             :    FIXME don't assume only frank vs. fire */
     135             : #define IS_FIREDANCER ( ctx->store!=NULL )
     136             : 
     137             : typedef union {
     138             :   struct {
     139             :     fd_wksp_t * mem;
     140             :     ulong       chunk0;
     141             :     ulong       wmark;
     142             :   };
     143             :   fd_net_rx_bounds_t net_rx;
     144             : } fd_shred_in_ctx_t;
     145             : 
     146             : typedef struct {
     147             :   fd_shredder_t      * shredder;
     148             :   fd_fec_resolver_t  * resolver;
     149             :   ulong                shred_limit;
     150             :   fd_pubkey_t          identity_key[1]; /* Just the public key */
     151             : 
     152             :   ulong                round_robin_id;
     153             :   ulong                round_robin_cnt;
     154             :   /* Number of batches shredded from PoH during the current slot.
     155             :      This should be the same for all the shred tiles. */
     156             :   ulong                batch_cnt;
     157             :   /* Slot of the most recent microblock we've seen from PoH,
     158             :      or 0 if we haven't seen one yet */
     159             :   ulong                slot;
     160             : 
     161             :   fd_rnonce_ss_t       repair_nonce_ss[1];
     162             : 
     163             :   fd_keyswitch_t *     keyswitch;
     164             :   fd_keyguard_client_t keyguard_client[1];
     165             : 
     166             :   fd_fec_set_t       * fec_sets;
     167             : 
     168             :   fd_stake_ci_t      * stake_ci;
     169             :   /* These are used in between during_frag and after_frag */
     170             :   fd_shred_dest_weighted_t * new_dest_ptr;
     171             :   ulong                      new_dest_cnt;
     172             :   ulong                      shredded_txn_cnt;
     173             :   ulong                      new_root;
     174             : 
     175             :   ulong poh_in_expect_seq;
     176             : 
     177             :   ushort net_id;
     178             : 
     179             :   int skip_frag;
     180             : 
     181             :   ulong                    adtl_dests_leader_cnt;
     182             :   fd_shred_dest_weighted_t adtl_dests_leader    [ FD_TOPO_ADTL_DESTS_MAX ];
     183             :   ulong                    adtl_dests_retransmit_cnt;
     184             :   fd_shred_dest_weighted_t adtl_dests_retransmit[ FD_TOPO_ADTL_DESTS_MAX ];
     185             : 
     186             :   fd_ip4_udp_hdrs_t data_shred_net_hdr  [1];
     187             :   fd_ip4_udp_hdrs_t parity_shred_net_hdr[1];
     188             : 
     189             :   ulong shredder_fec_set_idx;     /* In [0, shredder_max_fec_set_idx) */
     190             :   ulong shredder_max_fec_set_idx; /* exclusive */
     191             : 
     192             :   uchar shredder_merkle_root[32];
     193             : 
     194             :   ulong send_fec_set_idx[ FD_SHRED_BATCH_FEC_SETS_MAX ];
     195             :   ulong send_fec_set_cnt;
     196             :   ulong tsorig;  /* timestamp of the last packet in compressed form */
     197             : 
     198             :   /* Includes Ethernet, IP, UDP headers */
     199             :   ulong shred_buffer_sz;
     200             :   uchar shred_buffer[ FD_NET_MTU ];
     201             : 
     202             :   /* These seeds get generated in privileged_init but used in
     203             :      unprivileged_init, so we store them here in between. */
     204             :   ulong resolver_seed;
     205             :   ulong shred_dest_seed;
     206             : 
     207             :   fd_shred_in_ctx_t in[ 32 ];
     208             :   int               in_kind[ 32 ];
     209             : 
     210             :   fd_wksp_t * net_out_mem;
     211             :   ulong       net_out_chunk0;
     212             :   ulong       net_out_wmark;
     213             :   ulong       net_out_chunk;
     214             : 
     215             :   ulong       store_out_idx;
     216             :   fd_wksp_t * store_out_mem;
     217             :   ulong       store_out_chunk0;
     218             :   ulong       store_out_wmark;
     219             :   ulong       store_out_chunk;
     220             : 
     221             :   /* This is the output link for shreds that is currently consumed by
     222             :      the repair and replay tile. */
     223             :   ulong       shred_out_idx;
     224             :   fd_wksp_t * shred_out_mem;
     225             :   ulong       shred_out_chunk0;
     226             :   ulong       shred_out_wmark;
     227             :   ulong       shred_out_chunk;
     228             : 
     229             :   fd_store_t * store;
     230             : 
     231             :   fd_gossip_update_message_t gossip_upd_buf[1];
     232             : 
     233             :   struct {
     234             :     fd_histf_t contact_info_cnt[ 1 ];
     235             :     fd_histf_t batch_sz[ 1 ];
     236             :     fd_histf_t batch_microblock_cnt[ 1 ];
     237             :     fd_histf_t shredding_timing[ 1 ];
     238             :     fd_histf_t add_shred_timing[ 1 ];
     239             :     ulong shred_processing_result[ FD_FEC_RESOLVER_ADD_SHRED_RETVAL_CNT+FD_SHRED_ADD_SHRED_EXTRA_RETVAL_CNT ];
     240             :     ulong invalid_block_id_cnt;
     241             :     ulong shred_rejected_unchained_cnt;
     242             :     ulong repair_rcv_cnt;
     243             :     ulong repair_rcv_bytes;
     244             :     ulong turbine_rcv_cnt;
     245             :     ulong turbine_rcv_bytes;
     246             :     ulong bad_nonce;
     247             :   } metrics[ 1 ];
     248             : 
     249             :   struct {
     250             :     ulong txn_cnt;
     251             :     ulong pos; /* in payload, range [0, FD_SHRED_BATCH_RAW_BUF_SZ-8UL) */
     252             :     ulong slot; /* set to 0 when pos==0 */
     253             :     union {
     254             :       struct {
     255             :         ulong microblock_cnt;
     256             :         uchar payload[ FD_SHRED_BATCH_RAW_BUF_SZ - 8UL ];
     257             :       };
     258             :       uchar raw[ FD_SHRED_BATCH_RAW_BUF_SZ ];
     259             :     };
     260             :   } pending_batch;
     261             : 
     262             :   fd_epoch_schedule_t            epoch_schedule[1];
     263             :   fd_shred_features_activation_t features_activation[1];
     264             : 
     265             :   /* max_shred_idx is the exclusive upper bound for shred
     266             :      indices. We need to reject any shred with an
     267             :      index >= current_max_shred_idx, but we also want to reject anything
     268             :      that is part of an FEC set whose highest shred index would reach
     269             :      the bound.
     270             : 
     271             :      Because this bound can change with feature gates, for example the
     272             :      reduce_slot_time feature gates, we store the bound for the
     273             :      previous, current, and next regimes, along with the slots at which
     274             :      the current and next bounds take effect. This lets us apply, to
     275             :      each shred, the limit effective at that shreds slot.
     276             : 
     277             :      For a given shred slot, the max_shred_idx for that shred is:
     278             :       next_max_shred_idx_start_slot    <= slot                                    -> next_max_shred_idx
     279             :       current_max_shred_idx_start_slot <= slot < next_max_shred_idx_start_slot    -> current_max_shred_idx
     280             :                                           slot < current_max_shred_idx_start_slot -> prev_max_shred_idx
     281             :   */
     282             :   ulong                          prev_max_shred_idx;
     283             :   ulong                          current_max_shred_idx;
     284             :   ulong                          next_max_shred_idx;
     285             :   ulong                          current_max_shred_idx_start_slot;
     286             :   ulong                          next_max_shred_idx_start_slot;
     287             :   int                            larger_shred_limits_per_block;
     288             :   /* too large to be left in the stack */
     289             :   fd_shred_dest_idx_t scratchpad_dests[ FD_SHRED_DEST_MAX_FANOUT*(FD_REEDSOL_DATA_SHREDS_MAX+FD_REEDSOL_PARITY_SHREDS_MAX) ];
     290             : 
     291             :   uchar * chained_merkle_root;
     292             :   fd_bmtree_node_t out_merkle_roots[ FD_SHRED_BATCH_FEC_SETS_MAX ];
     293             :   uchar block_ids[ BLOCK_IDS_TABLE_CNT ][ FD_SHRED_MERKLE_ROOT_SZ ];
     294             : 
     295             :   /* Bank object that we receive from the PoH tile and pass on to
     296             :      the store tile for setting the block_id of a slot. */
     297             :   void const * leader_bank;
     298             : } fd_shred_ctx_t;
     299             : 
     300             : /* shred features are generally considered active at the epoch *following*
     301             :    the epoch in which the feature gate is activated.
     302             : 
     303             :    As an optimization, when the activation slot is received, it is converted
     304             :    into the first slot of the subsequent epoch.  This allows for a more
     305             :    efficient check (shred_slot >= feature_slot) and avoids the overhead of
     306             :    repeatedly converting slots into epochs for comparison.
     307             : 
     308             :    This function is only for Firedancer, while Frankendancer already receives
     309             :    the final activation slot from POH tile.
     310             : 
     311             :    In Agave, this is done with check_feature_activation():
     312             :    https://github.com/anza-xyz/agave/blob/v3.1.4/turbine/src/cluster_nodes.rs#L771
     313             :    https://github.com/anza-xyz/agave/blob/v3.1.4/core/src/shred_fetch_stage.rs#L456 */
     314             : static inline ulong
     315           0 : fd_shred_get_feature_activation_slot0( ulong feature_slot, fd_shred_ctx_t * ctx ) {
     316             :   /* if the feature does not have an activation slot yet, return ULONG_MAX */
     317           0 :   if( FD_UNLIKELY( feature_slot==ULONG_MAX ) ) {
     318           0 :     return ULONG_MAX;
     319           0 :   }
     320             :   /* if we don't have an epoch schedule yet, return ULONG_MAX */
     321           0 :   if( FD_UNLIKELY( ctx->epoch_schedule->slots_per_epoch==0 ) ) {
     322           0 :     return ULONG_MAX;
     323           0 :   }
     324             :   /* compute the activation epoch, add one, return the first slot. */
     325           0 :   ulong feature_epoch = 1 + fd_slot_to_epoch( ctx->epoch_schedule, feature_slot, NULL );
     326           0 :   return fd_epoch_slot0( ctx->epoch_schedule, feature_epoch );
     327           0 : }
     328             : 
     329             : FD_FN_CONST static inline ulong
     330           0 : scratch_align( void ) {
     331           0 :   return 128UL;
     332           0 : }
     333             : 
     334             : FD_FN_PURE static inline ulong
     335           0 : scratch_footprint( fd_topo_tile_t const * tile ) {
     336             : 
     337           0 :   ulong fec_resolver_footprint = fd_fec_resolver_footprint( tile->shred.fec_resolver_depth, 1UL, tile->shred.depth+1UL,
     338           0 :                                                             128UL * tile->shred.fec_resolver_depth );
     339           0 :   ulong l = FD_LAYOUT_INIT;
     340           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_shred_ctx_t),          sizeof(fd_shred_ctx_t)                  );
     341           0 :   l = FD_LAYOUT_APPEND( l, fd_stake_ci_align(),              fd_stake_ci_footprint()                 );
     342           0 :   l = FD_LAYOUT_APPEND( l, fd_fec_resolver_align(),          fec_resolver_footprint                  );
     343           0 :   l = FD_LAYOUT_APPEND( l, fd_shredder_align(),              fd_shredder_footprint()                 );
     344           0 :   return FD_LAYOUT_FINI( l, scratch_align() );
     345           0 : }
     346             : 
     347             : static inline void
     348           0 : during_housekeeping( fd_shred_ctx_t * ctx ) {
     349           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
     350           0 :     ulong seq_must_complete = ctx->keyswitch->param;
     351             : 
     352           0 :     if( FD_UNLIKELY( fd_seq_lt( ctx->poh_in_expect_seq, seq_must_complete ) ) ) {
     353             :       /* See fd_keyswitch.h, we need to flush any in-flight shreds from
     354             :          the leader pipeline before switching key. */
     355           0 :       FD_LOG_WARNING(( "Flushing in-flight unpublished shreds, must reach seq %lu, currently at %lu ...", seq_must_complete, ctx->poh_in_expect_seq ));
     356           0 :       return;
     357           0 :     }
     358             : 
     359           0 :     memcpy( ctx->identity_key->uc, ctx->keyswitch->bytes, 32UL );
     360           0 :     fd_stake_ci_set_identity( ctx->stake_ci, ctx->identity_key );
     361           0 :     fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
     362           0 :   }
     363           0 : }
     364             : 
     365             : static inline void
     366           0 : metrics_write( fd_shred_ctx_t * ctx ) {
     367           0 :   FD_MHIST_COPY( SHRED, CONTACT_INFO_PER_MESSAGE,   ctx->metrics->contact_info_cnt             );
     368           0 :   FD_MHIST_COPY( SHRED, BATCH_SIZE_BYTES,           ctx->metrics->batch_sz                     );
     369           0 :   FD_MHIST_COPY( SHRED, MICROBLOCK_PER_BATCH,       ctx->metrics->batch_microblock_cnt         );
     370           0 :   FD_MHIST_COPY( SHRED, SHREDDING_DURATION_SECONDS, ctx->metrics->shredding_timing             );
     371           0 :   FD_MHIST_COPY( SHRED, ADD_SHRED_DURATION_SECONDS, ctx->metrics->add_shred_timing             );
     372           0 :   FD_MCNT_SET  ( SHRED, SHRED_REPAIR_RX,            ctx->metrics->repair_rcv_cnt               );
     373           0 :   FD_MCNT_SET  ( SHRED, SHRED_REPAIR_RX_BYTES,      ctx->metrics->repair_rcv_bytes             );
     374           0 :   FD_MCNT_SET  ( SHRED, SHRED_TURBINE_RX,           ctx->metrics->turbine_rcv_cnt              );
     375           0 :   FD_MCNT_SET  ( SHRED, SHRED_TURBINE_RX_BYTES,     ctx->metrics->turbine_rcv_bytes            );
     376           0 :   FD_MCNT_SET  ( SHRED, NONCE_INVALID,              ctx->metrics->bad_nonce                    );
     377             : 
     378           0 :   FD_MCNT_SET  ( SHRED, BLOCK_ID_INVALID,           ctx->metrics->invalid_block_id_cnt         );
     379           0 :   FD_MCNT_SET  ( SHRED, SHRED_UNCHAINED_REJECTED,   ctx->metrics->shred_rejected_unchained_cnt );
     380             : 
     381           0 :   FD_MCNT_ENUM_COPY( SHRED, SHRED_PROCESSED, ctx->metrics->shred_processing_result             );
     382           0 : }
     383             : 
     384             : static inline void
     385             : handle_new_cluster_contact_info( fd_shred_ctx_t * ctx,
     386           0 :                                  uchar const    * buf ) {
     387           0 :   ulong const * header = (ulong const *)fd_type_pun_const( buf );
     388             : 
     389           0 :   ulong dest_cnt = header[ 0 ];
     390           0 :   fd_histf_sample( ctx->metrics->contact_info_cnt, dest_cnt );
     391             : 
     392           0 :   if( dest_cnt >= MAX_SHRED_DESTS )
     393           0 :     FD_LOG_ERR(( "Cluster nodes had %lu destinations, which was more than the max of %lu", dest_cnt, MAX_SHRED_DESTS ));
     394             : 
     395           0 :   fd_shred_dest_wire_t const * in_dests = fd_type_pun_const( header+1UL );
     396           0 :   fd_shred_dest_weighted_t * dests = fd_stake_ci_dest_add_init( ctx->stake_ci );
     397             : 
     398           0 :   ctx->new_dest_ptr = dests;
     399           0 :   ctx->new_dest_cnt = dest_cnt;
     400             : 
     401           0 :   for( ulong i=0UL; i<dest_cnt; i++ ) {
     402           0 :     memcpy( dests[i].pubkey.uc, in_dests[i].pubkey, 32UL );
     403           0 :     dests[i].ip4  = in_dests[i].ip4_addr;
     404           0 :     dests[i].port = in_dests[i].udp_port;
     405           0 :   }
     406           0 : }
     407             : 
     408             : static inline void
     409           0 : finalize_new_cluster_contact_info( fd_shred_ctx_t * ctx ) {
     410           0 :   fd_stake_ci_dest_add_fini( ctx->stake_ci, ctx->new_dest_cnt );
     411           0 : }
     412             : 
     413             : static inline int
     414             : before_frag( fd_shred_ctx_t * ctx,
     415             :              ulong            in_idx,
     416             :              ulong            seq,
     417           0 :              ulong            sig ) {
     418           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_IPECHO ) ) {
     419           0 :     FD_TEST( sig!=0UL && sig<=USHORT_MAX );
     420           0 :     fd_shredder_set_shred_version    ( ctx->shredder, (ushort)sig );
     421           0 :     fd_fec_resolver_set_shred_version( ctx->resolver, (ushort)sig );
     422           0 :     return 1;
     423           0 :   }
     424             : 
     425           0 :   if( FD_UNLIKELY( !ctx->shredder->shred_version ) ) return -1;
     426             : 
     427           0 :   if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_POH ) ) {
     428           0 :     ctx->poh_in_expect_seq = seq+1UL;
     429           0 :     return (int)(fd_disco_poh_sig_pkt_type( sig )!=POH_PKT_TYPE_MICROBLOCK) &
     430           0 :            (int)(fd_disco_poh_sig_pkt_type( sig )!=POH_PKT_TYPE_SHRED_EPOCH_MSG) &
     431           0 :            (int)(fd_disco_poh_sig_pkt_type( sig )!=POH_PKT_TYPE_LEADER_BANK);
     432           0 :   }
     433           0 :   if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_NET ) ) {
     434           0 :     return (int)(fd_disco_netmux_sig_proto( sig )!=DST_PROTO_SHRED) & (int)(fd_disco_netmux_sig_proto( sig )!=DST_PROTO_REPAIR);
     435           0 :   }
     436           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP ) ){
     437           0 :     return sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO &&
     438           0 :            sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE;
     439           0 :   }
     440           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_ROOTEDH ) ) {
     441           0 :     return sig!=0UL; /* only care about rooted banks, not completed blockhash */
     442           0 :   }
     443           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_ROOTED ) ) {
     444           0 :     return sig!=FD_TOWER_SIG_SLOT_ROOTED; /* only care about slot_confirmed messages */
     445           0 :   }
     446           0 :   return 0;
     447           0 : }
     448             : 
     449             : static void
     450             : during_frag( fd_shred_ctx_t * ctx,
     451             :              ulong            in_idx,
     452             :              ulong            seq FD_PARAM_UNUSED,
     453             :              ulong            sig,
     454             :              ulong            chunk,
     455             :              ulong            sz,
     456           0 :              ulong            ctl ) {
     457             : 
     458           0 :   ctx->skip_frag = 0;
     459             : 
     460           0 :   ctx->tsorig = fd_frag_meta_ts_comp( fd_tickcount() );
     461             : 
     462           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_REPAIR ) ) {
     463           0 :     if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>FD_NET_MTU ) )
     464           0 :     FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz,
     465           0 :                 ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
     466             : 
     467           0 :     uchar const * dcache_entry = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     468           0 :     fd_memcpy( ctx->shred_buffer, dcache_entry, sz );
     469           0 :     return;
     470           0 :   }
     471             : 
     472           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_CONTACT ) ) {
     473           0 :     if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark ) )
     474           0 :       FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz,
     475           0 :                    ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
     476             : 
     477           0 :     uchar const * dcache_entry = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     478           0 :     handle_new_cluster_contact_info( ctx, dcache_entry );
     479           0 :     return;
     480           0 :   }
     481             : 
     482           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP ) ) {
     483           0 :     if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>sizeof(fd_gossip_update_message_t) ) )
     484           0 :       FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz,
     485           0 :                    ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
     486           0 :     uchar const * gossip_upd_msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     487           0 :     fd_memcpy( ctx->gossip_upd_buf, gossip_upd_msg, sz );
     488           0 :     return;
     489           0 :   }
     490             : 
     491             :   /* Firedancer only */
     492           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_ROOTED ) ) {
     493           0 :     if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz<sizeof(fd_tower_slot_rooted_t) ) )
     494           0 :       FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz,
     495           0 :                    ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
     496           0 :     fd_tower_slot_rooted_t const * rooted_msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     497           0 :     ctx->new_root = rooted_msg->slot;
     498           0 :     return;
     499           0 :   }
     500             : 
     501             :   /* Frankendancer only */
     502           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_ROOTEDH ) ) {
     503           0 :     if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz<sizeof(fd_rooted_bank_t) ) )
     504           0 :       FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz,
     505           0 :                    ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
     506             :     /* The message format is a pointer to the bank (which is in the
     507             :        agave address space, so we couldn't access it even if we wanted
     508             :        to) followed by the rooted slot. */
     509           0 :     ulong const * replay_msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     510           0 :     ctx->new_root = replay_msg[ 1 ];
     511           0 :     return;
     512           0 :   }
     513             : 
     514             :   /* Firedancer only */
     515           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_EPOCH ) ) {
     516           0 :     if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark ) )
     517           0 :       FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz,
     518           0 :                    ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
     519             : 
     520           0 :     uchar const *               dcache_entry = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     521           0 :     fd_epoch_info_msg_t const * epoch_msg    = fd_type_pun_const( dcache_entry );
     522             : 
     523           0 :     FD_TEST( epoch_msg->staked_vote_cnt<=MAX_STAKE_WEIGHTS );
     524           0 :     FD_TEST( epoch_msg->staked_id_cnt<=MAX_STAKE_WEIGHTS );
     525             : 
     526           0 :     fd_stake_ci_epoch_msg_init( ctx->stake_ci, epoch_msg );
     527             : 
     528           0 :     *ctx->epoch_schedule = epoch_msg->epoch_schedule;
     529             : 
     530           0 :     if( FD_LIKELY( !ctx->larger_shred_limits_per_block ) ) {
     531           0 :       fd_slot_params_t slot_params = fd_slot_params_lookup( &FD_SLOT_PARAMS_400MS,
     532           0 :                                                             &epoch_msg->features,
     533           0 :                                                             &epoch_msg->epoch_schedule,
     534           0 :                                                             epoch_msg->start_slot );
     535             : 
     536           0 :       ctx->current_max_shred_idx            = slot_params.max_shred_idx;
     537           0 :       ctx->current_max_shred_idx_start_slot = fd_slot_params_effective_slot( &slot_params,
     538           0 :                                                                              &epoch_msg->features,
     539           0 :                                                                              &epoch_msg->epoch_schedule );
     540           0 :       ctx->next_max_shred_idx_start_slot    = fd_slot_params_next_effective_slot( &slot_params,
     541           0 :                                                                                   &epoch_msg->features,
     542           0 :                                                                                   &epoch_msg->epoch_schedule );
     543           0 :       ctx->prev_max_shred_idx               = fd_slot_params_lookup( &FD_SLOT_PARAMS_400MS,
     544           0 :                                                                      &epoch_msg->features,
     545           0 :                                                                      &epoch_msg->epoch_schedule,
     546           0 :                                                                      fd_ulong_sat_sub( ctx->current_max_shred_idx_start_slot, 1UL ) ).max_shred_idx;
     547           0 :       ctx->next_max_shred_idx               = fd_slot_params_lookup( &FD_SLOT_PARAMS_400MS,
     548           0 :                                                                      &epoch_msg->features,
     549           0 :                                                                      &epoch_msg->epoch_schedule,
     550           0 :                                                                      ctx->next_max_shred_idx_start_slot ).max_shred_idx;
     551           0 :     }
     552           0 :     ctx->features_activation->enforce_fixed_fec_set = fd_shred_get_feature_activation_slot0(
     553           0 :       epoch_msg->features.enforce_fixed_fec_set, ctx );
     554             : 
     555           0 :     return;
     556           0 :   }
     557             : 
     558           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_STAKE ) ) {
     559           0 :     if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark ) )
     560           0 :       FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz,
     561           0 :                    ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
     562             : 
     563           0 :     uchar const * dcache_entry = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     564           0 :     fd_stake_ci_stake_msg_init( ctx->stake_ci, fd_type_pun_const( dcache_entry ) );
     565           0 :     return;
     566           0 :   }
     567             : 
     568           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_POH ) ) {
     569           0 :     ctx->send_fec_set_cnt = 0UL;
     570             : 
     571           0 :     if( FD_UNLIKELY( fd_disco_poh_sig_pkt_type( sig )==POH_PKT_TYPE_LEADER_BANK ) ) {
     572             :       /* Only one tile needs to act on this. Other tiles see the frag but
     573             :          ignore it. */
     574           0 :       if( ctx->round_robin_id!=0UL ) return;
     575             :       /* poh is handing off a leader bank pointer for a slot we may
     576             :          be leader for.  Copy the pointer out of the dcache for
     577             :          after_frag to attach to the FEC sets for this slot. */
     578           0 :       if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz!=sizeof(void const *) ) )
     579           0 :         FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz,
     580           0 :               ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
     581             : 
     582           0 :       void const * const * src = (void const * const *)fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     583           0 :       ctx->leader_bank = *src;
     584           0 :       return;
     585           0 :     }
     586             : 
     587           0 :     if( FD_UNLIKELY( (fd_disco_poh_sig_pkt_type( sig )==POH_PKT_TYPE_SHRED_EPOCH_MSG) ) ) {
     588             :       /* There is a subset of FD_SHRED_FEATURES_ACTIVATION_... slots
     589             :           that the shred tile needs to be aware of, as well as
     590             :           shred limits that can change at epoch boundaries. Since these
     591             :           require the bank, we are forced (so far) to receive them from
     592             :           the poh tile (as a POH_PKT_TYPE_SHRED_EPOCH_MSG). */
     593           0 :       uchar const * dcache_entry = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     594           0 :       if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz!=(sizeof(fd_shred_epoch_msg_t)) ) )
     595           0 :         FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz,
     596           0 :               ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
     597             : 
     598           0 :       fd_shred_epoch_msg_t const * msg = (fd_shred_epoch_msg_t const *)dcache_entry;
     599             : 
     600           0 :       *ctx->features_activation = msg->features_activation;
     601             : 
     602           0 :       if( FD_LIKELY( !ctx->larger_shred_limits_per_block ) ) {
     603           0 :         fd_shred_slot_limits_t const * lim    = &msg->slot_limits;
     604           0 :         ctx->prev_max_shred_idx               = lim->prev_max_shred_idx;
     605           0 :         ctx->current_max_shred_idx            = lim->current_max_shred_idx;
     606           0 :         ctx->next_max_shred_idx               = lim->next_max_shred_idx;
     607           0 :         ctx->current_max_shred_idx_start_slot = lim->current_start_slot;
     608           0 :         ctx->next_max_shred_idx_start_slot    = lim->next_start_slot;
     609           0 :       }
     610           0 :     }
     611           0 :     else { /* (fd_disco_poh_sig_pkt_type( sig )==POH_PKT_TYPE_MICROBLOCK) */
     612             :       /* This is a frag from the PoH tile.  We'll copy it to our pending
     613             :         microblock batch and shred it if necessary (last in block or
     614             :         above watermark).  We just go ahead and shred it here, even
     615             :         though we may get overrun.  If we do end up getting overrun, we
     616             :         just won't send these shreds out and we'll reuse the FEC set for
     617             :         the next one.  From a higher level though, if we do get overrun,
     618             :         a bunch of shreds will never be transmitted, and we'll end up
     619             :         producing a block that never lands on chain. */
     620             : 
     621           0 :       uchar const * dcache_entry = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
     622           0 :       if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>FD_POH_SHRED_MTU ||
     623           0 :           sz<(sizeof(fd_entry_batch_meta_t)+sizeof(fd_entry_batch_header_t)) ) )
     624           0 :         FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz,
     625           0 :               ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
     626             : 
     627           0 :       fd_entry_batch_meta_t const * entry_meta = (fd_entry_batch_meta_t const *)dcache_entry;
     628           0 :       uchar const *                 entry      = dcache_entry + sizeof(fd_entry_batch_meta_t);
     629           0 :       ulong                         entry_sz   = sz           - sizeof(fd_entry_batch_meta_t);
     630             : 
     631           0 :       fd_entry_batch_header_t const * microblock = (fd_entry_batch_header_t const *)entry;
     632             : 
     633             :       /* It should never be possible for this to fail, but we check it
     634             :         anyway. */
     635           0 :       FD_TEST( entry_sz + ctx->pending_batch.pos <= sizeof(ctx->pending_batch.payload) );
     636             : 
     637           0 :       ulong target_slot = fd_disco_poh_sig_slot( sig );
     638           0 :       if( FD_UNLIKELY( (ctx->pending_batch.microblock_cnt>0) & (ctx->pending_batch.slot!=target_slot) ) ) {
     639             :         /* TODO: The Agave client sends a dummy entry batch with only 1
     640             :           byte and the block-complete bit set.  This helps other
     641             :           validators know that the block is dead and they should not try
     642             :           to continue building a fork on it.  We probably want a similar
     643             :           approach eventually. */
     644           0 :         FD_LOG_WARNING(( "Abandoning %lu microblocks for slot %lu and switching to slot %lu",
     645           0 :               ctx->pending_batch.microblock_cnt, ctx->pending_batch.slot, target_slot ));
     646           0 :         ctx->pending_batch.slot           = 0UL;
     647           0 :         ctx->pending_batch.pos            = 0UL;
     648           0 :         ctx->pending_batch.microblock_cnt = 0UL;
     649           0 :         ctx->pending_batch.txn_cnt        = 0UL;
     650           0 :         ctx->batch_cnt                    = 0UL;
     651             : 
     652           0 :         FD_MCNT_INC( SHRED, MICROBLOCK_ABANDONED, 1UL );
     653           0 :       }
     654             : 
     655           0 :       ctx->pending_batch.slot = target_slot;
     656             :       /* We want to send out some shreds immediately when we start a new
     657             :          slot to help with leader targeting. */
     658           0 :       int new_slot = 0;
     659           0 :       if( FD_UNLIKELY( target_slot!=ctx->slot )) {
     660             :         /* Reset batch count if we are in a new slot */
     661           0 :         ctx->batch_cnt = 0UL;
     662           0 :         ctx->slot      = target_slot;
     663           0 :         new_slot       = 1;
     664             : 
     665             :         /* At the beginning of a new slot, prepare chained_merkle_root.
     666             :            chained_merkle_root is initialized at the block_id of the parent
     667             :            block, there's two cases:
     668             : 
     669             :            1. block_id is passed in by the poh tile:
     670             :               - it's always passed when parent block had a different leader
     671             :               - it may be passed when we were leader for parent block (there
     672             :                 are race conditions when it's not passed)
     673             : 
     674             :            2. block_id is taken from block_ids table if we were the leader
     675             :               for the parent block (when we were NOT the leader, because of
     676             :               equivocation, we can't store block_id in the table)
     677             : 
     678             :            chained_merkle_root is stored in block_ids table at target_slot
     679             :            and it's progressively updated as more microblocks are received.
     680             :            As a result, when we move to a new slot, the block_ids table at
     681             :            the old slot will contain the block_id.
     682             : 
     683             :            The block_ids table is designed to protect against the race condition
     684             :            case in 1., therefore the table may not be set in some cases, e.g. if
     685             :            a validator (re)starts, but in those cases we don't expect the race
     686             :            condition to apply. */
     687           0 :         ctx->chained_merkle_root = ctx->block_ids[ target_slot % BLOCK_IDS_TABLE_CNT ];
     688           0 :         if( FD_UNLIKELY( SHOULD_PROCESS_THESE_SHREDS ) ) {
     689           0 :           if( FD_LIKELY( entry_meta->parent_block_id_valid ) ) {
     690             :             /* 1. Initialize chained_merkle_root sent from poh tile */
     691           0 :             memcpy( ctx->chained_merkle_root, entry_meta->parent_block_id, FD_SHRED_MERKLE_ROOT_SZ );
     692           0 :           } else {
     693           0 :             ulong parent_slot = target_slot - entry_meta->parent_offset;
     694           0 :             fd_epoch_leaders_t const * lsched = fd_stake_ci_get_lsched_for_slot( ctx->stake_ci, parent_slot );
     695           0 :             fd_pubkey_t const * slot_leader = fd_epoch_leaders_get( lsched, parent_slot );
     696             : 
     697           0 :             if( lsched && slot_leader && fd_memeq( slot_leader, ctx->identity_key, sizeof(fd_pubkey_t) ) ) {
     698             :               /* 2. Initialize chained_merkle_root from block_ids table, if we were the leader */
     699           0 :               memcpy( ctx->chained_merkle_root, ctx->block_ids[ parent_slot % BLOCK_IDS_TABLE_CNT ], FD_SHRED_MERKLE_ROOT_SZ );
     700           0 :             } else {
     701             :               /* This should never happen, log a metric and set chained_merkle_root to 0 */
     702           0 :               ctx->metrics->invalid_block_id_cnt++;
     703           0 :               memset( ctx->chained_merkle_root, 0, FD_SHRED_MERKLE_ROOT_SZ );
     704           0 :             }
     705           0 :           }
     706           0 :         }
     707           0 :       }
     708             : 
     709           0 :       if( FD_LIKELY( !SHOULD_PROCESS_THESE_SHREDS ) ) {
     710             :         /* If we are not processing this batch, filter in after_frag. */
     711           0 :         ctx->skip_frag = 1;
     712           0 :       }
     713             : 
     714           0 :       ulong   pending_batch_wmark = FD_SHRED_BATCH_WMARK_CHAINED;
     715           0 :       uchar * chained_merkle_root = ctx->chained_merkle_root;
     716           0 :       ulong   load_for_32_shreds  = FD_SHREDDER_CHAINED_FEC_SET_PAYLOAD_SZ;
     717             :       /* All fec sets in the last batch of a block need to be resigned.
     718             :          This needs to match Agave's behavior - as a reference, see:
     719             :          https://github.com/anza-xyz/agave/blob/v2.3/ledger/src/shred/merkle.rs#L1040 */
     720           0 :       if( FD_UNLIKELY( entry_meta->block_complete ) ) {
     721           0 :         pending_batch_wmark = FD_SHRED_BATCH_WMARK_RESIGNED;
     722             :         /* chained_merkle_root also applies to resigned FEC sets. */
     723           0 :         load_for_32_shreds = FD_SHREDDER_RESIGNED_FEC_SET_PAYLOAD_SZ;
     724           0 :       }
     725             : 
     726             :       /* If this microblock completes the block, the batch is then
     727             :          finalized here.  Otherwise, we check whether the new entry
     728             :          would exceed the pending_batch_wmark.  If true, then the
     729             :          batch is closed now, shredded, and a new batch is started
     730             :          with the incoming microblock.  If false, no shredding takes
     731             :          place, and the microblock is added to the current batch.
     732             :          Pack limits entry bytes so this batching cannot exceed
     733             :          max_shred_idx. */
     734           0 :       int forced_end_batch         = entry_meta->block_complete | new_slot;
     735           0 :       int batch_would_exceed_wmark = ( ctx->pending_batch.pos + entry_sz ) > pending_batch_wmark;
     736           0 :       int include_in_current_batch = forced_end_batch | ( !batch_would_exceed_wmark );
     737           0 :       int process_current_batch    = forced_end_batch | batch_would_exceed_wmark;
     738           0 :       int init_new_batch           = !include_in_current_batch;
     739             : 
     740           0 :       if( FD_LIKELY( include_in_current_batch ) ) {
     741           0 :         if( FD_UNLIKELY( SHOULD_PROCESS_THESE_SHREDS ) ) {
     742             :           /* Ugh, yet another memcpy */
     743           0 :           fd_memcpy( ctx->pending_batch.payload + ctx->pending_batch.pos, entry, entry_sz );
     744           0 :         }
     745           0 :         ctx->pending_batch.pos            += entry_sz;
     746           0 :         ctx->pending_batch.microblock_cnt += 1UL;
     747           0 :         ctx->pending_batch.txn_cnt        += microblock->txn_cnt;
     748           0 :       }
     749             : 
     750           0 :       if( FD_LIKELY( process_current_batch )) {
     751             :         /* Batch and padding size calculation. */
     752           0 :         ulong batch_sz        = sizeof(ulong) + ctx->pending_batch.pos; /* without padding */
     753           0 :         ulong batch_sz_padded = load_for_32_shreds * ( ( batch_sz + load_for_32_shreds - 1UL ) / load_for_32_shreds );
     754           0 :         ulong padding_sz      = batch_sz_padded - batch_sz;
     755             : 
     756           0 :         if( FD_UNLIKELY( SHOULD_PROCESS_THESE_SHREDS ) ) {
     757             :           /* If it's our turn, shred this batch. FD_UNLIKELY because shred
     758             :              tile cnt generally >= 2 */
     759             : 
     760           0 :           long shredding_timing = -fd_tickcount();
     761             : 
     762           0 :           fd_memset( ctx->pending_batch.payload + ctx->pending_batch.pos, 0, padding_sz );
     763             : 
     764           0 :           ctx->send_fec_set_cnt = 0UL; /* verbose */
     765           0 :           ctx->shredded_txn_cnt = ctx->pending_batch.txn_cnt;
     766             : 
     767           0 :           fd_shredder_init_batch( ctx->shredder, ctx->pending_batch.raw, batch_sz_padded, target_slot, entry_meta );
     768             : 
     769           0 :           ulong pend_sz  = batch_sz_padded;
     770           0 :           ulong pend_idx = 0;
     771           0 :           while( pend_sz > 0UL ) {
     772             : 
     773           0 :             fd_fec_set_t * out = ctx->fec_sets + ctx->shredder_fec_set_idx;
     774             : 
     775           0 :             FD_TEST( fd_shredder_next_fec_set( ctx->shredder, out, chained_merkle_root ) );
     776           0 :             memcpy( ctx->out_merkle_roots[pend_idx].hash, chained_merkle_root, 32UL );
     777             : 
     778           0 :             out->data_shred_rcvd   = 0U;
     779           0 :             out->parity_shred_rcvd = 0U;
     780             : 
     781           0 :             ctx->send_fec_set_idx[ ctx->send_fec_set_cnt ] = ctx->shredder_fec_set_idx;
     782           0 :             ctx->send_fec_set_cnt += 1UL;
     783           0 :             ctx->shredder_fec_set_idx = (ctx->shredder_fec_set_idx+1UL)%ctx->shredder_max_fec_set_idx;
     784             : 
     785           0 :             pend_sz -= load_for_32_shreds;
     786           0 :             pend_idx++;
     787           0 :           }
     788             : 
     789           0 :           fd_shredder_fini_batch( ctx->shredder );
     790           0 :           shredding_timing += fd_tickcount();
     791             : 
     792             :           /* Update metrics */
     793           0 :           fd_histf_sample( ctx->metrics->batch_sz,             batch_sz /* without padding */    );
     794           0 :           fd_histf_sample( ctx->metrics->batch_microblock_cnt, ctx->pending_batch.microblock_cnt );
     795           0 :           fd_histf_sample( ctx->metrics->shredding_timing,     (ulong)shredding_timing           );
     796           0 :         } else {
     797           0 :           ctx->send_fec_set_cnt = 0UL; /* verbose */
     798             : 
     799           0 :           fd_shredder_skip_batch( ctx->shredder, batch_sz_padded, target_slot, entry_meta->block_complete );
     800           0 :         }
     801             : 
     802           0 :         ctx->pending_batch.slot           = 0UL;
     803           0 :         ctx->pending_batch.pos            = 0UL;
     804           0 :         ctx->pending_batch.microblock_cnt = 0UL;
     805           0 :         ctx->pending_batch.txn_cnt        = 0UL;
     806           0 :         ctx->batch_cnt++;
     807           0 :       }
     808             : 
     809           0 :       if( FD_UNLIKELY( init_new_batch ) ) {
     810             :         /* TODO: this assumes that SHOULD_PROCESS_THESE_SHREDS is
     811             :            constant across batches.  Otherwise, the condition may
     812             :            need to be removed (or adjusted). */
     813           0 :         if( FD_UNLIKELY( SHOULD_PROCESS_THESE_SHREDS ) ) {
     814             :           /* Ugh, yet another memcpy */
     815           0 :           fd_memcpy( ctx->pending_batch.payload + 0UL /* verbose */, entry, entry_sz );
     816           0 :         }
     817           0 :         ctx->pending_batch.slot           = target_slot;
     818           0 :         ctx->pending_batch.pos            = entry_sz;
     819           0 :         ctx->pending_batch.microblock_cnt = 1UL;
     820           0 :         ctx->pending_batch.txn_cnt        = microblock->txn_cnt;
     821           0 :       }
     822           0 :     }
     823           0 :   } else if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_NET ) ) {
     824             :     /* The common case, from the net tile.  The FEC resolver API does
     825             :        not present a prepare/commit model. If we get overrun between
     826             :        when the FEC resolver verifies the signature and when it stores
     827             :        the local copy, we could end up storing and retransmitting
     828             :        garbage.  Instead we copy it locally, sadly, and only give it to
     829             :        the FEC resolver when we know it won't be overrun anymore. */
     830           0 :     uchar const * dcache_entry = fd_net_rx_translate_frag( &ctx->in[ in_idx ].net_rx, chunk, ctl, sz );
     831           0 :     ulong hdr_sz = fd_disco_netmux_sig_hdr_sz( sig );
     832           0 :     FD_TEST( hdr_sz <= sz ); /* Should be ensured by the net tile */
     833             :     /* Use the looser ctx->shred_limit as the max_shred_idx for this
     834             :        parse, as we do not have the slot for the shred yet. The
     835             :        tighter shred-specific max_shred_idx will be applied in the
     836             :        parse in after_frag. */
     837           0 :     fd_shred_t const * shred = fd_shred_parse( dcache_entry+hdr_sz, sz-hdr_sz, ctx->shred_limit );
     838           0 :     if( FD_UNLIKELY( !shred ) ) {
     839           0 :       ctx->skip_frag = 1;
     840           0 :       return;
     841           0 :     };
     842             : 
     843           0 :     if( FD_UNLIKELY( fd_disco_netmux_sig_proto( sig )==DST_PROTO_REPAIR ) ) {
     844           0 :       ctx->metrics->repair_rcv_cnt++;
     845           0 :       ctx->metrics->repair_rcv_bytes += sz;
     846           0 :     } else {
     847           0 :       ctx->metrics->turbine_rcv_cnt++;
     848           0 :       ctx->metrics->turbine_rcv_bytes += sz;
     849           0 :     }
     850             : 
     851             :     /* Drop unchained merkle shreds */
     852           0 :     int is_unchained = !fd_shred_is_chained( fd_shred_type( shred->variant ) );
     853           0 :     if( FD_UNLIKELY( is_unchained ) ) {
     854           0 :       ctx->metrics->shred_rejected_unchained_cnt++;
     855           0 :       ctx->skip_frag = 1;
     856           0 :       return;
     857           0 :     };
     858             : 
     859             :     /* all shreds in the same FEC set will have the same signature
     860             :        so we can round-robin shreds between the shred tiles based on
     861             :        just the signature without splitting individual FEC sets. */
     862           0 :     ulong sig = fd_ulong_load_8( shred->signature );
     863           0 :     if( FD_LIKELY( sig%ctx->round_robin_cnt!=ctx->round_robin_id ) ) {
     864           0 :       ctx->skip_frag = 1;
     865           0 :       return;
     866           0 :     }
     867           0 :     fd_memcpy( ctx->shred_buffer, dcache_entry+hdr_sz, sz-hdr_sz );
     868           0 :     ctx->shred_buffer_sz = sz-hdr_sz;
     869           0 :   }
     870           0 : }
     871             : 
     872             : static inline void
     873             : send_shred( fd_shred_ctx_t                 * ctx,
     874             :             fd_stem_context_t              * stem,
     875             :             fd_shred_t const               * shred,
     876             :             fd_shred_dest_weighted_t const * dest,
     877           0 :             ulong                            tsorig ) {
     878             : 
     879           0 :   if( FD_UNLIKELY( !dest->ip4 ) ) return;
     880             : 
     881           0 :   uchar * packet = fd_chunk_to_laddr( ctx->net_out_mem, ctx->net_out_chunk );
     882             : 
     883           0 :   int is_data = fd_shred_is_data( fd_shred_type( shred->variant ) );
     884           0 :   fd_ip4_udp_hdrs_t * hdr  = (fd_ip4_udp_hdrs_t *)packet;
     885           0 :   *hdr = *( is_data ? ctx->data_shred_net_hdr : ctx->parity_shred_net_hdr );
     886             : 
     887           0 :   fd_ip4_hdr_t * ip4 = hdr->ip4;
     888           0 :   ip4->daddr  = dest->ip4;
     889           0 :   ip4->net_id = fd_ushort_bswap( ctx->net_id++ );
     890           0 :   ip4->check  = 0U;
     891           0 :   ip4->check  = fd_ip4_hdr_check_fast( ip4 );
     892             : 
     893           0 :   hdr->udp->net_dport = fd_ushort_bswap( dest->port );
     894             : 
     895           0 :   ulong shred_sz = fd_ulong_if( is_data, FD_SHRED_MIN_SZ, FD_SHRED_MAX_SZ );
     896           0 : #if FD_HAS_AVX
     897             :   /* We're going to copy this shred potentially a bunch of times without
     898             :      reading it again, and we'd rather not thrash our cache, so we want
     899             :      to use non-temporal writes here.  We need to make sure we don't
     900             :      touch the cache line containing the network headers that we just
     901             :      wrote to though.  We know the destination is 64 byte aligned.  */
     902           0 :   FD_STATIC_ASSERT( sizeof(*hdr)<64UL, non_temporal );
     903             :   /* src[0:sizeof(hdrs)] is invalid, but now we want to copy
     904             :      dest[i]=src[i] for i>=sizeof(hdrs), so it simplifies the code. */
     905           0 :   uchar const * src = (uchar const *)((ulong)shred - sizeof(fd_ip4_udp_hdrs_t));
     906           0 :   memcpy( packet+sizeof(fd_ip4_udp_hdrs_t), src+sizeof(fd_ip4_udp_hdrs_t), 64UL-sizeof(fd_ip4_udp_hdrs_t) );
     907             : 
     908           0 :   ulong end_offset = shred_sz + sizeof(fd_ip4_udp_hdrs_t);
     909           0 :   ulong i;
     910           0 :   for( i=64UL; end_offset-i<64UL; i+=64UL ) {
     911           0 : #  if FD_HAS_AVX512
     912           0 :     _mm512_stream_si512( (void *)(packet+i     ), _mm512_loadu_si512( (void const *)(src+i     ) ) );
     913             : #  else
     914           0 :     _mm256_stream_si256( (void *)(packet+i     ), _mm256_loadu_si256( (void const *)(src+i     ) ) );
     915           0 :     _mm256_stream_si256( (void *)(packet+i+32UL), _mm256_loadu_si256( (void const *)(src+i+32UL) ) );
     916           0 : #  endif
     917           0 :   }
     918           0 :   _mm_sfence();
     919           0 :   fd_memcpy( packet+i, src+i, end_offset-i ); /* Copy the last partial cache line */
     920             : 
     921             : #else
     922             :   fd_memcpy( packet+sizeof(fd_ip4_udp_hdrs_t), shred, shred_sz );
     923             : #endif
     924             : 
     925           0 :   ulong pkt_sz = shred_sz + sizeof(fd_ip4_udp_hdrs_t);
     926           0 :   ulong tspub  = fd_frag_meta_ts_comp( fd_tickcount() );
     927           0 :   ulong sig    = fd_disco_netmux_sig( dest->ip4, dest->port, dest->ip4, DST_PROTO_OUTGOING, sizeof(fd_ip4_udp_hdrs_t) );
     928           0 :   ulong const chunk = ctx->net_out_chunk;
     929           0 :   fd_stem_publish( stem, NET_OUT_IDX, sig, chunk, pkt_sz, 0UL, tsorig, tspub );
     930           0 :   ctx->net_out_chunk = fd_dcache_compact_next( chunk, pkt_sz, ctx->net_out_chunk0, ctx->net_out_wmark );
     931           0 : }
     932             : 
     933             : static void
     934             : after_frag( fd_shred_ctx_t *    ctx,
     935             :             ulong               in_idx,
     936             :             ulong               seq,
     937             :             ulong               sig,
     938             :             ulong               sz,
     939             :             ulong               tsorig,
     940             :             ulong               _tspub,
     941           0 :             fd_stem_context_t * stem ) {
     942           0 :   (void)seq;
     943           0 :   (void)sz;
     944           0 :   (void)tsorig;
     945           0 :   (void)_tspub;
     946             : 
     947           0 :   if( FD_UNLIKELY( ctx->skip_frag ) ) return;
     948             : 
     949           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_CONTACT ) ) {
     950           0 :     finalize_new_cluster_contact_info( ctx );
     951           0 :     return;
     952           0 :   }
     953             : 
     954           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_EPOCH ) ) {
     955           0 :     fd_stake_ci_epoch_msg_fini( ctx->stake_ci );
     956           0 :     return;
     957           0 :   }
     958             : 
     959           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_STAKE ) ) {
     960           0 :     fd_stake_ci_stake_msg_fini( ctx->stake_ci );
     961           0 :     return;
     962           0 :   }
     963             : 
     964           0 :   if( FD_UNLIKELY( (ctx->in_kind[ in_idx ]==IN_KIND_ROOTED) | (ctx->in_kind[ in_idx ]==IN_KIND_ROOTEDH) ) ) {
     965           0 :     if( FD_LIKELY( (ctx->new_root > 0UL) & (ctx->new_root<ULONG_MAX) ) ) fd_fec_resolver_advance_slot_old( ctx->resolver, ctx->new_root );
     966           0 :     return;
     967           0 :   }
     968             : 
     969           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP ) ) {
     970           0 :     if( ctx->gossip_upd_buf->tag==FD_GOSSIP_UPDATE_TAG_CONTACT_INFO ) {
     971           0 :       fd_gossip_contact_info_t const * ci = ctx->gossip_upd_buf->contact_info->value;
     972           0 :       fd_ip4_port_t tvu_addr;
     973           0 :       tvu_addr.addr = ci->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_TVU ].is_ipv6 ? 0U : ci->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_TVU ].ip4;
     974           0 :       tvu_addr.port = ci->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_TVU ].port;
     975           0 :       if( !tvu_addr.l ){
     976           0 :         fd_stake_ci_dest_remove( ctx->stake_ci, fd_type_pun_const( ctx->gossip_upd_buf->origin ) );
     977           0 :       } else {
     978           0 :         fd_stake_ci_dest_update( ctx->stake_ci, fd_type_pun_const( ctx->gossip_upd_buf->origin ), tvu_addr.addr, fd_ushort_bswap( tvu_addr.port ) );
     979           0 :       }
     980           0 :     } else if( ctx->gossip_upd_buf->tag==FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE ) {
     981           0 :       if( FD_UNLIKELY( !memcmp( ctx->identity_key->uc, ctx->gossip_upd_buf->origin, 32UL ) ) ) {
     982             :         /* If our own contact info was dropped, we update with dummy IP
     983             :            instead of removing since stake_ci expects our contact info
     984             :            in the sdests table all the time. fd_stake_ci_new initializes
     985             :            both ei->sdests with our contact info so this should always
     986             :            update (and not append). */
     987           0 :         fd_stake_ci_dest_update( ctx->stake_ci, fd_type_pun_const( ctx->gossip_upd_buf->origin ), 1U, 0U );
     988           0 :       } else {
     989           0 :         fd_stake_ci_dest_remove( ctx->stake_ci, fd_type_pun_const( ctx->gossip_upd_buf->origin ) );
     990           0 :       }
     991           0 :     }
     992           0 :     return;
     993           0 :   }
     994             : 
     995           0 :   if( FD_UNLIKELY( (ctx->in_kind[ in_idx ]==IN_KIND_POH) & (ctx->send_fec_set_cnt==0UL) ) ) {
     996             :     /* Entry from PoH that didn't trigger a new FEC set to be made */
     997           0 :     return;
     998           0 :   }
     999           0 :   if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_REPAIR ) ) {
    1000           0 :     return;
    1001           0 :   }
    1002             : 
    1003           0 :   ulong fanout = 200UL; /* Default Agave's DATA_PLANE_FANOUT = 200UL */
    1004             : 
    1005           0 :   if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_NET ) ) {
    1006           0 :     uchar * shred_buffer    = ctx->shred_buffer;
    1007           0 :     ulong   shred_buffer_sz = ctx->shred_buffer_sz;
    1008             : 
    1009             :     /* Accessing the slot like this is safe because we have already
    1010             :        parsed the shred in during_frag */
    1011           0 :     ulong shred_slot    = ((fd_shred_t const *)shred_buffer)->slot;
    1012           0 :     ulong max_shred_idx = ctx->current_max_shred_idx;
    1013           0 :     if( FD_UNLIKELY( shred_slot< ctx->current_max_shred_idx_start_slot ) ) max_shred_idx = ctx->prev_max_shred_idx;
    1014           0 :     if( FD_UNLIKELY( shred_slot>=ctx->next_max_shred_idx_start_slot    ) ) max_shred_idx = ctx->next_max_shred_idx;
    1015             : 
    1016           0 :     fd_shred_t const * shred = fd_shred_parse( shred_buffer, shred_buffer_sz, max_shred_idx );
    1017             : 
    1018           0 :     if( FD_UNLIKELY( !shred       ) ) { ctx->metrics->shred_processing_result[ 1 ]++; return; }
    1019             : 
    1020           0 :     fd_epoch_leaders_t const * lsched = fd_stake_ci_get_lsched_for_slot( ctx->stake_ci, shred->slot );
    1021           0 :     if( FD_UNLIKELY( !lsched      ) ) { ctx->metrics->shred_processing_result[ 0 ]++; return; }
    1022             : 
    1023           0 :     fd_pubkey_t const * slot_leader = fd_epoch_leaders_get( lsched, shred->slot );
    1024           0 :     if( FD_UNLIKELY( !slot_leader ) ) { ctx->metrics->shred_processing_result[ 0 ]++; return; } /* Count this as bad slot too */
    1025             : 
    1026           0 :     fd_fec_set_t const * out_fec_set[1];
    1027           0 :     fd_shred_t const   * out_shred[1];
    1028           0 :     fd_fec_resolver_spilled_t spilled_fec = { 0 };
    1029           0 :     int from_repair = 0;
    1030             : 
    1031           0 :     uint nonce = UINT_MAX;
    1032           0 :     ulong shred_sz = fd_shred_sz( shred );
    1033           0 :     if( FD_UNLIKELY( (fd_disco_netmux_sig_proto( sig )==DST_PROTO_REPAIR) & (shred_buffer_sz>=shred_sz+sizeof(uint)) ) ) {
    1034           0 :       nonce = FD_LOAD(uint, shred_buffer + shred_sz );
    1035           0 :       long est_now_ns = fd_log_wallclock(); /* TODO: switch to fd_clock for performance */
    1036           0 :       int  slot_complete = fd_shred_is_data( fd_shred_type( shred->variant ) ) && (shred->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE);
    1037           0 :       int  nonce_okay = fd_rnonce_ss_verify( ctx->repair_nonce_ss, nonce, shred->slot, shred->idx, slot_complete, est_now_ns );
    1038           0 :       ctx->metrics->bad_nonce += (ulong)(!nonce_okay);
    1039           0 :       from_repair = nonce_okay;
    1040           0 :     }
    1041             : 
    1042           0 :     long add_shred_timing  = -fd_tickcount();
    1043           0 :     int rv = fd_fec_resolver_add_shred( ctx->resolver, shred, shred_buffer_sz, max_shred_idx, from_repair, slot_leader->uc, out_fec_set, out_shred, &ctx->out_merkle_roots[0], &spilled_fec );
    1044           0 :     add_shred_timing      +=  fd_tickcount();
    1045             : 
    1046           0 :     fd_histf_sample( ctx->metrics->add_shred_timing, (ulong)add_shred_timing );
    1047           0 :     ctx->metrics->shred_processing_result[ rv + FD_FEC_RESOLVER_ADD_SHRED_RETVAL_OFF+FD_SHRED_ADD_SHRED_EXTRA_RETVAL_CNT ]++;
    1048             : 
    1049           0 :     if( FD_UNLIKELY( ctx->shred_out_idx!=ULONG_MAX &&  /* Only send to repair in full Firedancer */
    1050           0 :                      spilled_fec.slot!=0 ) ) {
    1051             :       /* We've spilled an in-progress FEC set in the fec_resolver. We
    1052             :          need to let repair know to clear out it's cached info for that
    1053             :          fec set and re-repair those shreds. */
    1054           0 :       fd_fec_evicted_t * evicted_msg = (fd_fec_evicted_t *)fd_type_pun( fd_chunk_to_laddr( ctx->shred_out_mem, ctx->shred_out_chunk ) );
    1055           0 :       evicted_msg->slot        = spilled_fec.slot;
    1056           0 :       evicted_msg->fec_set_idx = spilled_fec.fec_set_idx;
    1057             : 
    1058           0 :       fd_stem_publish( stem, ctx->shred_out_idx, SHRED_SIG_FEC_EVICTED, ctx->shred_out_chunk, sizeof(fd_fec_evicted_t), 0, ctx->tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
    1059           0 :       ctx->shred_out_chunk = fd_dcache_compact_next( ctx->shred_out_chunk, sizeof(fd_fec_evicted_t), ctx->shred_out_chunk0, ctx->shred_out_wmark );
    1060           0 :     }
    1061             : 
    1062           0 :     if( FD_LIKELY( ctx->shred_out_idx!=ULONG_MAX  /* Only send to repair/replay in full Firedancer */
    1063           0 :                    && ( ( rv==FD_FEC_RESOLVER_SHRED_OKAY )
    1064           0 :                       | ( rv==FD_FEC_RESOLVER_SHRED_COMPLETES )
    1065           0 :                       | ( rv==FD_FEC_RESOLVER_SHRED_DUPLICATE )
    1066           0 :                       | ( rv==FD_FEC_RESOLVER_SHRED_EQUIVOC   ) ) ) ) {
    1067             : 
    1068             :       /* Construct the sig from fec_resolver result and shred source. */
    1069             : 
    1070           0 :       ulong _sig = fd_disco_netmux_sig_proto( sig )==DST_PROTO_REPAIR
    1071           0 :                      ? ( from_repair /*nonce_okay*/ ? SHRED_SIG_SRC_REPAIR : SHRED_SIG_SRC_BAD_REPAIR )
    1072           0 :                      : ( SHRED_SIG_SRC_TURBINE );
    1073           0 :       _sig = ((ulong)rv << 32UL) | _sig;
    1074             : 
    1075             :       /* Copy the full shred into the frag and publish. */
    1076             : 
    1077           0 :       fd_shred_base_t * shred_msg = (fd_shred_base_t *)fd_chunk_to_laddr( ctx->shred_out_mem, ctx->shred_out_chunk );
    1078           0 :       memcpy(  shred_msg->shred_, shred, fd_shred_sz( shred ) );
    1079           0 :       memcpy( &shred_msg->merkle_root, ctx->out_merkle_roots[0].hash, sizeof(fd_hash_t) );
    1080           0 :       if( FD_UNLIKELY( fd_disco_netmux_sig_proto( sig )==DST_PROTO_REPAIR ) ) { shred_msg->rnonce = nonce; }
    1081             : 
    1082           0 :       ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
    1083           0 :       fd_stem_publish( stem, ctx->shred_out_idx, _sig, ctx->shred_out_chunk, sizeof(fd_shred_base_t), 0UL, ctx->tsorig, tspub );
    1084           0 :       ctx->shred_out_chunk = fd_dcache_compact_next( ctx->shred_out_chunk, sizeof(fd_shred_base_t), ctx->shred_out_chunk0, ctx->shred_out_wmark );
    1085           0 :     }
    1086             : 
    1087           0 :     if( FD_LIKELY( fd_disco_netmux_sig_proto( sig ) != DST_PROTO_REPAIR &&
    1088           0 :                  ( (rv==FD_FEC_RESOLVER_SHRED_OKAY) | (rv==FD_FEC_RESOLVER_SHRED_COMPLETES) ) ) ) {
    1089             :       /* Relay this shred */
    1090           0 :       ulong max_dest_cnt[1];
    1091           0 :       do {
    1092             :         /* If we've validated the shred and it COMPLETES but we can't
    1093             :           compute the destination for whatever reason, don't forward
    1094             :           the shred, but still send it to the blockstore. */
    1095           0 :         fd_shred_dest_t * sdest = fd_stake_ci_get_sdest_for_slot( ctx->stake_ci, shred->slot );
    1096           0 :         if( FD_UNLIKELY( !sdest ) ) break;
    1097           0 :         fd_shred_dest_idx_t * dests = fd_shred_dest_compute_children( sdest, &shred, 1UL, ctx->scratchpad_dests, 1UL, fanout, fanout, max_dest_cnt );
    1098           0 :         if( FD_UNLIKELY( !dests ) ) break;
    1099             : 
    1100           0 :         for( ulong i=0UL; i<ctx->adtl_dests_retransmit_cnt; i++ ) send_shred( ctx, stem, *out_shred, ctx->adtl_dests_retransmit+i, ctx->tsorig );
    1101           0 :         for( ulong j=0UL; j<*max_dest_cnt; j++ ) send_shred( ctx, stem, *out_shred, fd_shred_dest_idx_to_dest( sdest, dests[ j ] ), ctx->tsorig );
    1102           0 :       } while( 0 );
    1103           0 :     }
    1104             : 
    1105           0 :     if( FD_LIKELY( rv!=FD_FEC_RESOLVER_SHRED_COMPLETES ) ) return;
    1106             : 
    1107           0 :     FD_TEST( ctx->fec_sets <= *out_fec_set );
    1108           0 :     ctx->send_fec_set_idx[ 0UL ] = (ulong)(*out_fec_set - ctx->fec_sets);
    1109           0 :     ctx->send_fec_set_cnt = 1UL;
    1110           0 :     ctx->shredded_txn_cnt = 0UL;
    1111           0 :   }
    1112             : 
    1113           0 :   if( FD_UNLIKELY( ctx->send_fec_set_cnt==0UL ) ) return;
    1114             : 
    1115             :   /* Try to distribute shredded txn count across the fec sets.
    1116             :      This is an approximation, but it is acceptable. */
    1117           0 :   ulong shredded_txn_cnt_per_fec_set  = ctx->shredded_txn_cnt / ctx->send_fec_set_cnt;
    1118           0 :   ulong shredded_txn_cnt_remain       = ctx->shredded_txn_cnt - shredded_txn_cnt_per_fec_set * ctx->send_fec_set_cnt;
    1119           0 :   ulong shredded_txn_cnt_last_fec_set = shredded_txn_cnt_per_fec_set + shredded_txn_cnt_remain;
    1120             : 
    1121             :   /* If this shred completes a FEC set or is part of a microblock from
    1122             :     pack (ie. we're leader), we now have a full FEC set: so we notify
    1123             :     repair and insert into the blockstore, as well as retransmit. */
    1124             : 
    1125           0 :   for( ulong fset_k=0; fset_k<ctx->send_fec_set_cnt; fset_k++ ) {
    1126             : 
    1127           0 :     fd_fec_set_t * set = ctx->fec_sets + ctx->send_fec_set_idx[ fset_k ];
    1128             : 
    1129           0 :     fd_shred_t const * last = set->data_shreds[ FD_FEC_SHRED_CNT - 1 ].s;
    1130             : 
    1131             :     /* Compute merkle root and chained merkle root. */
    1132             : 
    1133           0 :     int replay_fwd = 1;
    1134           0 :     if( FD_LIKELY( ctx->store ) ) { /* firedancer-only */
    1135             : 
    1136           0 :       set->leader_bank = NULL; /* un-used by firedancer */
    1137             : 
    1138             :       /* Insert shreds into the store. We do this regardless of whether
    1139             :          we are leader. */
    1140             : 
    1141           0 :       fd_store_fec_t * fec = fd_store_insert( ctx->store, ctx->round_robin_id, (fd_hash_t *)fd_type_pun( &ctx->out_merkle_roots[fset_k] ) );
    1142             : 
    1143             :       /* Firedancer is configured such that the store never fills up, as
    1144             :          the reasm is responsible for also evicting from store (based on
    1145             :          its eviction policy, see fd_reasm.h). fec is only NULL when the
    1146             :          store is full, so this is either a bug or misconfiguration. */
    1147             : 
    1148           0 :       if( FD_UNLIKELY( !fec ) ) FD_LOG_CRIT(( "store full" ));
    1149             : 
    1150             :       /* It's safe to memcpy the FEC payload outside of the shared lock,
    1151             :          because the store ele is guaranteed to remain valid here.  It
    1152             :          is not possible for a fd_store_remove to interleave, because
    1153             :          remove is only called by replay_tile, which (crucially) is only
    1154             :          sent this FEC via stem publish after we have finished copying.
    1155             : 
    1156             :          Copying outside the shared lock scope also means that we can
    1157             :          lower the duration for which the shared lock is held, and
    1158             :          enables replay to acquire the exclusive lock for removes
    1159             :          without getting starved. */
    1160             : 
    1161             :       /* if data_sz is non-zero, we've already inserted this FEC set into the store */
    1162           0 :       if( FD_UNLIKELY( fec->data_sz ) ) replay_fwd = 0;
    1163           0 :       else {
    1164           0 :         for( ulong i=0UL; i<FD_FEC_SHRED_CNT; i++ ) {
    1165           0 :           fd_shred_t * data_shred = set->data_shreds[i].s;
    1166           0 :           ulong        payload_sz = fd_shred_payload_sz( data_shred );
    1167           0 :           if( FD_UNLIKELY( fec->data_sz + payload_sz > ctx->store->fec_data_max ) ) {
    1168             : 
    1169             :             /* This code is only reachable if shred tile has completed the
    1170             :                FEC set, which implies it was able to validate it, yet
    1171             :                somehow the total payload sz of this FEC set exceeds the
    1172             :                maximum payload sz.  This indicates either a serious bug or
    1173             :                shred tile is compromised so FD_LOG_CRIT. */
    1174             : 
    1175           0 :             FD_LOG_CRIT(( "Shred tile %lu: completed FEC set %lu %u data_sz: %lu exceeds data_max: %lu. Ignoring FEC set.", ctx->round_robin_id, data_shred->slot, data_shred->fec_set_idx, fec->data_sz + payload_sz, ctx->store->fec_data_max ));
    1176           0 :           }
    1177           0 :           fd_memcpy( fd_store_fec_data( ctx->store, fec ) + fec->data_sz, fd_shred_data_payload( data_shred ), payload_sz );
    1178           0 :           fec->data_sz += payload_sz;
    1179           0 :           if( FD_LIKELY( i<32UL ) ) fec->shred_offs[ i ] = (uint)payload_sz +  (i==0UL ? 0U : fec->shred_offs[ i-1UL ]);
    1180           0 :         }
    1181           0 :       }
    1182           0 :     }
    1183             : 
    1184           0 :     if( FD_LIKELY( ctx->shred_out_idx!=ULONG_MAX && replay_fwd ) ) { /* firedancer-only */
    1185             : 
    1186             :       /* Send all of the data shred headers we recovered (weren't received) */
    1187           0 :       for( int i=0; i<32; i++ ) {
    1188           0 :         if( fd_uint_extract_bit( set->data_shred_rcvd, i )==0 ) {
    1189           0 :           fd_shred_t * const missing = &set->data_shreds[ i ].s[0];
    1190             : 
    1191           0 :           ulong sig = ((ulong)FD_FEC_RESOLVER_SHRED_COMPLETES << 32UL) | SHRED_SIG_SRC_RECONSTRUCTED;
    1192             : 
    1193           0 :           fd_shred_base_t * shred_msg = (fd_shred_base_t *)fd_chunk_to_laddr( ctx->shred_out_mem, ctx->shred_out_chunk );
    1194           0 :           memcpy(  shred_msg->shred_, missing, fd_shred_sz( missing ) );
    1195           0 :           memcpy( &shred_msg->merkle_root, ctx->out_merkle_roots[fset_k].hash, sizeof(fd_hash_t) );
    1196             : 
    1197           0 :           ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
    1198           0 :           fd_stem_publish( stem, ctx->shred_out_idx, sig, ctx->shred_out_chunk, sizeof(fd_shred_base_t), 0UL, ctx->tsorig, tspub );
    1199           0 :           ctx->shred_out_chunk = fd_dcache_compact_next( ctx->shred_out_chunk, sizeof(fd_shred_base_t), ctx->shred_out_chunk0, ctx->shred_out_wmark );
    1200           0 :         }
    1201           0 :       }
    1202             : 
    1203             :       /* Additionally, publish a frag to notify repair and replay that
    1204             :          the FEC set is complete.  Note the ordering wrt store shred
    1205             :          insertion above is intentional: shreds are inserted into the
    1206             :          store before notifying repair and replay.  This is because the
    1207             :          replay tile assumes the shreds are already in the store when
    1208             :          replay gets a notification from the shred tile that the FEC is
    1209             :          complete.  We we don't know whether shred will finish inserting
    1210             :          into store first or repair will finish validating the FEC set
    1211             :          first.  The header and merkle root of the last shred in the FEC
    1212             :          set are sent as part of this frag.
    1213             : 
    1214             :          This message, the shred msg, and the FEC evict msg constitute
    1215             :          the max 3 possible messages to repair/replay per after_frag.
    1216             :          In reality, it is only possible to publish all 3 in the case
    1217             :          where we receive a coding shred first for a FEC set where
    1218             :          (N=1,K=18), which allows for the FEC set to be instantly
    1219             :          completed by the singular coding shred, and that also happens
    1220             :          to evict a FEC set from the curr_map.  When fix-32 arrives, the
    1221             :          link burst value can be lowered to 2. */
    1222           0 :       ulong sig = ctx->in_kind[ in_idx ]==IN_KIND_POH ? SHRED_SIG_FEC_COMPLETE_LEADER : SHRED_SIG_FEC_COMPLETE;
    1223             : 
    1224           0 :       fd_fec_complete_t * complete_msg = fd_chunk_to_laddr( ctx->shred_out_mem, ctx->shred_out_chunk );
    1225           0 :       complete_msg->last_shred_hdr = *last;
    1226           0 :       memcpy( &complete_msg->merkle_root, ctx->out_merkle_roots[fset_k].hash, sizeof(fd_hash_t) );
    1227           0 :       complete_msg->chained_merkle_root = *(fd_hash_t *)fd_type_pun((uchar *)last + fd_shred_chain_off( last->variant ));
    1228             : 
    1229           0 :       fd_stem_publish( stem, ctx->shred_out_idx, sig, ctx->shred_out_chunk, sizeof(fd_fec_complete_t), 0UL, ctx->tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
    1230           0 :       ctx->shred_out_chunk = fd_dcache_compact_next( ctx->shred_out_chunk, sizeof(fd_fec_complete_t), ctx->shred_out_chunk0, ctx->shred_out_wmark );
    1231             : 
    1232           0 :     } else if( FD_UNLIKELY( ctx->store_out_idx != ULONG_MAX ) ) { /* frankendancer-only */
    1233             : 
    1234             :       /* Send to the blockstore */
    1235             : 
    1236           0 :       ulong txn_cnt = fd_ulong_if( fset_k==ctx->send_fec_set_cnt-1UL, shredded_txn_cnt_last_fec_set, shredded_txn_cnt_per_fec_set );
    1237             :       /* If the low 32 bits of sig are 0, the store tile will do extra
    1238             :          checks */
    1239           0 :       ulong new_sig = txn_cnt<<32 | (ulong)(ctx->in_kind[ in_idx ]!=IN_KIND_NET);
    1240             : 
    1241             :       /* Attach the leader bank pointer and merkle root so that the
    1242             :          store tile can set the block_id for the slot.  Network
    1243             :          FEC sets have no leader bank. */
    1244           0 :       if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_POH ) ) {
    1245           0 :         set->leader_bank = ctx->leader_bank;
    1246           0 :         memcpy( set->merkle_root, ctx->out_merkle_roots[fset_k].hash, 32UL );
    1247           0 :       } else {
    1248           0 :         set->leader_bank = NULL;
    1249           0 :       }
    1250             : 
    1251           0 :       ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
    1252             :       /* The size is actually slightly larger than USHORT_MAX, but the store tile
    1253             :          knows to use sizeof(fd_fec_set_t) instead of the sz field.  Put
    1254             :          USHORT_MAX so that monitoring tools are at least close. */
    1255           0 :       ulong sz = fd_ulong_min( sizeof(fd_fec_set_t), USHORT_MAX );
    1256           0 :       fd_stem_publish( stem, 0UL, new_sig, fd_laddr_to_chunk( ctx->store_out_mem, set ), sz, 0UL, ctx->tsorig, tspub );
    1257             : 
    1258             :       /* Store tile will release the bank pointer when it sees
    1259             :          SLOT_COMPLETE FEC set.  So we reset our tracking. */
    1260           0 :       if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_POH &&
    1261           0 :                        (set->data_shreds[ FD_FEC_SHRED_CNT-1UL ].s->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE) ) ) {
    1262           0 :         ctx->leader_bank  = NULL;
    1263           0 :       }
    1264           0 :     }
    1265             : 
    1266             :     /* Compute all the destinations for all the new shreds */
    1267             : 
    1268           0 :     fd_shred_t const * new_shreds[ FD_REEDSOL_DATA_SHREDS_MAX+FD_REEDSOL_PARITY_SHREDS_MAX ];
    1269           0 :     ulong k=0UL;
    1270           0 :     for( ulong i=0UL; i<FD_FEC_SHRED_CNT; i++ )
    1271           0 :       if( !(set->data_shred_rcvd   & (1U<<i)) ) new_shreds[ k++ ] = set->data_shreds  [ i ].s;
    1272           0 :     for( ulong i=0UL; i<FD_FEC_SHRED_CNT; i++ )
    1273           0 :       if( !(set->parity_shred_rcvd & (1U<<i)) ) new_shreds[ k++ ] = set->parity_shreds[ i ].s;
    1274             : 
    1275           0 :     if( FD_UNLIKELY( !k ) ) return;
    1276           0 :     fd_shred_dest_t * sdest = fd_stake_ci_get_sdest_for_slot( ctx->stake_ci, new_shreds[ 0 ]->slot );
    1277           0 :     if( FD_UNLIKELY( !sdest ) ) return;
    1278             : 
    1279           0 :     ulong out_stride;
    1280           0 :     ulong max_dest_cnt[1];
    1281           0 :     fd_shred_dest_idx_t * dests;
    1282           0 :     if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_NET ) ) {
    1283           0 :       for( ulong i=0UL; i<k; i++ ) {
    1284           0 :         for( ulong j=0UL; j<ctx->adtl_dests_retransmit_cnt; j++ ) send_shred( ctx, stem, new_shreds[ i ], ctx->adtl_dests_retransmit+j, ctx->tsorig );
    1285           0 :       }
    1286           0 :       out_stride = k;
    1287             :       /* In the case of feature activation, the fanout used below is
    1288             :           the same as the one calculated/modified previously at the
    1289             :           beginning of after_frag() for IN_KIND_NET in this slot. */
    1290           0 :       dests = fd_shred_dest_compute_children( sdest, new_shreds, k, ctx->scratchpad_dests, k, fanout, fanout, max_dest_cnt );
    1291           0 :     } else {
    1292           0 :       for( ulong i=0UL; i<k; i++ ) {
    1293           0 :         for( ulong j=0UL; j<ctx->adtl_dests_leader_cnt; j++ ) send_shred( ctx, stem, new_shreds[ i ], ctx->adtl_dests_leader+j, ctx->tsorig );
    1294           0 :       }
    1295           0 :       out_stride = 1UL;
    1296           0 :       *max_dest_cnt = 1UL;
    1297           0 :       dests = fd_shred_dest_compute_first   ( sdest, new_shreds, k, ctx->scratchpad_dests );
    1298           0 :     }
    1299           0 :     if( FD_UNLIKELY( !dests ) ) return;
    1300             : 
    1301             :     /* Send only the ones we didn't receive. */
    1302           0 :     for( ulong i=0UL; i<k; i++ ) {
    1303           0 :       for( ulong j=0UL; j<*max_dest_cnt; j++ ) send_shred( ctx, stem, new_shreds[ i ], fd_shred_dest_idx_to_dest( sdest, dests[ j*out_stride+i ]), ctx->tsorig );
    1304           0 :     }
    1305           0 :   }
    1306           0 : }
    1307             : 
    1308             : static void
    1309             : privileged_init( fd_topo_t const *      topo,
    1310           0 :                  fd_topo_tile_t const * tile ) {
    1311           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1312           0 :   FD_TEST( scratch!=NULL );
    1313             : 
    1314           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1315           0 :   fd_shred_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_shred_ctx_t ), sizeof( fd_shred_ctx_t ) );
    1316             : 
    1317           0 :   if( FD_UNLIKELY( !strcmp( tile->shred.identity_key_path, "" ) ) )
    1318           0 :     FD_LOG_ERR(( "identity_key_path not set" ));
    1319             : 
    1320           0 :   ctx->identity_key[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->shred.identity_key_path, /* pubkey only: */ 1 ) );
    1321             : 
    1322           0 :   if( FD_UNLIKELY( !fd_rng_secure( &(ctx->resolver_seed), sizeof(ulong) ) ) ) {
    1323           0 :     FD_LOG_CRIT(( "fd_rng_secure failed" ));
    1324           0 :   }
    1325           0 :   if( FD_UNLIKELY( !fd_rng_secure( &(ctx->shred_dest_seed), sizeof(ulong) ) ) ) {
    1326           0 :     FD_LOG_CRIT(( "fd_rng_secure failed" ));
    1327           0 :   }
    1328             :   /* This is only needed in frankendancer, but we'll overwrite it with
    1329             :      the value the repair tile generated in full firedancer. */
    1330           0 :   if( FD_UNLIKELY( !fd_rng_secure( ctx->repair_nonce_ss->bytes, sizeof(fd_rnonce_ss_t) ) ) ) {
    1331           0 :     FD_LOG_CRIT(( "fd_rng_secure failed" ));
    1332           0 :   }
    1333           0 : }
    1334             : 
    1335             : static void
    1336             : fd_shred_signer( void *        signer_ctx,
    1337             :                  uchar         signature[ static 64 ],
    1338           0 :                  uchar const   merkle_root[ static 32 ] ) {
    1339           0 :   fd_keyguard_client_sign( signer_ctx, signature, merkle_root, 32UL, FD_KEYGUARD_SIGN_TYPE_ED25519 );
    1340           0 : }
    1341             : 
    1342             : static void
    1343             : unprivileged_init( fd_topo_t const *      topo,
    1344           0 :                    fd_topo_tile_t const * tile ) {
    1345             : 
    1346           0 :   FD_TEST( 0==strcmp( topo->links[tile->out_link_id[ NET_OUT_IDX   ]].name, "shred_net"   ) );
    1347           0 :   FD_TEST( 0==strcmp( topo->links[tile->out_link_id[ SIGN_OUT_IDX  ]].name, "shred_sign"  ) );
    1348             : 
    1349           0 :   if( FD_UNLIKELY( !tile->out_cnt ) )
    1350           0 :     FD_LOG_ERR(( "shred tile has no primary output link" ));
    1351             : 
    1352           0 :   ulong shred_store_mcache_depth = tile->shred.depth;
    1353           0 :   if( topo->links[ tile->out_link_id[ 0 ] ].depth != shred_store_mcache_depth )
    1354           0 :     FD_LOG_ERR(( "shred tile out depths are not equal %lu %lu",
    1355           0 :                  topo->links[ tile->out_link_id[ 0 ] ].depth, shred_store_mcache_depth ));
    1356             : 
    1357           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1358           0 :   FD_TEST( scratch!=NULL );
    1359             : 
    1360           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1361           0 :   fd_shred_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_shred_ctx_t ), sizeof( fd_shred_ctx_t ) );
    1362             : 
    1363           0 :   ctx->round_robin_cnt = fd_topo_tile_name_cnt( topo, tile->name );
    1364           0 :   ctx->round_robin_id  = tile->kind_id;
    1365           0 :   ctx->batch_cnt       = 0UL;
    1366           0 :   ctx->slot            = ULONG_MAX;
    1367             : 
    1368             :   /* If the default partial_depth is ever changed, correspondingly
    1369             :      change the size of the fd_fec_intra_pool in fd_fec_repair. */
    1370           0 :   ulong fec_resolver_footprint = fd_fec_resolver_footprint( tile->shred.fec_resolver_depth, 1UL, shred_store_mcache_depth + 1UL,
    1371           0 :                                                             128UL * tile->shred.fec_resolver_depth );
    1372             :   /* See long comment at the top of this file for the computation of
    1373             :      fec_set_cnt. */
    1374           0 :   ulong fec_set_cnt            = 2UL*shred_store_mcache_depth + tile->shred.fec_resolver_depth + FD_SHRED_BATCH_FEC_SETS_MAX + 2UL;
    1375           0 :   ulong fec_sets_required_sz   = fec_set_cnt*sizeof(fd_fec_set_t);
    1376             : 
    1377           0 :   void * fec_sets_shmem = NULL;
    1378           0 :   ctx->shred_out_idx = fd_topo_find_tile_out_link( topo, tile, "shred_out", ctx->round_robin_id );
    1379           0 :   ctx->store_out_idx = fd_topo_find_tile_out_link( topo, tile, "shred_store",  ctx->round_robin_id );
    1380           0 :   if( FD_LIKELY( ctx->shred_out_idx!=ULONG_MAX ) ) { /* firedancer-only */
    1381           0 :     fd_topo_link_t const * shred_out = &topo->links[ tile->out_link_id[ ctx->shred_out_idx ] ];
    1382           0 :     ctx->shred_out_mem    = topo->workspaces[ topo->objs[ shred_out->dcache_obj_id ].wksp_id ].wksp;
    1383           0 :     ctx->shred_out_chunk0 = fd_dcache_compact_chunk0( ctx->shred_out_mem, shred_out->dcache );
    1384           0 :     ctx->shred_out_wmark  = fd_dcache_compact_wmark ( ctx->shred_out_mem, shred_out->dcache, shred_out->mtu );
    1385           0 :     ctx->shred_out_chunk  = ctx->shred_out_chunk0;
    1386           0 :     FD_TEST( fd_dcache_compact_is_safe( ctx->shred_out_mem, shred_out->dcache, shred_out->mtu, shred_out->depth ) );
    1387           0 :     ulong fec_sets_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "fec_sets" );
    1388           0 :     if( FD_UNLIKELY( fec_sets_obj_id == ULONG_MAX ) ) FD_LOG_ERR(( "invalid firedancer topo" ));
    1389           0 :     fd_topo_obj_t const * obj = &topo->objs[ fec_sets_obj_id ];
    1390           0 :     if( FD_UNLIKELY( obj->footprint<(fec_sets_required_sz*ctx->round_robin_cnt) ) ) {
    1391           0 :       FD_LOG_ERR(( "fec_sets wksp obj too small. It is %lu bytes but must be at least %lu bytes. ",
    1392           0 :                    obj->footprint,
    1393           0 :                    fec_sets_required_sz ));
    1394           0 :     }
    1395           0 :     fec_sets_shmem = (uchar *)fd_topo_obj_laddr( topo, fec_sets_obj_id ) + (ctx->round_robin_id * fec_sets_required_sz);
    1396             : 
    1397           0 :     ulong rnonce_ss_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "rnonce_ss" );
    1398           0 :     FD_TEST( rnonce_ss_id!=ULONG_MAX );
    1399           0 :     memcpy( ctx->repair_nonce_ss, fd_topo_obj_laddr( topo, rnonce_ss_id ), sizeof(fd_rnonce_ss_t) );
    1400             : 
    1401           0 :   } else if ( FD_LIKELY( ctx->store_out_idx!=ULONG_MAX ) ) { /* frankendancer-only */
    1402           0 :     FD_TEST( 0==strcmp( topo->links[tile->out_link_id[ ctx->store_out_idx ]].name, "shred_store" ) );
    1403           0 :     fec_sets_shmem = topo->links[ tile->out_link_id[ ctx->store_out_idx ] ].dcache;
    1404           0 :     if( FD_UNLIKELY( fd_dcache_data_sz( fec_sets_shmem )<fec_sets_required_sz ) ) {
    1405           0 :       FD_LOG_ERR(( "shred_store dcache too small. It is %lu bytes but must be at least %lu bytes. ",
    1406           0 :                   fd_dcache_data_sz( fec_sets_shmem ),
    1407           0 :                   fec_sets_required_sz ));
    1408           0 :     }
    1409           0 :   }
    1410             : 
    1411           0 :   if( FD_UNLIKELY( !tile->shred.fec_resolver_depth ) ) FD_LOG_ERR(( "fec_resolver_depth not set" ));
    1412           0 :   if( FD_UNLIKELY( !tile->shred.shred_listen_port  ) ) FD_LOG_ERR(( "shred_listen_port not set" ));
    1413             : 
    1414           0 :   void * _stake_ci = FD_SCRATCH_ALLOC_APPEND( l, fd_stake_ci_align(),              fd_stake_ci_footprint()            );
    1415           0 :   void * _resolver = FD_SCRATCH_ALLOC_APPEND( l, fd_fec_resolver_align(),          fec_resolver_footprint             );
    1416           0 :   void * _shredder = FD_SCRATCH_ALLOC_APPEND( l, fd_shredder_align(),              fd_shredder_footprint()            );
    1417             : 
    1418           0 :   fd_fec_set_t * fec_sets  = (fd_fec_set_t *)fec_sets_shmem;
    1419             : 
    1420           0 : #define NONNULL( x ) (__extension__({                                        \
    1421           0 :       __typeof__((x)) __x = (x);                                             \
    1422           0 :       if( FD_UNLIKELY( !__x ) ) FD_LOG_ERR(( #x " was unexpectedly NULL" )); \
    1423           0 :       __x; }))
    1424             : 
    1425           0 :   int has_ipecho_in = fd_topo_find_tile_in_link( topo, tile, "ipecho_out", 0UL )!=ULONG_MAX;
    1426           0 :   ushort expected_shred_version = tile->shred.expected_shred_version;
    1427           0 :   if( FD_UNLIKELY( !has_ipecho_in && !expected_shred_version ) ) {
    1428           0 :     ulong busy_obj_id = fd_pod_query_ulong( topo->props, "pohh_shred", ULONG_MAX );
    1429           0 :     FD_TEST( busy_obj_id!=ULONG_MAX );
    1430           0 :     ulong * gossip_shred_version = fd_fseq_join( fd_topo_obj_laddr( topo, busy_obj_id ) );
    1431           0 :     FD_LOG_INFO(( "Waiting for shred version to be determined via gossip." ));
    1432           0 :     ulong _expected_shred_version = ULONG_MAX;
    1433           0 :     do {
    1434           0 :       _expected_shred_version = FD_VOLATILE_CONST( *gossip_shred_version );
    1435           0 :     } while( _expected_shred_version==ULONG_MAX );
    1436             : 
    1437           0 :     if( FD_UNLIKELY( _expected_shred_version>USHORT_MAX ) ) FD_LOG_ERR(( "invalid shred version %lu", _expected_shred_version ));
    1438           0 :     FD_LOG_INFO(( "Using shred version %hu", (ushort)_expected_shred_version ));
    1439           0 :     expected_shred_version = (ushort)_expected_shred_version;
    1440           0 :   }
    1441             : 
    1442           0 :   ctx->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
    1443           0 :   FD_TEST( ctx->keyswitch );
    1444             : 
    1445             :   /* populate ctx */
    1446           0 :   ulong sign_in_idx = fd_topo_find_tile_in_link( topo, tile, "sign_shred", tile->kind_id );
    1447           0 :   FD_TEST( sign_in_idx!=ULONG_MAX );
    1448           0 :   fd_topo_link_t const * sign_in = &topo->links[ tile->in_link_id[ sign_in_idx ] ];
    1449           0 :   fd_topo_link_t const * sign_out = &topo->links[ tile->out_link_id[ SIGN_OUT_IDX ] ];
    1450           0 :   NONNULL( fd_keyguard_client_join( fd_keyguard_client_new( ctx->keyguard_client,
    1451           0 :                                                             sign_out->mcache,
    1452           0 :                                                             sign_out->dcache,
    1453           0 :                                                             sign_in->mcache,
    1454           0 :                                                             sign_in->dcache,
    1455           0 :                                                             sign_out->mtu ) ) );
    1456             : 
    1457           0 :   ctx->larger_shred_limits_per_block = tile->shred.larger_shred_limits_per_block;
    1458           0 :   ulong shred_limit                  = fd_ulong_if( tile->shred.larger_shred_limits_per_block, 32UL*32UL*1024UL, 32UL*1024UL );
    1459           0 :   ctx->shred_limit                   = shred_limit;
    1460           0 :   fd_fec_set_t * resolver_sets       = fec_sets + shred_store_mcache_depth + FD_SHRED_BATCH_FEC_SETS_MAX;
    1461           0 :   ctx->shredder = NONNULL( fd_shredder_join     ( fd_shredder_new     ( _shredder, fd_shred_signer, ctx->keyguard_client ) ) );
    1462           0 :   ctx->resolver = NONNULL( fd_fec_resolver_join ( fd_fec_resolver_new ( _resolver,
    1463           0 :                                                                         fd_shred_signer, ctx->keyguard_client,
    1464           0 :                                                                         tile->shred.fec_resolver_depth, 1UL,
    1465           0 :                                                                         shred_store_mcache_depth+1UL,
    1466           0 :                                                                         128UL * tile->shred.fec_resolver_depth, resolver_sets,
    1467           0 :                                                                         ctx->resolver_seed ) ) );
    1468             : 
    1469           0 :   if( FD_LIKELY( !!expected_shred_version ) ) {
    1470           0 :     fd_shredder_set_shred_version    ( ctx->shredder, expected_shred_version );
    1471           0 :     fd_fec_resolver_set_shred_version( ctx->resolver, expected_shred_version );
    1472           0 :   }
    1473             : 
    1474           0 :   ctx->fec_sets = fec_sets;
    1475             : 
    1476           0 :   ctx->stake_ci = fd_stake_ci_join( fd_stake_ci_new( _stake_ci, ctx->identity_key, ctx->shred_dest_seed ) );
    1477             : 
    1478           0 :   ctx->net_id   = (ushort)0;
    1479             : 
    1480           0 :   fd_ip4_udp_hdr_init( ctx->data_shred_net_hdr,   FD_SHRED_MIN_SZ, 0, tile->shred.shred_listen_port );
    1481           0 :   fd_ip4_udp_hdr_init( ctx->parity_shred_net_hdr, FD_SHRED_MAX_SZ, 0, tile->shred.shred_listen_port );
    1482             : 
    1483           0 :   ctx->adtl_dests_retransmit_cnt = tile->shred.adtl_dests_retransmit_cnt;
    1484           0 :   for( ulong i=0UL; i<ctx->adtl_dests_retransmit_cnt; i++) {
    1485           0 :     ctx->adtl_dests_retransmit[ i ].ip4 = tile->shred.adtl_dests_retransmit[ i ].ip;
    1486           0 :     ctx->adtl_dests_retransmit[ i ].port = tile->shred.adtl_dests_retransmit[ i ].port;
    1487           0 :   }
    1488           0 :   ctx->adtl_dests_leader_cnt = tile->shred.adtl_dests_leader_cnt;
    1489           0 :   for( ulong i=0UL; i<ctx->adtl_dests_leader_cnt; i++) {
    1490           0 :     ctx->adtl_dests_leader[i].ip4  = tile->shred.adtl_dests_leader[i].ip;
    1491           0 :     ctx->adtl_dests_leader[i].port = tile->shred.adtl_dests_leader[i].port;
    1492           0 :   }
    1493             : 
    1494           0 :   uchar has_contact_info_in = 0;
    1495           0 :   for( ulong i=0UL; i<tile->in_cnt; i++ ) {
    1496           0 :     fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
    1497           0 :     fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
    1498             : 
    1499           0 :     if( FD_LIKELY(      !strcmp( link->name, "net_shred"    ) ) ) {
    1500           0 :       ctx->in_kind[ i ] = IN_KIND_NET;
    1501           0 :       fd_net_rx_bounds_init( &ctx->in[ i ].net_rx, link->dcache );
    1502           0 :       continue; /* only net_rx needs to be set in this case. */
    1503           0 :     }
    1504           0 :     else if( FD_LIKELY( !strcmp( link->name, "poh_shred"    ) ) )   ctx->in_kind[ i ] = IN_KIND_POH;   /* Firedancer */
    1505           0 :     else if( FD_LIKELY( !strcmp( link->name, "pohh_shred"   ) ) )   ctx->in_kind[ i ] = IN_KIND_POH;   /* Frankendancer */
    1506           0 :     else if( FD_LIKELY( !strcmp( link->name, "stake_out"    ) ) )   ctx->in_kind[ i ] = IN_KIND_STAKE; /* Frankendancer */
    1507           0 :     else if( FD_LIKELY( !strcmp( link->name, "replay_epoch" ) ) )   ctx->in_kind[ i ] = IN_KIND_EPOCH; /* Firedancer */
    1508           0 :     else if( FD_LIKELY( !strcmp( link->name, "sign_shred"   ) ) )   ctx->in_kind[ i ] = IN_KIND_SIGN;
    1509           0 :     else if( FD_LIKELY( !strcmp( link->name, "ipecho_out"   ) ) )   ctx->in_kind[ i ] = IN_KIND_IPECHO;
    1510           0 :     else if( FD_LIKELY( !strcmp( link->name, "tower_out"    ) ) )   ctx->in_kind[ i ] = IN_KIND_ROOTED;
    1511           0 :     else if( FD_LIKELY( !strcmp( link->name, "replay_resol" ) ) )   ctx->in_kind[ i ] = IN_KIND_ROOTEDH;
    1512           0 :     else if( FD_LIKELY( !strcmp( link->name, "crds_shred"   ) ) ) { ctx->in_kind[ i ] = IN_KIND_CONTACT;
    1513           0 :       if( FD_UNLIKELY( has_contact_info_in ) ) FD_LOG_ERR(( "shred tile has multiple contact info in link types, can only be either gossip_out or crds_shred" ));
    1514           0 :       has_contact_info_in = 1;
    1515           0 :     }
    1516           0 :     else if( FD_LIKELY( !strcmp( link->name, "gossip_out"   ) ) ) { ctx->in_kind[ i ] = IN_KIND_GOSSIP;
    1517           0 :       if( FD_UNLIKELY( has_contact_info_in ) ) FD_LOG_ERR(( "shred tile has multiple contact info in link types, can only be either gossip_out or crds_shred" ));
    1518           0 :       has_contact_info_in = 1;
    1519           0 :     }
    1520             : 
    1521           0 :     else FD_LOG_ERR(( "shred tile has unexpected input link %lu %s", i, link->name ));
    1522             : 
    1523           0 :     if( FD_LIKELY( !!link->mtu ) ) {
    1524           0 :       ctx->in[ i ].mem    = link_wksp->wksp;
    1525           0 :       ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
    1526           0 :       ctx->in[ i ].wmark  = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
    1527           0 :     }
    1528           0 :   }
    1529             : 
    1530           0 :   fd_topo_link_t const * net_out = &topo->links[ tile->out_link_id[ NET_OUT_IDX ] ];
    1531             : 
    1532           0 :   ctx->net_out_chunk0 = fd_dcache_compact_chunk0( fd_wksp_containing( net_out->dcache ), net_out->dcache );
    1533           0 :   ctx->net_out_mem    = topo->workspaces[ topo->objs[ net_out->dcache_obj_id ].wksp_id ].wksp;
    1534           0 :   ctx->net_out_wmark  = fd_dcache_compact_wmark ( ctx->net_out_mem, net_out->dcache, net_out->mtu );
    1535           0 :   ctx->net_out_chunk  = ctx->net_out_chunk0;
    1536             : 
    1537           0 :   ctx->store = NULL;
    1538           0 :   ulong store_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "store" );
    1539           0 :   if( FD_LIKELY( store_obj_id!=ULONG_MAX ) ) { /* firedancer-only */
    1540           0 :     ctx->store = fd_store_join( fd_topo_obj_laddr( topo, store_obj_id ) );
    1541           0 :     FD_TEST( ctx->store->magic==FD_STORE_MAGIC );
    1542           0 :     FD_TEST( ctx->store->part_cnt==ctx->round_robin_cnt ); /* single-writer (shred tile) per store part */
    1543           0 :     FD_TEST( !fd_store_verify( ctx->store ) );
    1544           0 :   }
    1545             : 
    1546           0 :   if( FD_LIKELY( ctx->shred_out_idx!=ULONG_MAX ) ) { /* firedancer-only */
    1547           0 :     fd_topo_link_t const * shred_out = &topo->links[ tile->out_link_id[ ctx->shred_out_idx ] ];
    1548           0 :     ctx->shred_out_mem         = topo->workspaces[ topo->objs[ shred_out->dcache_obj_id ].wksp_id ].wksp;
    1549           0 :     ctx->shred_out_chunk0      = fd_dcache_compact_chunk0( ctx->shred_out_mem, shred_out->dcache );
    1550           0 :     ctx->shred_out_wmark       = fd_dcache_compact_wmark ( ctx->shred_out_mem, shred_out->dcache, shred_out->mtu );
    1551           0 :     ctx->shred_out_chunk       = ctx->shred_out_chunk0;
    1552           0 :     FD_TEST( fd_dcache_compact_is_safe( ctx->shred_out_mem, shred_out->dcache, shred_out->mtu, shred_out->depth ) );
    1553           0 :   }
    1554             : 
    1555           0 :   if( FD_LIKELY( ctx->store_out_idx!=ULONG_MAX ) ) { /* frankendancer-only */
    1556           0 :     fd_topo_link_t const * store_out = &topo->links[ tile->out_link_id[ ctx->store_out_idx ] ];
    1557           0 :     ctx->store_out_mem         = topo->workspaces[ topo->objs[ store_out->dcache_obj_id ].wksp_id ].wksp;
    1558           0 :     ctx->store_out_chunk0      = fd_dcache_compact_chunk0( ctx->store_out_mem, store_out->dcache );
    1559           0 :     ctx->store_out_wmark       = fd_dcache_compact_wmark ( ctx->store_out_mem, store_out->dcache, store_out->mtu );
    1560           0 :     ctx->store_out_chunk       = ctx->store_out_chunk0;
    1561           0 :     FD_TEST( fd_dcache_compact_is_safe( ctx->store_out_mem, store_out->dcache, store_out->mtu, store_out->depth ) );
    1562           0 :   }
    1563             : 
    1564           0 :   ctx->poh_in_expect_seq = 0UL;
    1565             : 
    1566           0 :   ctx->shredder_fec_set_idx = 0UL;
    1567           0 :   ctx->shredder_max_fec_set_idx = shred_store_mcache_depth + FD_SHRED_BATCH_FEC_SETS_MAX;
    1568             : 
    1569           0 :   ctx->chained_merkle_root = NULL;
    1570           0 :   memset( ctx->out_merkle_roots, 0, sizeof(ctx->out_merkle_roots) );
    1571             : 
    1572           0 :   for( ulong i=0UL; i<FD_SHRED_BATCH_FEC_SETS_MAX; i++ ) { ctx->send_fec_set_idx[ i ] = ULONG_MAX; }
    1573           0 :   ctx->send_fec_set_cnt = 0UL;
    1574             : 
    1575           0 :   ctx->shred_buffer_sz  = 0UL;
    1576           0 :   memset( ctx->shred_buffer, 0xFF, FD_NET_MTU );
    1577             : 
    1578           0 :   ctx->leader_bank  = NULL;
    1579             : 
    1580           0 :   fd_histf_join( fd_histf_new( ctx->metrics->contact_info_cnt,     FD_MHIST_MIN(         SHRED, CONTACT_INFO_PER_MESSAGE   ),
    1581           0 :                                                                    FD_MHIST_MAX(         SHRED, CONTACT_INFO_PER_MESSAGE   ) ) );
    1582           0 :   fd_histf_join( fd_histf_new( ctx->metrics->batch_sz,             FD_MHIST_MIN(         SHRED, BATCH_SIZE_BYTES           ),
    1583           0 :                                                                    FD_MHIST_MAX(         SHRED, BATCH_SIZE_BYTES           ) ) );
    1584           0 :   fd_histf_join( fd_histf_new( ctx->metrics->batch_microblock_cnt, FD_MHIST_MIN(         SHRED, MICROBLOCK_PER_BATCH       ),
    1585           0 :                                                                    FD_MHIST_MAX(         SHRED, MICROBLOCK_PER_BATCH       ) ) );
    1586           0 :   fd_histf_join( fd_histf_new( ctx->metrics->shredding_timing,     FD_MHIST_SECONDS_MIN( SHRED, SHREDDING_DURATION_SECONDS ),
    1587           0 :                                                                    FD_MHIST_SECONDS_MAX( SHRED, SHREDDING_DURATION_SECONDS ) ) );
    1588           0 :   fd_histf_join( fd_histf_new( ctx->metrics->add_shred_timing,     FD_MHIST_SECONDS_MIN( SHRED, ADD_SHRED_DURATION_SECONDS ),
    1589           0 :                                                                    FD_MHIST_SECONDS_MAX( SHRED, ADD_SHRED_DURATION_SECONDS ) ) );
    1590           0 :   memset( ctx->metrics->shred_processing_result, '\0', sizeof(ctx->metrics->shred_processing_result) );
    1591           0 :   ctx->metrics->invalid_block_id_cnt         = 0UL;
    1592           0 :   ctx->metrics->shred_rejected_unchained_cnt = 0UL;
    1593           0 :   ctx->metrics->repair_rcv_cnt               = 0UL;
    1594           0 :   ctx->metrics->repair_rcv_bytes             = 0UL;
    1595           0 :   ctx->metrics->turbine_rcv_cnt              = 0UL;
    1596           0 :   ctx->metrics->turbine_rcv_bytes            = 0UL;
    1597           0 :   ctx->metrics->bad_nonce                    = 0UL;
    1598             : 
    1599           0 :   ctx->pending_batch.microblock_cnt = 0UL;
    1600           0 :   ctx->pending_batch.txn_cnt        = 0UL;
    1601           0 :   ctx->pending_batch.pos            = 0UL;
    1602           0 :   ctx->pending_batch.slot           = 0UL;
    1603           0 :   memset( ctx->pending_batch.payload, 0, sizeof(ctx->pending_batch.payload) );
    1604             : 
    1605           0 :   memset( ctx->epoch_schedule, 0, sizeof(ctx->epoch_schedule) );
    1606           0 :   for( ulong i=0UL; i<FD_SHRED_FEATURES_ACTIVATION_SLOT_CNT; i++ ) {
    1607           0 :     ctx->features_activation->slots[i] = FD_SHRED_FEATURES_ACTIVATION_SLOT_DISABLED;
    1608           0 :   }
    1609           0 :   ctx->prev_max_shred_idx               = ctx->shred_limit;
    1610           0 :   ctx->current_max_shred_idx            = ctx->shred_limit;
    1611           0 :   ctx->next_max_shred_idx               = ctx->shred_limit;
    1612           0 :   ctx->current_max_shred_idx_start_slot = 0UL;
    1613           0 :   ctx->next_max_shred_idx_start_slot    = ULONG_MAX;
    1614             : 
    1615           0 :   ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
    1616           0 :   if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
    1617           0 :     FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
    1618             : 
    1619           0 :   memset( ctx->block_ids, 0, sizeof(ctx->block_ids) );
    1620           0 : }
    1621             : 
    1622             : static ulong
    1623             : populate_allowed_seccomp( fd_topo_t const *      topo,
    1624             :                           fd_topo_tile_t const * tile,
    1625             :                           ulong                  out_cnt,
    1626           0 :                           struct sock_filter *   out ) {
    1627           0 :   (void)topo;
    1628           0 :   (void)tile;
    1629             : 
    1630           0 :   populate_sock_filter_policy_fd_shred_tile( out_cnt, out, (uint)fd_log_private_logfile_fd() );
    1631           0 :   return sock_filter_policy_fd_shred_tile_instr_cnt;
    1632           0 : }
    1633             : 
    1634             : static ulong
    1635             : populate_allowed_fds( fd_topo_t const *      topo,
    1636             :                       fd_topo_tile_t const * tile,
    1637             :                       ulong                  out_fds_cnt,
    1638           0 :                       int *                  out_fds ) {
    1639           0 :   (void)topo;
    1640           0 :   (void)tile;
    1641             : 
    1642           0 :   if( FD_UNLIKELY( out_fds_cnt<2UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
    1643             : 
    1644           0 :   ulong out_cnt = 0UL;
    1645           0 :   out_fds[ out_cnt++ ] = 2; /* stderr */
    1646           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
    1647           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
    1648           0 :   return out_cnt;
    1649           0 : }
    1650             : 
    1651             : /* Excluding net_out (where the link is unreliable), STEM_BURST needs
    1652             :    to guarantee enough credits for the worst case. There are 4 cases
    1653             :    to consider: (IN_KIND_NET/IN_KIND_POH) x (Frankendancer/Firedancer)
    1654             :    In the IN_KIND_NET case:  (Frankendancer) sends 1 frag to
    1655             :    store;  (Firedancer) that is one frag for the shred to repair, and
    1656             :    then another frag to repair for the FEC set.
    1657             :    In the IN_KIND_POH case:  (Frankendancer) there might be
    1658             :    FD_SHRED_BATCH_FEC_SETS_MAX FEC sets;  (Firedancer) that is
    1659             :    FD_SHRED_BATCH_FEC_SETS_MAX frags to repair (one per FEC set).
    1660             :    Therefore, the worst case is IN_KIND_POH for Frankendancer. */
    1661           0 : #define STEM_BURST (FD_SHRED_BATCH_FEC_SETS_MAX + 40UL)
    1662             : 
    1663             : /* See explanation in fd_pack */
    1664           0 : #define STEM_LAZY  (128L*3000L)
    1665             : 
    1666           0 : #define STEM_CALLBACK_CONTEXT_TYPE  fd_shred_ctx_t
    1667           0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_shred_ctx_t)
    1668             : 
    1669           0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
    1670           0 : #define STEM_CALLBACK_METRICS_WRITE       metrics_write
    1671           0 : #define STEM_CALLBACK_BEFORE_FRAG         before_frag
    1672           0 : #define STEM_CALLBACK_DURING_FRAG         during_frag
    1673           0 : #define STEM_CALLBACK_AFTER_FRAG          after_frag
    1674             : 
    1675             : #include "../stem/fd_stem.c"
    1676             : 
    1677             : fd_topo_run_tile_t fd_tile_shred = {
    1678             :   .name                     = "shred",
    1679             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    1680             :   .populate_allowed_fds     = populate_allowed_fds,
    1681             :   .scratch_align            = scratch_align,
    1682             :   .scratch_footprint        = scratch_footprint,
    1683             :   .privileged_init          = privileged_init,
    1684             :   .unprivileged_init        = unprivileged_init,
    1685             :   .run                      = stem_run,
    1686             : };

Generated by: LCOV version 1.14