LCOV - code coverage report
Current view: top level - discof/repair - fd_repair_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 745 0.0 %
Date: 2026-08-14 04:54:57 Functions: 0 93 0.0 %

          Line data    Source code
       1             : /* The repair tile is responsible for repairing missing shreds that were
       2             :    not received via Turbine.  The goal is to ensure that slots we "care"
       3             :    about have their FEC sets inserted into store.
       4             : 
       5             :    Generally there are two distinct traffic patterns:
       6             : 
       7             :    a. Firedancer boots up and fires off a large number of repairs to
       8             :       recover all the blocks between the snapshot on which it is booting
       9             :       and the head of the chain.  In this mode, repair tile utilization
      10             :       is very high along with net and sign utilization.
      11             : 
      12             :    b. Firedancer catches up to the head of the chain and enters steady
      13             :       state where most shred traffic is delivered over turbine.  In this
      14             :       state, repairs are only occasionally needed to recover shreds lost
      15             :       due to anomalies like packet loss, transmitter (leader) never sent
      16             :       them or even a malicious leader etc.  On rare occasion, repair
      17             :       will also need to recover a different version of a block that
      18             :       equivocated.
      19             : 
      20             :    To accomplish the above, repair mainly processes 4 kinds of frags:
      21             : 
      22             :    1. Shred data (from shred tile)
      23             : 
      24             :       Any shred (coding or data) that passes validation and filtering in
      25             :       the shred tile is forwarded to repair.  Repair uses these to track
      26             :       which shreds have been received in `fd_forest`, a tree data
      27             :       structure that mirrors the block/slot ancestry chain.  It also
      28             :       uses these shreds to discover slots or ancestries that were not
      29             :       known. fd_forest tracks metadata for each slot, including slot
      30             :       completion status, merkle roots, and metrics.
      31             : 
      32             :       Any shred that we can correlate with a repair request we made is
      33             :       used to update peer response latency metrics in fd_policy (See
      34             :       fd_policy.h for more details).
      35             : 
      36             :    2. FEC status messages (from shred tile)
      37             : 
      38             :       These fall under two categories: FEC completion and FEC eviction.
      39             :       When all shreds in a FEC set have been recovered, the shred tile
      40             :       sends a completion message. This may trigger chained merkle
      41             :       verification if the slot has a confirmed block_id.  The completed
      42             :       FEC message is always forwarded to replay via repair_out.
      43             : 
      44             :       When an incomplete FEC set is evicted from the shred tile's FEC
      45             :       resolver (e.g. due to capacity), it also notifies repair.  Repair
      46             :       clears the corresponding FEC set entries from the forest so those
      47             :       shred indices can be re-requested if they are necessary.  As
      48             :       mentioned in fd_forest.h, forest needs to maintain a strict subset
      49             :       of shreds that are known by fec_resolver, store, and reasm in
      50             :       order to guarantee forward progress always.
      51             : 
      52             :    3. Pings (from net tile)
      53             : 
      54             :       Repair peers use a ping-pong protocol to verify liveness before
      55             :       serving repair requests.  When a ping arrives over the network,
      56             :       repair validates the message and constructs a pong response. To
      57             :       prevent spam attacks, repair has stopgaps like tracking how many
      58             :       pongs per peer are currently in the sign queue and dropping pings
      59             :       from unknown peers.  These are the only untrusted inputs to
      60             :       repair.
      61             : 
      62             :    4. Sign task responses (from sign tile)
      63             : 
      64             :       Repair requests are signed asynchronously; the repair tile
      65             :       constructs a repair request, dispatches it to a sign tile via the
      66             :       repair_sign output link.  The repair-sign communication is
      67             :       manually managed via credit tracking in the repair tile (see
      68             :       comment in out_ctx_t struct definition).
      69             : 
      70             :       After receiving the signature back from sign tile, repair injects
      71             :       the signature into the pending request and dispatches it to the
      72             :       net tile via the repair_net output link. The behavior depends on
      73             :       the request type:
      74             :       - Pong: the signed pong is sent to the peer that pinged us.
      75             :       - Warmup: a proactive request sent when a new peer's contact info
      76             :         first arrives, prepaying the ping-pong RTT cost.  The signed
      77             :         request is sent to the peer if it is still active.
      78             :       - Regular shred request: the request is recorded in an inflight
      79             :         table (for tracking response latency and timeouts) and the
      80             :         signed packet is sent to the selected peer.
      81             : 
      82             :    Secondary "other" frags that are processed but not part of core
      83             :    repair logic:
      84             : 
      85             :    5. Confirmation messages from tower
      86             : 
      87             :       Tower sends two kinds of messages relevant to repair:
      88             :       - slot_done: indicates a slot has finished replay and may advance
      89             :         the root.  Repair publishes (roots) the forest up to that slot,
      90             :         pruning old ancestry.
      91             :       - slot_confirmed: indicates a slot has reached a confirmation
      92             :         level (e.g. duplicate-confirmed).  If the slot is not yet in the
      93             :         forest, repair creates a sentinel block so it can be repaired.
      94             :         It also stores the confirmed block_id and may trigger chained
      95             :         merkle verification. See fd_forest.h on more details about
      96             :         chained merkle verification.
      97             : 
      98             :    6. Eviction messages from replay (reasm)
      99             : 
     100             :       When the replay tile's reassembly buffer evicts a FEC set (e.g.
     101             :       due to pool capacity) from itself and from store, it notifies
     102             :       repair with the slot and fec_set_idx.  Repair clears those FEC
     103             :       entries from the forest so the shreds can be re-requested.
     104             : 
     105             :    7. Contact info messages from gossip
     106             : 
     107             :       Gossip forwards contact info updates and removals for other
     108             :       validators.  Repair uses these to maintain a list of peers to
     109             :       make requests to.
     110             : 
     111             :    If fd_forest tracks what we know about each shred, fd_policy and
     112             :    fd_inflights is responsible for deciding what next repair request to
     113             :    make. fd_policy and fd_inflights split responsibility: fd_policy
     114             :    makes any new requests, orphan requests, and requests directly off
     115             :    the forest iterator, while fd_inflights re-requests anything that has
     116             :    been requested but not received yet within a timeout window. */
     117             : 
     118             : #define _GNU_SOURCE
     119             : 
     120             : #include "../genesis/fd_genesi_tile.h"
     121             : #include "../../disco/topo/fd_topo.h"
     122             : #include "../../disco/fd_clock_tile.h"
     123             : #include "generated/fd_repair_tile_seccomp.h"
     124             : #include "../../disco/keyguard/fd_keyload.h"
     125             : #include "../../disco/keyguard/fd_keyguard.h"
     126             : #include "../../disco/keyguard/fd_keyswitch.h"
     127             : #include "../../disco/metrics/fd_metrics.h"
     128             : #include "../../disco/net/fd_net_tile.h"
     129             : #include "../../disco/shred/fd_rnonce_ss.h"
     130             : #include "../../disco/shred/fd_shred_tile.h"
     131             : #include "fd_repair_tile.h"
     132             : #include "../../flamenco/gossip/fd_gossip_message.h"
     133             : #include "../replay/fd_replay_tile.h"
     134             : #include "../tower/fd_tower_tile.h"
     135             : #include "../../discof/restore/utils/fd_ssmsg.h"
     136             : #include "../../util/net/fd_net_headers.h"
     137             : #include "../../util/pod/fd_pod_format.h"
     138             : #include "../../tango/fd_tango_base.h"
     139             : 
     140             : #include "../forest/fd_forest.h"
     141             : #include "fd_repair_metrics.h"
     142             : #include "fd_inflight.h"
     143             : #include "fd_repair.h"
     144             : #include "fd_policy.h"
     145             : 
     146             : #define DEBUG_LOGGING 0
     147             : 
     148             : #define IN_KIND_CONTACT (0)
     149           0 : #define IN_KIND_NET     (1)
     150           0 : #define IN_KIND_TOWER   (2)
     151           0 : #define IN_KIND_SHRED   (3)
     152           0 : #define IN_KIND_SIGN    (4)
     153           0 : #define IN_KIND_SNAP    (5)
     154           0 : #define IN_KIND_GOSSIP  (6)
     155           0 : #define IN_KIND_GENESIS (7)
     156           0 : #define IN_KIND_REPLAY  (8)
     157             : 
     158             : #define MAX_IN_LINKS    (32)
     159             : #define MAX_SHRED_TILE_CNT ( 16UL )
     160             : #define MAX_SIGN_TILE_CNT  ( 16UL )
     161             : 
     162             : /* Max number of validators that can be actively queried */
     163           0 : #define FD_REPAIR_PEER_MAX (FD_CONTACT_INFO_TABLE_SIZE)
     164             : 
     165             : /* Max number of pending repair requests recently made to keep track of.
     166             :    Calculated generally as we estimate around 50k/s/core to sign
     167             :    requests. Assuming an over-provisioned 4 sign tiles just for repair,
     168             :    this means we can make up to ~200k requests per second.  With a dedup
     169             :    timeout of 80ms, this means we can make up to ~16k requests within
     170             :    the dedup timeout window.  We round up to the next power of two to
     171             :    get the dedup cache max.  Since we are sizing the dedup cache for a
     172             :    generous margin, and this number not particularly fragile or
     173             :    sensitive, we can leave it static. */
     174           0 : #define FD_REQLIM_CACHE_MAX (1<<20)
     175             : 
     176             : /* static map from request type to metric array index */
     177             : static uint metric_index[FD_REPAIR_KIND_ORPHAN + 1] = {
     178             :   [FD_REPAIR_KIND_PONG]          = FD_METRICS_ENUM_REPAIR_SENT_REQUEST_TYPE_V_PONG_IDX,
     179             :   [FD_REPAIR_KIND_SHRED]         = FD_METRICS_ENUM_REPAIR_SENT_REQUEST_TYPE_V_NEEDED_WINDOW_IDX,
     180             :   [FD_REPAIR_KIND_HIGHEST_SHRED] = FD_METRICS_ENUM_REPAIR_SENT_REQUEST_TYPE_V_NEEDED_HIGHEST_WINDOW_IDX,
     181             :   [FD_REPAIR_KIND_ORPHAN]        = FD_METRICS_ENUM_REPAIR_SENT_REQUEST_TYPE_V_NEEDED_ORPHAN_IDX,
     182             : };
     183             : 
     184             : typedef union {
     185             :   struct {
     186             :     fd_wksp_t * mem;
     187             :     ulong       chunk0;
     188             :     ulong       wmark;
     189             :     ulong       mtu;
     190             :   };
     191             :   fd_net_rx_bounds_t net_rx;
     192             : } in_ctx_t;
     193             : 
     194             : struct out_ctx {
     195             :   ulong         idx;
     196             :   fd_wksp_t *   mem;
     197             :   ulong         chunk0;
     198             :   ulong         wmark;
     199             :   ulong         chunk;
     200             : 
     201             :   /* Repair tile directly tracks credit outside of stem for these
     202             :      asynchronous sign links.  In particular, credits tracks the RETURN
     203             :      sign_repair link.  This is because repair_sign and
     204             :      sign_repair are unreliable.  If both links were reliable, and the
     205             :      links filled completely, stem would get into a deadlock.  Neither
     206             :      repair or sign would have credits, which would prevent frags from
     207             :      getting polled in repair or sign, which would prevent any credits
     208             :      from getting returned back to the tiles.  So the sign_repair return
     209             :      link must be unreliable. credits / max_credits are used by the
     210             :      repair_sign link,  but credits tracks the RETURN
     211             :      sign_repair link.
     212             : 
     213             :      Consider the scenario:
     214             : 
     215             :              repair_sign (depth 128)        sign_repair (depth 128)
     216             :      repair  ---------------------->  sign ------------------------> repair
     217             :              [rest free, r130, r129]       [r128, r127, ... , r1] (full)
     218             : 
     219             :      If repair is publishing too many requests too fast(common in
     220             :      catchup), and not polling enough frags from sign, without manual
     221             :      management the sign_repair link would be overrun.  Nothing is
     222             :      stopping repair from publishing more requests, because sign is
     223             :      functioning fast enough to handle the requests. However, nothing is
     224             :      stopping sign from polling the next request and signing it, and
     225             :      PUBLISHING it on the sign_repair link that is already full, because
     226             :      the sign_repair link is unreliable.
     227             : 
     228             :      This is why we need to manually track credits for the sign_repair
     229             :      link. We must ensure that there are never more than 128 items in
     230             :      the ENTIRE repair_sign -> sign tile -> sign_repair work queue, else
     231             :      there is always a possibility of an overrun in the sign_repair
     232             :      link.
     233             : 
     234             :      We can furthermore ensure some nice properties by having the
     235             :      repair_sign link have a greater depth than the sign_repair link.
     236             :      This way, we exclusively use manual credit management to control
     237             :      the rate at which we publish requests to sign.  This allows for
     238             :      repair_sign to also be unreliable.  Even when the repair sign link
     239             :      is "full", we can avoid backpressure and continue polling frags,
     240             :      without overruning the sign_repair link.
     241             : 
     242             :      To lose a frag to overrun isn't necessarily critical, but in
     243             :      general the repair tile relies on the fact that a signing task
     244             :      published to sign tile will always come back.  If we lose a frag to
     245             :      overrun, then there will be an entry in the pending signs structure
     246             :      that is never removed, and theoretically the map could fill up.
     247             :      Conceptually, with a reliable (unreliable links, but strictly
     248             :      controlled count-per-link) sign->repair->sign structure, there
     249             :      should be no eviction needed in this pending signs structure. */
     250             : 
     251             :   ulong in_idx;      /* index of the incoming link */
     252             :   ulong credits;     /* available credits for link */
     253             :   ulong max_credits; /* maximum credits (depth) */
     254             : };
     255             : typedef struct out_ctx out_ctx_t;
     256             : 
     257             : /* Data needed to sign and send a pong that is not contained in the
     258             :    pong msg itself. */
     259             : 
     260             : struct pong_data {
     261             :   fd_ip4_port_t  peer_addr;
     262             :   fd_hash_t      hash;
     263             :   uint           daddr;
     264             :   fd_pubkey_t    key;
     265             : };
     266             : typedef struct pong_data pong_data_t;
     267             : 
     268             : struct sign_req {
     269             :   ulong       key;        /* map key, ctx->pending_key_next */
     270             :   ulong       buflen;
     271             :   union {
     272             :     uchar           buf[sizeof(fd_repair_msg_t)];
     273             :     fd_repair_msg_t msg;
     274             :   };
     275             :   pong_data_t  pong_data; /* populated only for pong msgs */
     276             : };
     277             : typedef struct sign_req sign_req_t;
     278             : 
     279             : #define MAP_NAME         fd_signs_map
     280           0 : #define MAP_KEY          key
     281           0 : #define MAP_KEY_NULL     ULONG_MAX
     282           0 : #define MAP_KEY_INVAL(k) (k==ULONG_MAX)
     283           0 : #define MAP_T            sign_req_t
     284             : #define MAP_MEMOIZE      0
     285             : #include "../../util/tmpl/fd_map_dynamic.c"
     286             : 
     287             : /* Because the sign tiles could be all busy when a contact info or a
     288             :    ping arrives, we need to save ping messages to be signed in a queue
     289             :    and dispatched in after_credit when there are sign tiles available.
     290             :    The size of the queue is sized to be the number of warm up
     291             :    requests we might burst to the queue all at once (at most
     292             :    FD_REPAIR_PEER_MAX), then doubled for good measure.
     293             : 
     294             :    There is a possibility that someone could spam pings to block other
     295             :    peers' pings (and prevent us from responding to those pings). To
     296             :    mitigate this, we track the number of pings currently living in the
     297             :    sign queue that belong to each peer.  If a peer already has a pong
     298             :    living in the sign queue, we drop the pings from that peer.
     299             : 
     300             :    The peer could send us a new bogus ping every time we pop their ping
     301             :    from the sign queue, but there would be no way to prevent other
     302             :    peers' pings from getting processed, so the wasted work and impact
     303             :    would be minimal.
     304             : 
     305             :    Typical flow is that a pong will get added to the pong_queue during
     306             :    an after_frag call.  Then on the following after_credit will get
     307             :    popped from the sign_queue and added to sign_map, and then dispatched
     308             :    to the sign tile.
     309             : 
     310             :    Note that after the first turbine shred arrives, the signs_queue also
     311             :    stores highest window index requests for slots between snapshot and
     312             :    turbine_slot0, which are dispatched first before any other requests
     313             :    as a catchup optimization.  This doesn't break any of the inflight
     314             :    invariants as highest window index requests do not get added to the
     315             :    inflight table. */
     316             : 
     317             : struct sign_pending {
     318             :   fd_repair_msg_t msg;
     319             :   pong_data_t     pong_data; /* populated only for pong msgs */
     320             : };
     321             : typedef struct sign_pending sign_pending_t;
     322             : 
     323             : #define QUEUE_NAME       fd_signs_queue
     324           0 : #define QUEUE_T          sign_pending_t
     325           0 : #define QUEUE_MAX        (2*FD_REPAIR_PEER_MAX)
     326             : #include "../../util/tmpl/fd_queue.c"
     327             : 
     328             : struct ctx {
     329             :   long tsdebug; /* timestamp for debug printing */
     330             : 
     331             :   fd_clock_tile_t clock[1];
     332             : 
     333             :   ulong repair_seed;
     334             : 
     335             :   fd_keyswitch_t * keyswitch;
     336             :   int              halt_signing;
     337             : 
     338             :   fd_ip4_port_t repair_intake_addr;
     339             : 
     340             :   fd_forest_t    * forest;
     341             :   fd_policy_t    * policy;
     342             :   fd_reqlim_t    * dedup;
     343             :   fd_inflights_t * inflights;
     344             :   fd_repair_t    * protocol;
     345             : 
     346             :   ulong enforce_fixed_fec_set; /* min slot where the feature is enforced */
     347             : 
     348             :   fd_pubkey_t identity_public_key;
     349             : 
     350             :   fd_wksp_t * wksp;
     351             : 
     352             :   fd_stem_context_t * stem;
     353             : 
     354             :   uchar    in_kind[ MAX_IN_LINKS ];
     355             :   in_ctx_t in_links[ MAX_IN_LINKS ];
     356             : 
     357             :   int skip_frag;
     358             : 
     359             :   out_ctx_t net_out_ctx[1];
     360             : 
     361             :   out_ctx_t repair_out_ctx[1];
     362             : 
     363             :   /* repair_sign links (to sign tiles 1+) - for round-robin distribution */
     364             : 
     365             :   ulong     repair_sign_cnt;
     366             :   out_ctx_t repair_sign_out_ctx[ MAX_SIGN_TILE_CNT ];
     367             : 
     368             :   ulong     sign_rrobin_idx;
     369             : 
     370             :   /* Pending sign requests for async operations */
     371             : 
     372             :   uint             pending_key_next;
     373             :   sign_req_t     * signs_map;  /* contains any request currently in the repair->sign or sign->repair dcache */
     374             :   sign_pending_t * pong_queue;  /* contains any pong or initial warmup request waiting to be dispatched to repair->sign. Size is 2*FD_REPAIR_PEER_MAX */
     375             : 
     376             :   ushort net_id;
     377             : 
     378             :   /* Buffers for incoming unreliable frags */
     379             :   uchar net_buf[ FD_NET_MTU ];
     380             :   uchar sign_buf[ sizeof(fd_ed25519_sig_t) ];
     381             : 
     382             :   /* Store chunk for incoming reliable frags */
     383             :   ulong chunk;
     384             :   ulong snap_out_chunk; /* store second to last chunk for snap_out */
     385             : 
     386             :   fd_ip4_udp_hdrs_t intake_hdr[1];
     387             : 
     388             :   fd_rnonce_ss_t repair_nonce_ss[1];
     389             : 
     390             :   ulong manifest_slot;
     391             :   struct {
     392             :     ulong send_pkt_cnt;
     393             :     ulong sent_pkt_types[FD_METRICS_ENUM_REPAIR_SENT_REQUEST_TYPE_CNT];
     394             :     ulong current_slot;
     395             :     ulong old_shred;
     396             :     ulong last_requested_slot;
     397             :     ulong last_requested_orphan;
     398             :     ulong sign_tile_unavail;
     399             :     ulong rerequest;
     400             :     ulong malformed_ping;
     401             :     ulong unknown_peer_ping;
     402             :     ulong fail_sigverify_ping;
     403             :     fd_histf_t slot_compl_time[ 1 ];
     404             :     fd_histf_t response_latency[ 1 ];
     405             :     ulong blk_evicted;
     406             :     ulong blk_failed_insert;
     407             : 
     408             :     ulong slot_evicted;
     409             :     ulong slot_evicted_by;
     410             :     ulong slot_failed_insert;
     411             : 
     412             :     ulong failed_chain_verify_cnt;
     413             :     ulong failed_chain_verify_slot;
     414             :   } metrics[ 1 ];
     415             : 
     416             :   /* Slot-level metrics */
     417             : 
     418             :   fd_repair_metrics_t * slot_metrics;
     419             :   ulong turbine_slot0;  // catchup considered complete after this slot
     420             : };
     421             : typedef struct ctx ctx_t;
     422             : 
     423             : FD_FN_CONST static inline ulong
     424           0 : scratch_align( void ) {
     425           0 :   return 128UL;
     426           0 : }
     427             : 
     428             : FD_FN_PURE static inline ulong
     429           0 : scratch_footprint( fd_topo_tile_t const * tile ) {
     430           0 :   ulong total_sign_depth = tile->repair.repair_sign_depth * tile->repair.repair_sign_cnt;
     431           0 :   int   lg_sign_depth    = fd_ulong_find_msb( fd_ulong_pow2_up(total_sign_depth) ) + 1;
     432             : 
     433           0 :   ulong l = FD_LAYOUT_INIT;
     434           0 :   l = FD_LAYOUT_APPEND( l, alignof(ctx_t),            sizeof(ctx_t)                                                      );
     435           0 :   l = FD_LAYOUT_APPEND( l, fd_repair_align(),         fd_repair_footprint     ()                                         );
     436           0 :   l = FD_LAYOUT_APPEND( l, fd_forest_align(),         fd_forest_footprint     ( tile->repair.slot_max )                  );
     437           0 :   l = FD_LAYOUT_APPEND( l, fd_policy_align(),         fd_policy_footprint     ( FD_REPAIR_PEER_MAX )                     );
     438           0 :   l = FD_LAYOUT_APPEND( l, fd_reqlim_align(),         fd_reqlim_footprint     ( FD_REQLIM_CACHE_MAX )                     );
     439           0 :   l = FD_LAYOUT_APPEND( l, fd_inflights_align(),      fd_inflights_footprint  ()                                         );
     440           0 :   l = FD_LAYOUT_APPEND( l, fd_signs_map_align(),      fd_signs_map_footprint  ( lg_sign_depth )                          );
     441           0 :   l = FD_LAYOUT_APPEND( l, fd_signs_queue_align(),    fd_signs_queue_footprint()                                         );
     442           0 :   l = FD_LAYOUT_APPEND( l, fd_repair_metrics_align(), fd_repair_metrics_footprint()                                      );
     443           0 :   return FD_LAYOUT_FINI( l, scratch_align() );
     444           0 : }
     445             : 
     446             : /* Below functions manage the current pending sign requests. */
     447             : 
     448             : static sign_req_t *
     449             : sign_map_insert( ctx_t *                 ctx,
     450             :                  fd_repair_msg_t const * msg,
     451           0 :                  pong_data_t const     * opt_pong_data ) {
     452             : 
     453             :   /* Check if there is any space for a new pending sign request. Should never fail as long as credit management is working. */
     454           0 :   if( FD_UNLIKELY( fd_signs_map_key_cnt( ctx->signs_map )==fd_signs_map_key_max( ctx->signs_map ) ) ) return NULL;
     455             : 
     456           0 :   sign_req_t * pending = fd_signs_map_insert( ctx->signs_map, ctx->pending_key_next++ );
     457           0 :   if( FD_UNLIKELY( !pending ) ) return NULL; /* Not possible, unless the same key is used twice. */
     458           0 :   pending->msg    = *msg;
     459           0 :   pending->buflen = fd_repair_sz( msg );
     460           0 :   if( FD_UNLIKELY( opt_pong_data ) ) pending->pong_data = *opt_pong_data;
     461           0 :   return pending;
     462           0 : }
     463             : 
     464             : static int
     465             : sign_map_remove( ctx_t * ctx,
     466           0 :                  ulong   key ) {
     467           0 :   sign_req_t * pending = fd_signs_map_query( ctx->signs_map, key, NULL );
     468           0 :   if( FD_UNLIKELY( !pending ) ) return -1;
     469           0 :   fd_signs_map_remove( ctx->signs_map, pending );
     470           0 :   return 0;
     471           0 : }
     472             : 
     473             : static void
     474             : send_packet( ctx_t             * ctx,
     475             :              fd_stem_context_t * stem,
     476             :              uint                dst_ip_addr,
     477             :              ushort              dst_port,
     478             :              uint                src_ip_addr,
     479             :              uchar const *       payload,
     480             :              ulong               payload_sz,
     481           0 :              ulong               tsorig ) {
     482           0 :   ctx->metrics->send_pkt_cnt++;
     483           0 :   uchar * packet = fd_chunk_to_laddr( ctx->net_out_ctx->mem, ctx->net_out_ctx->chunk );
     484           0 :   fd_ip4_udp_hdrs_t * hdr = (fd_ip4_udp_hdrs_t *)packet;
     485           0 :   *hdr = *ctx->intake_hdr;
     486             : 
     487           0 :   fd_ip4_hdr_t * ip4 = hdr->ip4;
     488           0 :   ip4->saddr       = src_ip_addr;
     489           0 :   ip4->daddr       = dst_ip_addr;
     490           0 :   ip4->net_id      = fd_ushort_bswap( ctx->net_id++ );
     491           0 :   ip4->check       = 0U;
     492           0 :   ip4->net_tot_len = fd_ushort_bswap( (ushort)(payload_sz + sizeof(fd_ip4_hdr_t)+sizeof(fd_udp_hdr_t)) );
     493           0 :   ip4->check       = fd_ip4_hdr_check_fast( ip4 );
     494             : 
     495           0 :   fd_udp_hdr_t * udp = hdr->udp;
     496           0 :   udp->net_dport = dst_port;
     497           0 :   udp->net_len   = fd_ushort_bswap( (ushort)(payload_sz + sizeof(fd_udp_hdr_t)) );
     498           0 :   fd_memcpy( packet+sizeof(fd_ip4_udp_hdrs_t), payload, payload_sz );
     499           0 :   hdr->udp->check = 0U;
     500             : 
     501           0 :   ulong tspub     = fd_frag_meta_ts_comp( fd_tickcount() );
     502           0 :   ulong sig       = fd_disco_netmux_sig( dst_ip_addr, dst_port, dst_ip_addr, DST_PROTO_OUTGOING, sizeof(fd_ip4_udp_hdrs_t) );
     503           0 :   ulong packet_sz = payload_sz + sizeof(fd_ip4_udp_hdrs_t);
     504           0 :   ulong chunk     = ctx->net_out_ctx->chunk;
     505           0 :   fd_stem_publish( stem, ctx->net_out_ctx->idx, sig, chunk, packet_sz, 0UL, tsorig, tspub );
     506           0 :   ctx->net_out_ctx->chunk = fd_dcache_compact_next( chunk, packet_sz, ctx->net_out_ctx->chunk0, ctx->net_out_ctx->wmark );
     507           0 : }
     508             : 
     509             : /* Returns a sign_out context with max available credits.
     510             :    If no sign_out context has available credits, returns NULL. */
     511             : static out_ctx_t *
     512           0 : sign_avail_credits( ctx_t * ctx ) {
     513           0 :   out_ctx_t * sign_out = NULL;
     514           0 :   ulong max_credits = 0;
     515           0 :   for( uint i = 0; i < ctx->repair_sign_cnt; i++ ) {
     516           0 :     if( ctx->repair_sign_out_ctx[i].credits > max_credits ) {
     517           0 :       max_credits =  ctx->repair_sign_out_ctx[i].credits;
     518           0 :       sign_out    = &ctx->repair_sign_out_ctx[i];
     519           0 :     }
     520           0 :   }
     521           0 :   return sign_out;
     522           0 : }
     523             : 
     524             : /* Prepares the signing preimage and publishes a signing request that
     525             :    will be signed asynchronously by the sign tile.  The signed data will
     526             :    be returned via dcache as a frag. */
     527             : static void
     528             : fd_repair_send_sign_request( ctx_t                 * ctx,
     529             :                              out_ctx_t             * sign_out,
     530             :                              fd_repair_msg_t const * msg,
     531           0 :                              pong_data_t     const * opt_pong_data ) {
     532             : 
     533           0 :   if( FD_UNLIKELY( ctx->halt_signing ) ) FD_LOG_CRIT(( "can't dispatch sign requests while halting signing" ));
     534             : 
     535             :   /* New sign request */
     536           0 :   sign_req_t * pending = sign_map_insert( ctx, msg, opt_pong_data );
     537           0 :   if( FD_UNLIKELY( !pending ) ) return;
     538             : 
     539           0 :   ulong   sig         = 0;
     540           0 :   ulong   preimage_sz = 0;
     541           0 :   uchar * dst         = fd_chunk_to_laddr( sign_out->mem, sign_out->chunk );
     542             : 
     543           0 :   if( FD_UNLIKELY( msg->kind == FD_REPAIR_KIND_PONG ) ) {
     544           0 :     uchar pre_image[FD_REPAIR_PONG_PREIMAGE_SZ];
     545           0 :     preimage_pong( &opt_pong_data->hash, pre_image );
     546           0 :     preimage_sz = FD_REPAIR_PONG_PREIMAGE_SZ;
     547           0 :     fd_memcpy( dst, pre_image, preimage_sz );
     548           0 :     sig = ((ulong)pending->key << 32) | (uint)FD_KEYGUARD_SIGN_TYPE_SHA256_ED25519;
     549           0 :   } else {
     550             :     /* Sign and prepare the message directly into the pending buffer */
     551           0 :     uchar * preimage = preimage_req( &pending->msg, &preimage_sz );
     552           0 :     fd_memcpy( dst, preimage, preimage_sz );
     553           0 :     sig = ((ulong)pending->key << 32) | (uint)FD_KEYGUARD_SIGN_TYPE_ED25519;
     554           0 :   }
     555             : 
     556           0 :   fd_stem_publish( ctx->stem, sign_out->idx, sig, sign_out->chunk, preimage_sz, 0UL, 0UL, 0UL );
     557           0 :   sign_out->chunk = fd_dcache_compact_next( sign_out->chunk, preimage_sz, sign_out->chunk0, sign_out->wmark );
     558             : 
     559           0 :   ctx->metrics->sent_pkt_types[metric_index[msg->kind]]++;
     560           0 :   sign_out->credits--;
     561           0 : }
     562             : 
     563             : static inline int
     564             : before_frag( ctx_t * ctx,
     565             :              ulong   in_idx,
     566             :              ulong   seq FD_PARAM_UNUSED,
     567           0 :              ulong   sig ) {
     568           0 :   uint in_kind = ctx->in_kind[ in_idx ];
     569           0 :   if( FD_LIKELY  ( in_kind==IN_KIND_NET   ) ) return fd_disco_netmux_sig_proto( sig )!=DST_PROTO_REPAIR;
     570           0 :   if( FD_UNLIKELY( in_kind==IN_KIND_SHRED ) ) return fd_int_if( fd_forest_root_slot( ctx->forest )==ULONG_MAX, -1, 0 ); /* not ready to read frag */
     571           0 :   if( FD_UNLIKELY( in_kind==IN_KIND_GOSSIP ) ) {
     572           0 :     return sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO &&
     573           0 :            sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE;
     574           0 :   }
     575           0 :   if( FD_UNLIKELY( in_kind==IN_KIND_REPLAY ) ) return sig!=REPLAY_SIG_REASM_EVICTED;
     576           0 :   return 0;
     577           0 : }
     578             : 
     579             : static void
     580             : during_frag( ctx_t * ctx,
     581             :              ulong   in_idx,
     582             :              ulong   seq FD_PARAM_UNUSED,
     583             :              ulong   sig,
     584             :              ulong   chunk,
     585             :              ulong   sz,
     586           0 :              ulong   ctl ) {
     587           0 :   ctx->skip_frag = 0;
     588             : 
     589           0 :   uint             in_kind =  ctx->in_kind[ in_idx ];
     590           0 :   in_ctx_t const * in_ctx  = &ctx->in_links[ in_idx ];
     591           0 :   ctx->chunk = chunk;
     592             : 
     593           0 :   if( FD_UNLIKELY( in_kind==IN_KIND_NET ) ) {
     594           0 :     ulong hdr_sz = fd_disco_netmux_sig_hdr_sz( sig );
     595           0 :     FD_TEST( hdr_sz <= sz ); /* Should be ensured by the net tile */
     596           0 :     uchar const * dcache_entry = fd_net_rx_translate_frag( &in_ctx->net_rx, chunk, ctl, sz );
     597           0 :     fd_memcpy( ctx->net_buf, dcache_entry, sz );
     598           0 :     return;
     599           0 :   }
     600             : 
     601           0 :   if( FD_UNLIKELY( in_kind==IN_KIND_GENESIS ) ) {
     602           0 :     FD_TEST( sizeof(fd_genesis_meta_t)<=sig );
     603           0 :     return;
     604           0 :   }
     605             : 
     606           0 :   if( FD_UNLIKELY( sz!=0UL && ( chunk<in_ctx->chunk0 || chunk>in_ctx->wmark || sz>in_ctx->mtu ) ) )
     607           0 :     FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu] in kind %u", chunk, sz, in_ctx->chunk0, in_ctx->wmark, in_kind ));
     608             : 
     609           0 :   if( FD_UNLIKELY( in_kind==IN_KIND_SNAP ) ) {
     610           0 :     if( FD_UNLIKELY( fd_ssmsg_sig_message( sig )!=FD_SSMSG_DONE ) ) ctx->snap_out_chunk = chunk;
     611           0 :     return;
     612           0 :   }
     613             : 
     614           0 :   if( FD_UNLIKELY( in_kind==IN_KIND_SIGN ) ) {
     615             :     /* sign_repair is unreliable, so we copy the frag for convention.
     616             :        Theoretically impossible to overrun. */
     617           0 :     uchar const * dcache_entry = fd_chunk_to_laddr_const( in_ctx->mem, chunk );
     618           0 :     fd_memcpy( ctx->sign_buf, dcache_entry, sz );
     619           0 :     return;
     620           0 :   }
     621           0 : }
     622             : 
     623             : static inline void
     624             : after_snap( ctx_t * ctx,
     625             :                  ulong                  sig,
     626           0 :                  uchar const          * chunk ) {
     627           0 :   if( FD_UNLIKELY( fd_ssmsg_sig_message( sig )!=FD_SSMSG_DONE ) ) return;
     628           0 :   fd_snapshot_manifest_t * manifest = (fd_snapshot_manifest_t *)chunk;
     629             : 
     630           0 :   fd_forest_init( ctx->forest, manifest->slot );
     631           0 : }
     632             : 
     633             : static inline void
     634           0 : after_gossip( ctx_t * ctx, fd_gossip_update_message_t const * msg, ulong sig ) {
     635           0 :   switch( sig ) {
     636           0 :     case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE: {
     637           0 :       fd_policy_peer_remove( ctx->policy, fd_type_pun_const( msg->origin ) );
     638           0 :       break;
     639           0 :     }
     640           0 :     case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO: {
     641           0 :       fd_gossip_contact_info_t const * contact_info = msg->contact_info->value;
     642           0 :       fd_ip4_port_t repair_peer;
     643           0 :       repair_peer.addr = contact_info->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_SERVE_REPAIR ].is_ipv6 ? 0U : contact_info->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_SERVE_REPAIR ].ip4;
     644           0 :       repair_peer.port = contact_info->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_SERVE_REPAIR ].port;
     645           0 :       if( FD_UNLIKELY( !repair_peer.addr || !repair_peer.port ) ) return;
     646           0 :       fd_policy_peer_t const * peer = fd_policy_peer_upsert( ctx->policy, fd_type_pun_const( msg->origin ), &repair_peer );
     647           0 :       if( FD_LIKELY( peer && !fd_signs_queue_full( ctx->pong_queue ) ) ) {
     648             :         /* The repair process uses a Ping-Pong protocol that incurs one
     649             :            round-trip time (RTT) for the initmial repair request.  To
     650             :            optimize this, we proactively send a placeholder repair request
     651             :            as soon as we receive a peer's contact information for the first
     652             :            time, effectively prepaying the RTT cost. */
     653           0 :         fd_repair_msg_t * init = fd_repair_shred( ctx->protocol, fd_type_pun_const( msg->origin ), (ulong)fd_clock_tile_now( ctx->clock )/1000000UL, 0, 0, 0 );
     654           0 :         fd_signs_queue_push( ctx->pong_queue, (sign_pending_t){ .msg = *init } );
     655           0 :       }
     656           0 :       break;
     657           0 :     }
     658           0 :     default: FD_LOG_ERR(( "bad gossip sig %lu", sig ));
     659           0 :   }
     660           0 : }
     661             : 
     662             : static inline void
     663             : after_sign( ctx_t             * ctx,
     664             :             ulong               in_idx,
     665             :             ulong               sig,
     666           0 :             fd_stem_context_t * stem ) {
     667           0 :   ulong pending_key = sig >> 32;
     668             :   /* Look up the pending request. Since the repair_sign links are
     669             :      reliable, the incoming sign_repair fragments represent a complete
     670             :      set of the previously sent outgoing messages. However, with
     671             :      multiple sign tiles, the responses may arrive interleaved. */
     672             : 
     673             :   /* Find which sign tile sent this response and increment its credits */
     674           0 :   for( uint i = 0; i < ctx->repair_sign_cnt; i++ ) {
     675           0 :     if( ctx->repair_sign_out_ctx[i].in_idx == in_idx ) {
     676           0 :       if( FD_LIKELY( ctx->repair_sign_out_ctx[i].credits < ctx->repair_sign_out_ctx[i].max_credits ) ) ctx->repair_sign_out_ctx[i].credits++;
     677           0 :       break;
     678           0 :     }
     679           0 :   }
     680             : 
     681           0 :   sign_req_t * pending_ = fd_signs_map_query( ctx->signs_map, pending_key, NULL );
     682           0 :   if( FD_UNLIKELY( !pending_ ) ) FD_LOG_CRIT(( "No pending request found for key %lu", pending_key )); /* implies either bad programmer error or something happened with sign tile */
     683             : 
     684           0 :   sign_req_t   pending[1] = { *pending_ }; /* Make a copy of the pending request so we can sign_map_remove immediately. */
     685           0 :   sign_map_remove( ctx, pending_key );
     686             : 
     687             :   /* This is a pong message */
     688           0 :   if( FD_UNLIKELY( pending->msg.kind == FD_REPAIR_KIND_PONG ) ) {
     689           0 :     fd_policy_peer_t * peer = fd_policy_peer_query( ctx->policy, &pending->pong_data.key );
     690           0 :     if( FD_LIKELY( peer && peer->ping ) ) peer->ping--; /* prevent underflow if the peer was removed/readded */
     691             : 
     692           0 :     fd_memcpy( pending->msg.pong.sig, ctx->sign_buf, 64UL );
     693           0 :     send_packet( ctx, stem, pending->pong_data.peer_addr.addr, pending->pong_data.peer_addr.port, pending->pong_data.daddr, pending->buf, fd_repair_sz( &pending->msg ), fd_frag_meta_ts_comp( fd_tickcount() ) );
     694           0 :     return;
     695           0 :   }
     696             : 
     697             :   /* Inject the signature into the pending request */
     698           0 :   fd_memcpy( pending->buf + 4, ctx->sign_buf, 64UL );
     699           0 :   uint  src_ip4 = 0U;
     700             : 
     701             :   /* This is a warmup message */
     702           0 :   if( FD_UNLIKELY( pending->msg.kind == FD_REPAIR_KIND_SHRED && pending->msg.shred.slot == 0 ) ) {
     703           0 :     fd_policy_peer_t * peer = fd_policy_peer_query( ctx->policy, &pending->msg.shred.to );
     704           0 :     if( FD_UNLIKELY( peer ) ) send_packet( ctx, stem, peer->ip4, peer->port, src_ip4, pending->buf, pending->buflen, fd_frag_meta_ts_comp( fd_tickcount() ) );
     705           0 :     else { /* This is a warmup request for a peer that is no longer active.  There's no reason to pick another peer for a warmup rq, so just drop it. */ }
     706           0 :     return;
     707           0 :   }
     708             : 
     709             :   /* This is a regular repair shred request
     710             : 
     711             :      We need to ensure we always send out any shred requests we have,
     712             :      because policy_next has no way to revisit a shred.  But the fact
     713             :      that peers can drop out of the peer list makes this complicated.
     714             :      If the peer is still there (common), it's fine.  If the peer is not
     715             :      there, we can add this request to the inflights table, pretend
     716             :      we've sent it and let the inflight timeout request it down the
     717             :      line. */
     718             : 
     719           0 :   fd_policy_peer_t * active = fd_policy_peer_query( ctx->policy, &pending->msg.shred.to );
     720           0 :   if( FD_UNLIKELY( !active ) ) {
     721             :      /* Already added to the inflights table, pretend we've sent it
     722             :         and let the inflight timeout request it down the line. */
     723           0 :     return;
     724           0 :   }
     725             :   /* Happy path - all is well, our peer didn't drop out from beneath us. */
     726           0 :   if( FD_UNLIKELY( pending->msg.kind == FD_REPAIR_KIND_ORPHAN ) ) ctx->metrics->last_requested_orphan = pending->msg.orphan.slot;
     727           0 :   else                                                            ctx->metrics->last_requested_slot   = pending->msg.shred.slot;
     728             : 
     729           0 :   send_packet( ctx, stem, active->ip4, active->port, src_ip4, pending->buf, pending->buflen, fd_frag_meta_ts_comp( fd_tickcount() ) );
     730           0 : }
     731             : 
     732             : static int
     733           0 : blk_insert_check( ctx_t * ctx, fd_forest_blk_t * new_blk, ulong new_slot, ulong evicted ) {
     734           0 :   if( FD_UNLIKELY( !new_blk ) ) {
     735           0 :     ctx->metrics->blk_failed_insert++;
     736           0 :     ctx->metrics->slot_failed_insert = new_slot;
     737           0 :     return 0;
     738           0 :   } else {
     739           0 :     if( FD_UNLIKELY( evicted != ULONG_MAX ) ) {
     740           0 :       ctx->metrics->blk_evicted++;
     741           0 :       ctx->metrics->slot_evicted    = evicted;
     742           0 :       ctx->metrics->slot_evicted_by = new_slot;
     743           0 :     }
     744           0 :     return 1;
     745           0 :   }
     746           0 : }
     747             : 
     748           0 : static inline int shred_src( ulong sig ) {
     749           0 :   uint sig_src = fd_shred_sig_src( sig );
     750           0 :   switch( sig_src ) {
     751           0 :     case SHRED_SIG_SRC_TURBINE:
     752           0 :       return SHRED_SRC_TURBINE;
     753           0 :     case SHRED_SIG_SRC_RECONSTRUCTED:
     754           0 :       return SHRED_SRC_RECOVERED;
     755           0 :     case SHRED_SIG_SRC_REPAIR:
     756           0 :       return SHRED_SRC_REPAIR;
     757           0 :     case SHRED_SIG_SRC_BAD_REPAIR:
     758           0 :       return SHRED_SRC_TURBINE; /* like fec_resolver, treat bad repair shreds as turbine shreds */
     759           0 :     case SHRED_SIG_SRC_LEADER:
     760           0 :       return SHRED_SRC_LEADER;
     761           0 :     default: FD_LOG_CRIT((  "bad shred sig src %u", sig_src ));
     762           0 :   }
     763           0 : }
     764             : 
     765             : static inline void
     766             : after_shred( ctx_t      * ctx,
     767             :              ulong        sig,
     768             :              fd_shred_t * shred,
     769             :              ulong        nonce,
     770             :              fd_hash_t *  mr,
     771           0 :              fd_hash_t *  cmr ) {
     772             : 
     773             :   /* we don't want to add a slot to the forest that chains to a slot
     774             :      older than root, to avoid filling forest up with junk.
     775             :      Especially if we are close to full and we are having trouble
     776             :      rooting, we can't rely on publishing to prune these useless
     777             :      subtrees. TODO: do the same with reasm/store/shred? */
     778             : 
     779           0 :   if( FD_UNLIKELY( shred->slot <= fd_forest_root_slot( ctx->forest ) ||
     780           0 :                    shred->slot - shred->data.parent_off < fd_forest_root_slot( ctx->forest ) ) ) {
     781           0 :     ctx->metrics->old_shred++;
     782           0 :     return;
     783           0 :   }
     784             : 
     785             :   /* Insert the shred sig (shared by all shred members in the FEC set)
     786             :       into the map. */
     787           0 :   int is_code = fd_shred_is_code( fd_shred_type( shred->variant ) );
     788           0 :   int src     = shred_src( sig );
     789             : 
     790           0 :   if( FD_LIKELY( !is_code ) ) {
     791           0 :     long rtt = 0;
     792           0 :     fd_pubkey_t peer;
     793             : 
     794           0 :     int slot_complete = !!(shred->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE);
     795           0 :     int ref_tick      = shred->data.flags & FD_SHRED_DATA_REF_TICK_MASK;
     796           0 :     ulong evicted     = ULONG_MAX;
     797           0 :     fd_forest_blk_t * blk = fd_forest_blk_insert( ctx->forest, shred->slot, shred->slot - shred->data.parent_off, &evicted );
     798           0 :     if( FD_UNLIKELY( !blk_insert_check( ctx, blk, shred->slot, evicted ) ) ) return;
     799             : 
     800           0 :     if( FD_LIKELY( fd_forest_data_shred_insert( ctx->forest, shred->slot, shred->slot - shred->data.parent_off, shred->idx, shred->fec_set_idx, slot_complete, ref_tick, src, mr, cmr ) ) ) {
     801           0 :       if( FD_UNLIKELY( src == SHRED_SRC_REPAIR && ( rtt = fd_inflights_request_match( ctx->inflights, nonce, shred->slot, shred->idx, &peer ) ) > 0 ) ) {
     802           0 :         fd_policy_peer_response_update( ctx->policy, &peer, rtt );
     803           0 :         fd_histf_sample( ctx->metrics->response_latency, (ulong)rtt );
     804           0 :       }
     805           0 :     }
     806           0 :   } else {
     807           0 :     fd_forest_code_shred_insert( ctx->forest, shred->slot, shred->idx );
     808           0 :   }
     809           0 : }
     810             : 
     811             : /* Kicks off the chained merkle verification starting at a slot with
     812             :    a confirmed, canonical block_id.  Either finishes successfully and
     813             :    returns early, or detects an incorrect FEC set and clears it.  In
     814             :    this case the verification is paused and state is saved at where
     815             :    it left off.  Verification can be re-triggered in after_fec as well. */
     816             : static inline void
     817             : check_confirmed( ctx_t           * ctx,
     818             :                  fd_forest_blk_t * blk,
     819           0 :                  fd_hash_t const * confirmed_bid ) {
     820             : 
     821           0 :   if( FD_LIKELY( !blk->chain_confirmed && blk->complete_idx != UINT_MAX ) ) {
     822             :     /* The above conditions say that all the shreds of the block have arrived. */
     823           0 :     fd_forest_blk_t * bad_blk = fd_forest_fec_chain_verify( ctx->forest, blk, confirmed_bid );
     824           0 :     if( FD_LIKELY( !bad_blk ) ) {
     825             :       /* chain verified successfully from blk to as far as we have fec data */
     826           0 :       return;
     827           0 :     }
     828             : 
     829           0 :     uint bad_fec_idx = fd_forest_merkle_last_incorrect_idx( bad_blk );
     830             :     /* bad_fec_idx UINT_MAX implies this slot is fully validated.  Not
     831             :        possible here because that implies bad_blk is NULL and would
     832             :        early exit above. */
     833           0 :     FD_TEST( bad_fec_idx != UINT_MAX );
     834             : 
     835           0 :     fd_hash_t const * expected = (bad_fec_idx == bad_blk->complete_idx - (FD_FEC_SHRED_CNT - 1)) ? &bad_blk->confirmed_bid : &bad_blk->merkle_roots[(bad_fec_idx / 32) + 1].cmr;
     836             : 
     837           0 :     FD_BASE58_ENCODE_32_BYTES( confirmed_bid->uc,                             confirmed_bid_b58 );
     838           0 :     FD_BASE58_ENCODE_32_BYTES( expected->uc,                                  expected_mr );
     839           0 :     FD_BASE58_ENCODE_32_BYTES( bad_blk->merkle_roots[bad_fec_idx / 32].mr.uc, recorded_mr );
     840             : 
     841           0 :     FD_LOG_WARNING(( "[%s] slot %lu block_id %s confirmation detected incorrect FECs. bad FEC is slot %lu fec set %u. expected mr (%s) != recorded mr (%s)",
     842           0 :                        __func__,
     843           0 :                        blk->slot,
     844           0 :                        confirmed_bid_b58,
     845           0 :                        bad_blk->slot,
     846           0 :                        bad_fec_idx,
     847           0 :                        expected_mr,
     848           0 :                        recorded_mr ));
     849             : 
     850           0 :     ctx->metrics->failed_chain_verify_cnt++;
     851           0 :     ctx->metrics->failed_chain_verify_slot = bad_blk->slot;
     852             : 
     853             :     /* If we have a bad block, we need to dump and repair backwards from
     854             :        the point where the merkle root is incorrect.
     855             :        We start only by dumping the last incorrect FEC. It's possible that
     856             :        this is the only incorrect one.  If it isn't though, when the slot
     857             :        recompletes, this function will trigger again and we will dump the
     858             :        second to last incorrect FEC. */
     859             : 
     860           0 :     fd_forest_fec_clear( ctx->forest, bad_blk->slot, bad_fec_idx, FD_FEC_SHRED_CNT - 1 );
     861           0 :   }
     862           0 : }
     863             : 
     864             : /* Returns 1 if the fec is guaranteed invalid, 0 otherwise. */
     865             : static inline int
     866             : after_fec( ctx_t      * ctx,
     867             :            fd_shred_t * shred,
     868             :            fd_hash_t  * mr,
     869           0 :            fd_hash_t  * cmr ) {
     870             : 
     871             :   /* When this is a FEC completes msg, it is implied that all the
     872             :      other shreds in the FEC set can also be inserted.  Shred inserts
     873             :      into the forest are idempotent so it is fine to insert the same
     874             :      shred multiple times. */
     875             : 
     876           0 :   int slot_complete = !!( shred->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE );
     877           0 :   int ref_tick      = shred->data.flags & FD_SHRED_DATA_REF_TICK_MASK;
     878             : 
     879             :   /* Similar to after_shred, do not insert a slot that chains to a slot older than root */
     880           0 :   if( FD_UNLIKELY( shred->slot <= fd_forest_root_slot( ctx->forest ) ||
     881           0 :                    shred->slot - shred->data.parent_off < fd_forest_root_slot( ctx->forest ) ) ) return 0;
     882             : 
     883           0 :   ulong evicted  = ULONG_MAX;
     884           0 :   fd_forest_blk_t * ele = fd_forest_blk_insert( ctx->forest, shred->slot, shred->slot - shred->data.parent_off, &evicted );
     885           0 :   if( FD_UNLIKELY( !blk_insert_check( ctx, ele, shred->slot, evicted ) ) ) return 0;
     886           0 :   if( FD_UNLIKELY( !fd_forest_fec_insert( ctx->forest, shred->slot, shred->slot - shred->data.parent_off, shred->idx, shred->fec_set_idx, slot_complete, ref_tick, mr, cmr ) ) ) return 1;
     887             : 
     888             :   /* metrics for completed slots */
     889           0 :   if( FD_UNLIKELY( ele->complete_idx != UINT_MAX && ele->buffered_idx==ele->complete_idx ) ) {
     890           0 :     long now = fd_tickcount();
     891           0 :     long start_ts = ele->first_req_ts == 0 || ele->slot >= ctx->turbine_slot0 ? ele->first_shred_ts : ele->first_req_ts;
     892           0 :     ulong duration_ticks = (ulong)(now - start_ts);
     893           0 :     fd_histf_sample( ctx->metrics->slot_compl_time, duration_ticks );
     894           0 :     fd_repair_metrics_add_slot( ctx->slot_metrics, ele->slot, start_ts, now, ele->repair_cnt, ele->turbine_cnt );
     895             :     /* Note: this log does not imply that the slot is fully executable.
     896             :        It's possible that we have a slot that doesn't chain verify,
     897             :        which could be un-executable. */
     898           0 :     FD_BASE58_ENCODE_32_BYTES( ele->merkle_roots[ele->complete_idx / 32].mr.uc, block_id );
     899           0 :     FD_BASE58_ENCODE_32_BYTES( mr->uc, fec_mr );
     900           0 :     FD_LOG_INFO(( "[%s] slot is complete %lu. num_data_shreds: %u, num_repaired: %u, num_turbine: %u, num_recovered: %u, duration: %.2f ms. last recvd fec: %u, mr %s. current block_id: %s",
     901           0 :                     __func__,
     902           0 :                     ele->slot,
     903           0 :                     ele->complete_idx + 1,
     904           0 :                     ele->repair_cnt,
     905           0 :                     ele->turbine_cnt,
     906           0 :                     ele->recovered_cnt,
     907           0 :                     (double)fd_metrics_convert_ticks_to_nanoseconds(duration_ticks) / 1e6,
     908           0 :                     shred->fec_set_idx,
     909           0 :                     fec_mr,
     910           0 :                     block_id ));
     911           0 :   }
     912             : 
     913             :   /* re-trigger continuation of chained merkle verification if slot is
     914             :      complete. TODO MOVE TO AFTER_SHRED? */
     915           0 :   fd_hash_t empty_mr = (fd_hash_t){ .ul = { 0, 0, 0, 0 } };
     916           0 :   if( FD_UNLIKELY( ele->buffered_idx == ele->complete_idx && !fd_hash_eq( &ele->confirmed_bid, &empty_mr ) ) ) {
     917           0 :     check_confirmed( ctx, ele, &ele->confirmed_bid /* if lowest_verified_fec is not UINT_MAX, confirmed_bid must be populated */ );
     918           0 :   }
     919           0 :   return 0;
     920           0 : }
     921             : 
     922             : static inline void
     923             : after_net( ctx_t * ctx,
     924           0 :            ulong   sz  ) {
     925           0 :   fd_eth_hdr_t * eth; fd_ip4_hdr_t * ip4; fd_udp_hdr_t * udp;
     926           0 :   uchar * data; ulong data_sz;
     927           0 :   if( FD_UNLIKELY( !fd_ip4_udp_hdr_strip( ctx->net_buf, sz, &data, &data_sz, &eth, &ip4, &udp ) ) ) {
     928           0 :     ctx->metrics->malformed_ping++;
     929           0 :     return;
     930           0 :   }
     931           0 :   fd_ip4_port_t peer_addr = { .addr=ip4->saddr, .port=udp->net_sport };
     932             : 
     933           0 :   fd_repair_ping_t ping[1];
     934           0 :   int err = fd_repair_ping_de( ping, data, data_sz );
     935           0 :   if( FD_UNLIKELY( err ) ) {
     936           0 :     ctx->metrics->malformed_ping++;
     937           0 :     return;
     938           0 :   }
     939             : 
     940           0 :   fd_policy_peer_t * peer = fd_policy_peer_query( ctx->policy, &ping->ping.from );
     941           0 :   if( FD_UNLIKELY( !peer ) ) {
     942           0 :     ctx->metrics->unknown_peer_ping++;
     943           0 :     return;
     944           0 :   }
     945           0 :   if( FD_UNLIKELY( peer->ping ) ) return;
     946           0 :   if( FD_UNLIKELY( fd_signs_queue_full( ctx->pong_queue ) ) ) return;
     947             : 
     948           0 :   fd_sha512_t sha[1];
     949           0 :   if( FD_UNLIKELY( FD_ED25519_SUCCESS != fd_ed25519_verify( ping->ping.hash.uc, 32UL, ping->ping.sig, ping->ping.from.uc, sha ) ) ) {
     950           0 :     ctx->metrics->fail_sigverify_ping++;
     951           0 :     return;
     952           0 :   }
     953             : 
     954             :   /* Any gossip peer can send a ping, but they are bounded to at most
     955             :      one ping in the queue so they can't evict others' pings without
     956             :      multiple gossip identities. */
     957             : 
     958           0 :   fd_repair_msg_t * pong = fd_repair_pong( ctx->protocol, &ping->ping.hash );
     959           0 :   fd_signs_queue_push( ctx->pong_queue, (sign_pending_t){ .msg = *pong, .pong_data = { .peer_addr = peer_addr, .hash = ping->ping.hash, .daddr = ip4->daddr, .key = ping->ping.from } } );
     960           0 :   peer->ping++;
     961           0 : }
     962             : 
     963             : static inline void
     964             : after_evict( ctx_t * ctx,
     965           0 :              fd_fec_evicted_t * evicted ) {
     966           0 :   fd_forest_fec_clear( ctx->forest, evicted->slot, evicted->fec_set_idx, FD_FEC_SHRED_CNT - 1 );
     967           0 : }
     968             : 
     969             : static inline void
     970             : after_tower( ctx_t *       ctx,
     971             :              ulong         sig,
     972           0 :              uchar const * chunk ) {
     973             : 
     974           0 :   switch( sig ) {
     975           0 :     case FD_TOWER_SIG_SLOT_DONE: {
     976           0 :       fd_tower_slot_done_t const * msg = (fd_tower_slot_done_t const *)fd_type_pun_const( chunk );
     977           0 :       if( FD_LIKELY( msg->root_slot!=ULONG_MAX && msg->root_slot > fd_forest_root_slot( ctx->forest ) ) ) fd_forest_publish( ctx->forest, msg->root_slot );
     978           0 :       break;
     979           0 :     }
     980           0 :     case FD_TOWER_SIG_SLOT_CONFIRMED: {
     981           0 :       fd_tower_slot_confirmed_t const * msg = (fd_tower_slot_confirmed_t const *)fd_type_pun_const( chunk );
     982           0 :       if( msg->slot > fd_forest_root_slot( ctx->forest ) && (msg->level >= FD_TOWER_SLOT_CONFIRMED_DUPLICATE ) ) {
     983           0 :         fd_forest_blk_t * blk = fd_forest_query( ctx->forest, msg->slot );
     984           0 :         if( FD_UNLIKELY( !blk ) ) {
     985             :           /* If we receive a confirmation for a slot we don't have,
     986             :               create a sentinel forest block that we can repair from. */
     987           0 :           ulong evicted = ULONG_MAX;
     988           0 :           blk = fd_forest_blk_insert( ctx->forest, msg->slot, ULONG_MAX, &evicted );
     989           0 :           FD_LOG_INFO(("[%s] creating sentinel for duplicate confirmed block %lu", __func__, msg->slot));
     990           0 :           if( FD_UNLIKELY( !blk_insert_check( ctx, blk, msg->slot, evicted ) ) ) return;
     991           0 :         }
     992             : 
     993             :         /* Confirm the block */
     994           0 :         blk->confirmed_bid = msg->block_id;
     995           0 :         check_confirmed( ctx, blk, &msg->block_id );
     996           0 :       }
     997           0 :       break;
     998           0 :     }
     999           0 :     default: return;
    1000           0 :   }
    1001           0 : }
    1002             : 
    1003             : static void
    1004             : after_frag( ctx_t *             ctx,
    1005             :             ulong               in_idx,
    1006             :             ulong               seq    FD_PARAM_UNUSED,
    1007             :             ulong               sig,
    1008             :             ulong               sz,
    1009             :             ulong               tsorig FD_PARAM_UNUSED,
    1010             :             ulong               tspub,
    1011           0 :             fd_stem_context_t * stem ) {
    1012           0 :   if( FD_UNLIKELY( ctx->skip_frag ) ) return;
    1013             : 
    1014           0 :   ctx->stem = stem;
    1015           0 :   in_ctx_t const * in_ctx  = &ctx->in_links[ in_idx ];
    1016           0 :   uint             in_kind = ctx->in_kind[ in_idx ];
    1017             : 
    1018           0 :   switch( in_kind ) {
    1019             :     /* Unreliable frags */
    1020           0 :     case IN_KIND_NET:  {
    1021           0 :       after_net( ctx, sz );
    1022           0 :       break;
    1023           0 :     }
    1024           0 :     case IN_KIND_SIGN: {
    1025           0 :       after_sign( ctx, in_idx, sig, stem );
    1026           0 :       break;
    1027           0 :     }
    1028             :     /* Reliable frags read directly from dcache */
    1029           0 :     case IN_KIND_SNAP: {
    1030           0 :       after_snap( ctx, sig, fd_chunk_to_laddr( ctx->in_links[ in_idx ].mem, ctx->snap_out_chunk ) );
    1031           0 :       break;
    1032           0 :     }
    1033           0 :     case IN_KIND_GENESIS: {
    1034           0 :       fd_genesis_meta_t const * meta = (fd_genesis_meta_t const *)fd_type_pun_const( fd_chunk_to_laddr( in_ctx->mem, ctx->chunk ) );
    1035           0 :       if( meta->bootstrap ) fd_forest_init( ctx->forest, 0 );
    1036           0 :       break;
    1037           0 :     }
    1038           0 :     case IN_KIND_GOSSIP: {
    1039           0 :       fd_gossip_update_message_t const * msg = (fd_gossip_update_message_t const *)fd_type_pun_const( fd_chunk_to_laddr( in_ctx->mem, ctx->chunk ) );
    1040           0 :       after_gossip( ctx, msg, sig );
    1041           0 :       break;
    1042           0 :     }
    1043           0 :     case IN_KIND_REPLAY: {
    1044           0 :       fd_replay_fec_evicted_t const * msg = (fd_replay_fec_evicted_t const *)fd_type_pun_const( fd_chunk_to_laddr( in_ctx->mem, ctx->chunk ) );
    1045           0 :       fd_forest_fec_clear( ctx->forest, msg->slot, msg->fec_set_idx, FD_FEC_SHRED_CNT - 1 );
    1046           0 :       break;
    1047           0 :     }
    1048           0 :     case IN_KIND_TOWER: {
    1049           0 :       after_tower( ctx, sig, fd_chunk_to_laddr( in_ctx->mem, ctx->chunk ) );
    1050           0 :       break;
    1051           0 :     }
    1052           0 :     case IN_KIND_SHRED: {
    1053             : 
    1054             :       /* There are 3 message types from shred:
    1055             :           1. resolver evict - incomplete FEC set is evicted by resolver
    1056             :           2. fec complete   - FEC set is completed by resolver. Also contains a shred.
    1057             :           3. shred          - new shred
    1058             : 
    1059             :           Msgs 2 and 3 have a shred header in the dcache.  Msg 1 is empty. */
    1060             : 
    1061           0 :       uint sig_src = fd_shred_sig_src( sig );
    1062           0 :       int  sig_res = fd_shred_sig_res( sig );
    1063             : 
    1064           0 :       if( FD_UNLIKELY( sig_src==SHRED_SIG_FEC_EVICTED ) ) {
    1065           0 :         fd_fec_evicted_t * evicted = (fd_fec_evicted_t *)fd_type_pun( fd_chunk_to_laddr( in_ctx->mem, ctx->chunk ) );
    1066           0 :         after_evict( ctx, evicted );
    1067           0 :         return;
    1068           0 :       }
    1069             : 
    1070           0 :       uchar * src = fd_chunk_to_laddr( in_ctx->mem, ctx->chunk );
    1071           0 :       fd_shred_base_t * shred_msg = (fd_shred_base_t *)fd_type_pun( src );
    1072           0 :       fd_shred_t      * shred     = &shred_msg->shred; /* completes & shred messages all have a shred header at the same offset (after merkle root) */
    1073             : 
    1074           0 :       if( FD_UNLIKELY( shred->slot > ctx->metrics->current_slot && sig_src == SHRED_SIG_SRC_TURBINE ) ) {
    1075           0 :         FD_LOG_INFO(( "[Turbine] slot: %lu, root: %lu", shred->slot, fd_forest_root_slot( ctx->forest ) ));
    1076           0 :         ctx->metrics->current_slot = shred->slot;
    1077           0 :       }
    1078             : 
    1079           0 :       if( FD_UNLIKELY( ctx->turbine_slot0 == ULONG_MAX && sig_src == SHRED_SIG_SRC_TURBINE ) ) {
    1080           0 :         ctx->turbine_slot0 = shred->slot;
    1081           0 :         fd_repair_metrics_set_turbine_slot0( ctx->slot_metrics, shred->slot );
    1082           0 :         fd_policy_set_turbine_slot0( ctx->policy, shred->slot );
    1083             : 
    1084             :         /* On first turbine shred, seed repair by queuing highest_shred
    1085             :            requests for slots between snapshot and turbine_slot0. This
    1086             :            bypasses forest entirely and dispatches directly via the sign
    1087             :            queue. Cap at half queue capacity to leave room for pongs. */
    1088           0 :         ulong root = fd_forest_root_slot( ctx->forest );
    1089           0 :         if( FD_LIKELY( root != ULONG_MAX && shred->slot > root ) ) {
    1090           0 :           ulong capacity = fd_signs_queue_max( ctx->pong_queue ) - fd_signs_queue_cnt( ctx->pong_queue );
    1091           0 :           ulong seed_cnt = fd_ulong_min( shred->slot-root, capacity/2 );
    1092           0 :           long  now_ms   = fd_clock_tile_now( ctx->clock )/(long)1e6;
    1093           0 :           for( ulong i=1; i<=seed_cnt; i++ ) {
    1094           0 :             ulong slot = root + i;
    1095           0 :             fd_pubkey_t const * peer = fd_policy_peer_select( ctx->policy );
    1096           0 :             if( FD_UNLIKELY( !peer ) ) break;
    1097           0 :             fd_repair_msg_t * msg = fd_repair_highest_shred( ctx->protocol, peer, (ulong)now_ms, 0, slot, 0 );
    1098           0 :             if( FD_LIKELY( msg ) )  fd_signs_queue_push( ctx->pong_queue, (sign_pending_t){ .msg = *msg } );
    1099           0 :           }
    1100           0 :         }
    1101           0 :       }
    1102             : 
    1103           0 :       if( FD_UNLIKELY( sig==SHRED_SIG_FEC_COMPLETE || sig==SHRED_SIG_FEC_COMPLETE_LEADER ) ) {
    1104           0 :         fd_fec_complete_t * complete_msg = (fd_fec_complete_t *)fd_type_pun( src );
    1105           0 :         int   invalid = after_fec( ctx, &complete_msg->last_shred_hdr, &complete_msg->merkle_root, &complete_msg->chained_merkle_root );
    1106           0 :         ulong fwd_sig = invalid ? REPAIR_SIG_FEC_INVALID : (sig==SHRED_SIG_FEC_COMPLETE_LEADER ? REPAIR_SIG_FEC_LEADER : REPAIR_SIG_FEC);
    1107             : 
    1108             :         /* indiscriminately forward FEC complete messages along to replay */
    1109           0 :         memcpy( fd_chunk_to_laddr( ctx->repair_out_ctx->mem, ctx->repair_out_ctx->chunk ), src, sz );
    1110           0 :         fd_stem_publish( ctx->stem, ctx->repair_out_ctx->idx, fwd_sig, ctx->repair_out_ctx->chunk, sz, 0UL, 0UL, tspub );
    1111           0 :         ctx->repair_out_ctx->chunk = fd_dcache_compact_next( ctx->repair_out_ctx->chunk, sz, ctx->repair_out_ctx->chunk0, ctx->repair_out_ctx->wmark );
    1112           0 :       } else if( FD_LIKELY( sig_res!=SHRED_SIG_RESULT_EQVOC ) ) {
    1113           0 :         fd_hash_t * cmr = (fd_hash_t *)fd_type_pun(shred_msg->shred_ + fd_shred_chain_off( shred->variant ));
    1114           0 :         after_shred( ctx, sig, shred, shred_msg->rnonce, &shred_msg->merkle_root, cmr );
    1115           0 :       }
    1116           0 :       return;
    1117           0 :     }
    1118           0 :     default: FD_LOG_ERR(( "bad in_kind %u", in_kind )); /* Should never reach here since before_frag should have filtered out any unexpected frags. */
    1119           0 :   }
    1120           0 : }
    1121             : 
    1122             : /* Defer a request by adding it to the outstanding inflights table so it
    1123             :    can be re-requested after a timeout window.  Nonce is 0 because these
    1124             :    are not real requests made to the network, and cannot be matched
    1125             :    by a shred response. */
    1126             : static void
    1127           0 : defer_inflight_request( ctx_t * ctx, ulong slot, ulong shred_idx ) {
    1128           0 :   fd_hash_t hash = { .ul[0] = 0 };
    1129           0 :   fd_inflight_key_t inflight_req = { .slot = slot, .shred_idx = shred_idx, .nonce = 0 };
    1130           0 :   if( FD_LIKELY( !fd_inflight_map_ele_query( ctx->inflights->map, &inflight_req, NULL, ctx->inflights->pool ) ) ) {
    1131           0 :     fd_inflights_request_insert( ctx->inflights, 0, &hash, slot, shred_idx );
    1132           0 :   }
    1133           0 : }
    1134             : 
    1135             : /* Should be called for any regular FD_REPAIR_KIND_SHRED request made. */
    1136             : static void
    1137           0 : record_inflight_request( ctx_t * ctx, ulong nonce, fd_pubkey_t const * peer, ulong slot, ulong shred_idx ) {
    1138           0 :   fd_inflights_request_insert( ctx->inflights, nonce, peer, slot, shred_idx );
    1139           0 :   fd_policy_peer_request_update( ctx->policy, peer );
    1140           0 : }
    1141             : 
    1142             : 
    1143             : /* Repair request invariants:
    1144             : 
    1145             :    Highest window index requests and orphan requests are much less
    1146             :    fragile than regular shred requests.  This is because we request
    1147             :    orphans and highest window index requests whenever we "need" them,
    1148             :    i.e. we fire and forget them.
    1149             : 
    1150             :    But regular shred requests are only iterated once per shred per slot.
    1151             :    This is for performance reasons - we don't want to iterate over the
    1152             :    forest more than necessary.  If we let knowledge of a regular shred
    1153             :    request get dropped, it's possible the request could get lost
    1154             :    forever.  This is why any regular shred request that is conceived
    1155             :    needs to be handled at the repair tile level.  All regular shred
    1156             :    requests must be added to the inflights outstanding table so it can
    1157             :    be re-requested after a timeout window.
    1158             : 
    1159             :    An inflight entry should only ever be permanently removed upon
    1160             :    receipt of a shred response.
    1161             : 
    1162             :    There are two methods through which we make regular shred requests:
    1163             :    1. policy_next
    1164             :    2. fd_inflights_request_pop
    1165             : 
    1166             :    In general, policy_next makes the first request for shred X, and
    1167             :    fd_inflights_request_pop makes all subsequent requests for shred X at
    1168             :    DEDUP_TIMEOUT intervals.  This is to give each request a fair chance
    1169             :    to be received, but also to avoid colliding nonces.  This is because
    1170             :    we want to be as accurate as possible when tracking per-peer response
    1171             :    latency.
    1172             : 
    1173             :    With eviction, it's possible for policy_next to make the same request
    1174             :    for shred X multiple times, in short timeout intervals.  Therefore we
    1175             :    need to have both policy_next and fd_inflights_request_pop requests
    1176             :    pass through the same dedup cache.  At the point of eviction, we
    1177             :    leave requests for that slot in the dedup cache and in the inflights
    1178             :    table. If an old request from before eviction happens to
    1179             :    dedup a request on the new insertion of the slot, these requests must
    1180             :    be queued up in inflights table so they can be re-requested after a
    1181             :    timeout window. */
    1182             : 
    1183             : static inline void
    1184             : after_credit( ctx_t *             ctx,
    1185             :               fd_stem_context_t * stem FD_PARAM_UNUSED,
    1186             :               int *               opt_poll_in FD_PARAM_UNUSED,
    1187           0 :               int *               charge_busy ) {
    1188           0 :   long now = fd_clock_tile_now( ctx->clock );
    1189             : 
    1190           0 :   if( FD_UNLIKELY( ctx->halt_signing ) ) {
    1191           0 :     *charge_busy = 1;
    1192           0 :     return;
    1193           0 :   }
    1194             : 
    1195             :   /* Verify that there is at least one sign tile with available credits.
    1196             :      If not, we can't send any requests and leave early. */
    1197           0 :   out_ctx_t * sign_out = sign_avail_credits( ctx );
    1198           0 :   if( FD_UNLIKELY( !sign_out ) ) {
    1199           0 :     ctx->metrics->sign_tile_unavail++;
    1200           0 :     return;
    1201           0 :   }
    1202             : 
    1203             :   /* If inflights is at capacity, then the only thing we can send is:
    1204             :      pongs, initial highest window index requests, or resend things that
    1205             :      are already inflight.  Any new requests that would cause an
    1206             :      inflight to be added to the queue must be deferred. */
    1207             : 
    1208           0 :   if( FD_UNLIKELY( !fd_signs_queue_empty( ctx->pong_queue ) ) ) {
    1209           0 :     sign_pending_t signable = fd_signs_queue_pop( ctx->pong_queue );
    1210           0 :     fd_repair_send_sign_request( ctx, sign_out, &signable.msg, signable.msg.kind == FD_REPAIR_KIND_PONG ? &signable.pong_data : NULL );
    1211           0 :     *charge_busy = 1;
    1212           0 :     return;
    1213           0 :   }
    1214             : 
    1215           0 :   if( FD_UNLIKELY( fd_inflights_should_drain( ctx->inflights, now ) ) ) {
    1216           0 :     ulong nonce; ulong slot; ulong shred_idx;
    1217           0 :     *charge_busy = 1;
    1218           0 :     fd_inflights_request_pop( ctx->inflights, &nonce, &slot, &shred_idx );
    1219             : 
    1220           0 :     fd_forest_blk_t * blk = fd_forest_query( ctx->forest, slot );
    1221           0 :     if( FD_UNLIKELY( blk && !fd_forest_blk_idxs_test( blk->idxs, shred_idx ) ) ) {
    1222           0 :       fd_pubkey_t const * peer = fd_policy_peer_select( ctx->policy );
    1223             : 
    1224           0 :       if( FD_UNLIKELY( !peer || fd_reqlim_next( ctx->dedup, fd_reqlim_key( FD_REPAIR_KIND_SHRED, slot, (uint)shred_idx ), now ) ) ) {
    1225             :         /* No peers available, park the request in inflights. */
    1226           0 :         defer_inflight_request( ctx, slot, shred_idx );
    1227           0 :       } else {
    1228           0 :         ctx->metrics->rerequest++;
    1229           0 :         nonce = fd_rnonce_ss_compute( ctx->repair_nonce_ss, 1, slot, (uint)shred_idx, now );
    1230           0 :         fd_repair_msg_t * msg = fd_repair_shred( ctx->protocol, peer, (ulong)now/(ulong)1e6, (uint)nonce, slot, shred_idx );
    1231           0 :         fd_repair_send_sign_request( ctx, sign_out, msg, NULL );
    1232           0 :         record_inflight_request( ctx, nonce, peer, slot, shred_idx ); /* Request is definitely a regular shred request. */
    1233           0 :         return;
    1234           0 :       }
    1235           0 :     }
    1236           0 :   }
    1237             : 
    1238           0 :   if( FD_UNLIKELY( fd_inflights_outstanding_free( ctx->inflights ) <= fd_signs_map_key_cnt( ctx->signs_map ) ) ) return; /* no new requests allowed */
    1239             : 
    1240           0 :   fd_repair_msg_t const * cout = fd_policy_next( ctx->policy, ctx->dedup, ctx->forest, ctx->protocol, now, ctx->metrics->current_slot, charge_busy );
    1241           0 :   if( FD_UNLIKELY( !cout ) ) return;
    1242             : 
    1243           0 :   if( ( cout->kind == FD_REPAIR_KIND_SHRED && fd_reqlim_next( ctx->dedup, fd_reqlim_key( FD_REPAIR_KIND_SHRED, cout->shred.slot, (uint)cout->shred.shred_idx ), now ) ) ) {
    1244             :     /* Here if policy_next is re-requesting a shred that's already been
    1245             :        requested. This could be happen during a race - imagine we make a
    1246             :        request for shred 0 in slot Y.  Then eviction causes slot Y to be
    1247             :        removed and then readded. policy_next will re-request shred 0,
    1248             :        but if we let it get dropped here, it's possible the request
    1249             :        could get lost forever. */
    1250           0 :     defer_inflight_request( ctx, cout->shred.slot, cout->shred.shred_idx );
    1251           0 :     return;
    1252           0 :   }
    1253             : 
    1254             :   /* finally, send the request made by policy */
    1255           0 :   fd_repair_send_sign_request( ctx, sign_out, cout, NULL );
    1256           0 :   if( FD_LIKELY( cout->kind == FD_REPAIR_KIND_SHRED ) ) record_inflight_request( ctx, cout->shred.nonce, &cout->shred.to, cout->shred.slot, cout->shred.shred_idx );
    1257           0 : }
    1258             : 
    1259             : static void
    1260           0 : signs_queue_update_identity( ctx_t * ctx ) {
    1261           0 :   ulong queue_cnt = fd_signs_queue_cnt( ctx->pong_queue );
    1262           0 :   for( ulong i=0UL; i<queue_cnt; i++ ) {
    1263           0 :     sign_pending_t signable = fd_signs_queue_pop( ctx->pong_queue );
    1264           0 :     switch( signable.msg.kind ) {
    1265           0 :       case FD_REPAIR_KIND_PONG:
    1266           0 :         memcpy( signable.msg.pong.from.uc, ctx->identity_public_key.uc, sizeof(fd_pubkey_t) );
    1267           0 :         break;
    1268           0 :       case FD_REPAIR_KIND_SHRED:
    1269           0 :         memcpy( signable.msg.shred.from.uc, ctx->identity_public_key.uc, sizeof(fd_pubkey_t) );
    1270           0 :         break;
    1271           0 :       case FD_REPAIR_KIND_HIGHEST_SHRED:
    1272           0 :         memcpy( signable.msg.highest_shred.from.uc, ctx->identity_public_key.uc, sizeof(fd_pubkey_t) );
    1273           0 :         break;
    1274           0 :       case FD_REPAIR_KIND_ORPHAN:
    1275           0 :         memcpy( signable.msg.orphan.from.uc, ctx->identity_public_key.uc, sizeof(fd_pubkey_t) );
    1276           0 :         break;
    1277           0 :       default:
    1278           0 :         FD_LOG_CRIT(( "Unhandled repair kind %u", signable.msg.kind ));
    1279           0 :         break;
    1280           0 :     }
    1281           0 :     fd_signs_queue_push( ctx->pong_queue, signable );
    1282           0 :   }
    1283           0 : }
    1284             : 
    1285             : static inline void
    1286           0 : during_housekeeping( ctx_t * ctx ) {
    1287           0 :   if( FD_UNLIKELY( fd_clock_tile_recal_due( ctx->clock ) ) ) fd_clock_tile_recal( ctx->clock );
    1288             : 
    1289             : # if DEBUG_LOGGING
    1290             :   long now = fd_log_wallclock();
    1291             :   if( FD_UNLIKELY( now - ctx->tsdebug > (long)10e9 ) ) {
    1292             :     fd_forest_print( ctx->forest );
    1293             :     ctx->tsdebug = fd_log_wallclock();
    1294             :   }
    1295             : # endif
    1296             : 
    1297           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
    1298           0 :     FD_LOG_DEBUG(( "keyswitch: unhalting" ));
    1299           0 :     FD_CHECK_CRIT( ctx->halt_signing, "state machine corruption" );
    1300           0 :     ctx->halt_signing = 0;
    1301           0 :     fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
    1302           0 :   }
    1303             : 
    1304           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
    1305             : 
    1306           0 :     if( !ctx->halt_signing ) {
    1307             :       /* At this point, stop sending new sign requests to the sign tile
    1308             :          and wait for all outstanding sign requests to be received back
    1309             :          from the sign tile.  We also need to update any pending
    1310             :          outgoing sign requests with the new identity key. */
    1311           0 :       FD_LOG_DEBUG(( "keyswitch: halting signing" ));
    1312           0 :       ctx->halt_signing = 1;
    1313           0 :       memcpy( ctx->identity_public_key.uc, ctx->keyswitch->bytes, 32UL );
    1314           0 :       ctx->protocol->identity_key = ctx->identity_public_key;
    1315           0 :       signs_queue_update_identity( ctx );
    1316           0 :     }
    1317             : 
    1318           0 :     if( fd_signs_map_key_cnt( ctx->signs_map )==0UL ) {
    1319             :       /* Once there are no more in flight sign requests, we are ready to
    1320             :          say that the keyswitch is completed. */
    1321           0 :       FD_LOG_DEBUG(( "keyswitch: completed, no more outstanding stale sign requests" ));
    1322           0 :       fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
    1323           0 :     }
    1324           0 :   }
    1325           0 : }
    1326             : 
    1327             : static void
    1328             : privileged_init( fd_topo_t const *      topo,
    1329           0 :                  fd_topo_tile_t const * tile ) {
    1330           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1331             : 
    1332           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1333           0 :   ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ctx_t), sizeof(ctx_t) );
    1334           0 :   fd_memset( ctx, 0, sizeof(ctx_t) );
    1335             : 
    1336           0 :   uchar const * identity_key = fd_keyload_load( tile->repair.identity_key_path, /* pubkey only: */ 1 );
    1337           0 :   fd_memcpy( ctx->identity_public_key.uc, identity_key, sizeof(fd_pubkey_t) );
    1338             : 
    1339           0 :   FD_TEST( fd_rng_secure( &ctx->repair_seed, sizeof(ulong) ) );
    1340             : 
    1341           0 :   ulong rnonce_ss_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "rnonce_ss" );
    1342           0 :   FD_TEST( rnonce_ss_id!=ULONG_MAX );
    1343           0 :   memcpy( ctx->repair_nonce_ss, fd_topo_obj_laddr( topo, rnonce_ss_id ), sizeof(fd_rnonce_ss_t) );
    1344           0 : }
    1345             : 
    1346             : static void
    1347             : unprivileged_init( fd_topo_t const *      topo,
    1348           0 :                    fd_topo_tile_t const * tile ) {
    1349           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1350             : 
    1351           0 :   ulong total_sign_depth = tile->repair.repair_sign_depth * tile->repair.repair_sign_cnt;
    1352           0 :   int   lg_sign_depth    = fd_ulong_find_msb( fd_ulong_pow2_up(total_sign_depth) ) + 1;
    1353             : 
    1354           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1355           0 :   ctx_t * ctx       = FD_SCRATCH_ALLOC_APPEND( l, alignof(ctx_t),            sizeof(ctx_t)                                                 );
    1356           0 :   ctx->protocol     = FD_SCRATCH_ALLOC_APPEND( l, fd_repair_align(),         fd_repair_footprint()                                         );
    1357           0 :   ctx->forest       = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_align(),         fd_forest_footprint( tile->repair.slot_max )                  );
    1358           0 :   ctx->policy       = FD_SCRATCH_ALLOC_APPEND( l, fd_policy_align(),         fd_policy_footprint( FD_REPAIR_PEER_MAX )                     );
    1359           0 :   ctx->dedup        = FD_SCRATCH_ALLOC_APPEND( l, fd_reqlim_align(),          fd_reqlim_footprint( FD_REQLIM_CACHE_MAX )                      );
    1360           0 :   ctx->inflights    = FD_SCRATCH_ALLOC_APPEND( l, fd_inflights_align(),      fd_inflights_footprint()                                      );
    1361           0 :   ctx->signs_map    = FD_SCRATCH_ALLOC_APPEND( l, fd_signs_map_align(),      fd_signs_map_footprint( lg_sign_depth )                       );
    1362           0 :   ctx->pong_queue   = FD_SCRATCH_ALLOC_APPEND( l, fd_signs_queue_align(),    fd_signs_queue_footprint()                                    );
    1363           0 :   ctx->slot_metrics = FD_SCRATCH_ALLOC_APPEND( l, fd_repair_metrics_align(), fd_repair_metrics_footprint()                                 );
    1364           0 :   ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
    1365           0 :   if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
    1366           0 :     FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
    1367             : 
    1368           0 :   ctx->protocol     = fd_repair_join        ( fd_repair_new        ( ctx->protocol,  &ctx->identity_public_key                                                      ) );
    1369           0 :   ctx->forest       = fd_forest_join        ( fd_forest_new        ( ctx->forest,    tile->repair.slot_max, ctx->repair_seed                                        ) );
    1370           0 :   ctx->policy       = fd_policy_join        ( fd_policy_new        ( ctx->policy,    FD_REPAIR_PEER_MAX, ctx->repair_seed, ctx->repair_nonce_ss ) );
    1371           0 :   ctx->dedup        = fd_reqlim_join        ( fd_reqlim_new        ( ctx->dedup,     FD_REQLIM_CACHE_MAX, ctx->repair_seed                      ) );
    1372           0 :   ctx->inflights    = fd_inflights_join     ( fd_inflights_new     ( ctx->inflights, ctx->repair_seed+1234UL                                                       ) );
    1373           0 :   ctx->signs_map    = fd_signs_map_join     ( fd_signs_map_new     ( ctx->signs_map, lg_sign_depth, 0UL                                                            ) );
    1374           0 :   ctx->pong_queue   = fd_signs_queue_join   ( fd_signs_queue_new   ( ctx->pong_queue                                                                               ) );
    1375           0 :   ctx->slot_metrics = fd_repair_metrics_join( fd_repair_metrics_new( ctx->slot_metrics                                                                             ) );
    1376             : 
    1377           0 :   ctx->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
    1378           0 :   FD_TEST( ctx->keyswitch );
    1379             : 
    1380           0 :   ctx->halt_signing = 0;
    1381             : 
    1382             :   /* Process in links */
    1383             : 
    1384           0 :   if( FD_UNLIKELY( tile->in_cnt > MAX_IN_LINKS ) ) FD_LOG_ERR(( "repair tile has too many input links" ));
    1385             : 
    1386           0 :   uint  sign_repair_in_idx[ MAX_SIGN_TILE_CNT ] = {0};
    1387           0 :   uint  sign_repair_idx  = 0;
    1388           0 :   ulong sign_link_depth  = 0;
    1389             : 
    1390           0 :   for( uint in_idx=0U; in_idx<(tile->in_cnt); in_idx++ ) {
    1391           0 :     fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ in_idx ] ];
    1392           0 :     if( 0==strcmp( link->name, "net_repair" ) ) {
    1393           0 :       ctx->in_kind[ in_idx ] = IN_KIND_NET;
    1394           0 :       fd_net_rx_bounds_init( &ctx->in_links[ in_idx ].net_rx, link->dcache );
    1395           0 :       continue;
    1396           0 :     } else if( 0==strcmp( link->name, "sign_repair" ) ) {
    1397           0 :       ctx->in_kind[ in_idx ]                  = IN_KIND_SIGN;
    1398           0 :       sign_repair_in_idx[ sign_repair_idx++ ] = in_idx;
    1399           0 :       sign_link_depth                         = link->depth;
    1400           0 :     }
    1401           0 :     else if( 0==strcmp( link->name, "gossip_out"   ) ) ctx->in_kind[ in_idx ] = IN_KIND_GOSSIP;
    1402           0 :     else if( 0==strcmp( link->name, "tower_out"    ) ) ctx->in_kind[ in_idx ] = IN_KIND_TOWER;
    1403           0 :     else if( 0==strcmp( link->name, "shred_out"    ) ) ctx->in_kind[ in_idx ] = IN_KIND_SHRED;
    1404           0 :     else if( 0==strcmp( link->name, "snapin_manif" ) ) ctx->in_kind[ in_idx ] = IN_KIND_SNAP;
    1405           0 :     else if( 0==strcmp( link->name, "genesi_out"   ) ) ctx->in_kind[ in_idx ] = IN_KIND_GENESIS;
    1406           0 :     else if( 0==strcmp( link->name, "replay_out"   ) ) ctx->in_kind[ in_idx ] = IN_KIND_REPLAY;
    1407           0 :     else FD_LOG_ERR(( "repair tile has unexpected input link %s", link->name ));
    1408             : 
    1409           0 :     ctx->in_links[ in_idx ].mem    = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
    1410           0 :     ctx->in_links[ in_idx ].chunk0 = fd_dcache_compact_chunk0( ctx->in_links[ in_idx ].mem, link->dcache );
    1411           0 :     ctx->in_links[ in_idx ].wmark  = fd_dcache_compact_wmark ( ctx->in_links[ in_idx ].mem, link->dcache, link->mtu );
    1412           0 :     ctx->in_links[ in_idx ].mtu    = link->mtu;
    1413             : 
    1414           0 :     FD_TEST( fd_dcache_compact_is_safe( ctx->in_links[in_idx].mem, link->dcache, link->mtu, link->depth ) );
    1415           0 :   }
    1416             : 
    1417           0 :   ctx->net_out_ctx->idx    = UINT_MAX;
    1418           0 :   ctx->repair_out_ctx->idx = UINT_MAX;
    1419           0 :   ctx->repair_sign_cnt   = 0;
    1420           0 :   ctx->sign_rrobin_idx   = 0;
    1421             : 
    1422           0 :   for( uint out_idx=0U; out_idx<(tile->out_cnt); out_idx++ ) {
    1423           0 :     fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ out_idx ] ];
    1424             : 
    1425           0 :     if( 0==strcmp( link->name, "repair_net" ) ) {
    1426             : 
    1427           0 :       if( ctx->net_out_ctx->idx!=UINT_MAX ) continue; /* only use first net link */
    1428           0 :       ctx->net_out_ctx->idx    = out_idx;
    1429           0 :       ctx->net_out_ctx->mem    = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
    1430           0 :       ctx->net_out_ctx->chunk0 = fd_dcache_compact_chunk0( ctx->net_out_ctx->mem, link->dcache );
    1431           0 :       ctx->net_out_ctx->wmark  = fd_dcache_compact_wmark( ctx->net_out_ctx->mem, link->dcache, link->mtu );
    1432           0 :       ctx->net_out_ctx->chunk  = ctx->net_out_ctx->chunk0;
    1433             : 
    1434           0 :     } else if( 0==strcmp( link->name, "repair_out" ) ) {
    1435             : 
    1436           0 :       out_ctx_t * replay_out = ctx->repair_out_ctx;
    1437           0 :       replay_out->idx        = out_idx;
    1438           0 :       replay_out->mem        = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
    1439           0 :       replay_out->chunk0     = fd_dcache_compact_chunk0( replay_out->mem, link->dcache );
    1440           0 :       replay_out->wmark      = fd_dcache_compact_wmark( replay_out->mem, link->dcache, link->mtu );
    1441           0 :       replay_out->chunk      = replay_out->chunk0;
    1442             : 
    1443           0 :     } else if( 0==strcmp( link->name, "repair_sign" ) ) {
    1444             : 
    1445           0 :       out_ctx_t * repair_sign_out  = &ctx->repair_sign_out_ctx[ ctx->repair_sign_cnt ];
    1446           0 :       repair_sign_out->idx         = out_idx;
    1447           0 :       repair_sign_out->mem         = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
    1448           0 :       repair_sign_out->chunk0      = fd_dcache_compact_chunk0( repair_sign_out->mem, link->dcache );
    1449           0 :       repair_sign_out->wmark       = fd_dcache_compact_wmark( repair_sign_out->mem, link->dcache, link->mtu );
    1450           0 :       repair_sign_out->chunk       = repair_sign_out->chunk0;
    1451           0 :       repair_sign_out->in_idx      = sign_repair_in_idx[ ctx->repair_sign_cnt++ ]; /* match to the sign_repair input link */
    1452           0 :       repair_sign_out->max_credits = sign_link_depth;
    1453           0 :       repair_sign_out->credits     = sign_link_depth;
    1454             : 
    1455           0 :     } else {
    1456           0 :       FD_LOG_ERR(( "repair tile has unexpected output link %s", link->name ));
    1457           0 :     }
    1458           0 :   }
    1459           0 :   FD_TEST( ctx->net_out_ctx->idx!=UINT_MAX );
    1460           0 :   FD_TEST( ctx->repair_out_ctx->idx!=UINT_MAX );
    1461           0 :   if( FD_UNLIKELY( ctx->repair_sign_cnt!=sign_repair_idx ) ) {
    1462           0 :     FD_LOG_ERR(( "Mismatch between repair_sign output links (%lu) and sign_repair input links (%u)", ctx->repair_sign_cnt, sign_repair_idx ));
    1463           0 :   }
    1464           0 :   if( FD_UNLIKELY( fd_signs_map_key_max( ctx->signs_map ) < tile->repair.repair_sign_depth * tile->repair.repair_sign_cnt ) ) {
    1465           0 :     FD_LOG_ERR(( "Repair pending signs tracking map is too small: %lu < %lu.", fd_signs_map_key_max( ctx->signs_map ), tile->repair.repair_sign_depth * tile->repair.repair_sign_cnt ));
    1466           0 :   }
    1467             : 
    1468           0 :   ctx->wksp = topo->workspaces[ topo->objs[ tile->tile_obj_id ].wksp_id ].wksp;
    1469           0 :   ctx->repair_intake_addr.port = fd_ushort_bswap( tile->repair.repair_client_listen_port );
    1470             : 
    1471             :   /* TODO clean these up */
    1472           0 :   ctx->net_id = (ushort)0;
    1473           0 :   fd_ip4_udp_hdr_init( ctx->intake_hdr, 0, 0, tile->repair.repair_client_listen_port );
    1474             : 
    1475             :   /* Repair set up */
    1476             : 
    1477           0 :   ctx->turbine_slot0 = ULONG_MAX;
    1478           0 :   FD_LOG_INFO(( "repair my addr - intake addr: " FD_IP4_ADDR_FMT ":%u",
    1479           0 :     FD_IP4_ADDR_FMT_ARGS( ctx->repair_intake_addr.addr ), fd_ushort_bswap( ctx->repair_intake_addr.port )
    1480           0 :   ));
    1481             : 
    1482           0 :   memset( ctx->metrics, 0, sizeof(ctx->metrics) );
    1483             : 
    1484           0 :   fd_histf_join( fd_histf_new( ctx->metrics->slot_compl_time, FD_MHIST_SECONDS_MIN( REPAIR, SLOT_COMPLETE_DURATION_SECONDS ),
    1485           0 :                                                               FD_MHIST_SECONDS_MAX( REPAIR, SLOT_COMPLETE_DURATION_SECONDS ) ) );
    1486           0 :   fd_histf_join( fd_histf_new( ctx->metrics->response_latency, FD_MHIST_MIN( REPAIR, RESPONSE_LATENCY_NANOS ),
    1487           0 :                                                                FD_MHIST_MAX( REPAIR, RESPONSE_LATENCY_NANOS ) ) );
    1488             : 
    1489           0 :   fd_clock_tile_init( ctx->clock );
    1490             : 
    1491           0 :   ctx->tsdebug = fd_log_wallclock();
    1492           0 :   ctx->pending_key_next = 0;
    1493           0 : }
    1494             : 
    1495             : static ulong
    1496             : populate_allowed_seccomp( fd_topo_t const *      topo FD_PARAM_UNUSED,
    1497             :                           fd_topo_tile_t const * tile FD_PARAM_UNUSED,
    1498             :                           ulong                  out_cnt,
    1499           0 :                           struct sock_filter *   out ) {
    1500           0 :   populate_sock_filter_policy_fd_repair_tile( out_cnt, out, (uint)fd_log_private_logfile_fd() );
    1501           0 :   return sock_filter_policy_fd_repair_tile_instr_cnt;
    1502           0 : }
    1503             : 
    1504             : static ulong
    1505             : populate_allowed_fds( fd_topo_t const *      topo FD_PARAM_UNUSED,
    1506             :                       fd_topo_tile_t const * tile FD_PARAM_UNUSED,
    1507             :                       ulong                  out_fds_cnt,
    1508           0 :                       int *                  out_fds ) {
    1509           0 :   if( FD_UNLIKELY( out_fds_cnt<2UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
    1510             : 
    1511           0 :   ulong out_cnt = 0UL;
    1512           0 :   out_fds[ out_cnt++ ] = 2; /* stderr */
    1513           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
    1514           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
    1515           0 :   return out_cnt;
    1516           0 : }
    1517             : 
    1518             : static inline void
    1519           0 : metrics_write( ctx_t * ctx ) {
    1520           0 :   FD_MGAUGE_SET( REPAIR, SLOT_CURRENT,          ctx->metrics->current_slot );
    1521           0 :   FD_MGAUGE_SET( REPAIR, SLOT_HIGHEST_REPAIRED, fd_forest_highest_repaired_slot( ctx->forest ) );
    1522           0 :   FD_MCNT_SET( REPAIR, SHRED_OLD,             ctx->metrics->old_shred );
    1523           0 :   FD_MCNT_SET( REPAIR, PEER_REQUESTED,        fd_policy_peer_pool_used( ctx->policy->peers.pool ) );
    1524           0 :   FD_MCNT_SET( REPAIR, SIGN_TILE_UNAVAILABLE, ctx->metrics->sign_tile_unavail );
    1525           0 :   FD_MCNT_SET( REPAIR, SHRED_REREQUESTED,     ctx->metrics->rerequest );
    1526             : 
    1527           0 :   FD_MGAUGE_SET( REPAIR, SLOT_LAST_REQUESTED,   ctx->metrics->last_requested_slot );
    1528           0 :   FD_MGAUGE_SET( REPAIR, ORPHAN_LAST_REQUESTED, ctx->metrics->last_requested_orphan );
    1529           0 :   FD_MGAUGE_SET( REPAIR, REQUEST_INFLIGHT,      fd_inflight_pool_used( ctx->inflights->pool ) - ctx->inflights->popped_cnt );
    1530             : 
    1531           0 :   FD_MCNT_SET      ( REPAIR, PKT_TX,      ctx->metrics->send_pkt_cnt   );
    1532           0 :   FD_MCNT_ENUM_COPY( REPAIR, REQUEST_TX, ctx->metrics->sent_pkt_types );
    1533             : 
    1534           0 :   FD_MHIST_COPY( REPAIR, SLOT_COMPLETE_DURATION_SECONDS, ctx->metrics->slot_compl_time );
    1535           0 :   FD_MHIST_COPY( REPAIR, RESPONSE_LATENCY_NANOS,         ctx->metrics->response_latency );
    1536             : 
    1537           0 :   FD_MCNT_SET  ( REPAIR, BLOCK_EVICTED,       ctx->metrics->blk_evicted );
    1538           0 :   FD_MCNT_SET  ( REPAIR, BLOCK_INSERT_FAILED, ctx->metrics->blk_failed_insert );
    1539           0 :   FD_MGAUGE_SET( REPAIR, SLOT_LAST_EVICTED,        ctx->metrics->slot_evicted );
    1540           0 :   FD_MGAUGE_SET( REPAIR, SLOT_LAST_EVICTION_CAUSE, ctx->metrics->slot_evicted_by );
    1541           0 :   FD_MGAUGE_SET( REPAIR, SLOT_LAST_INSERT_FAILED,  ctx->metrics->slot_failed_insert );
    1542             : 
    1543           0 :   FD_MCNT_SET  ( REPAIR, CHAIN_VERIFY_FAILED,           ctx->metrics->failed_chain_verify_cnt );
    1544           0 :   FD_MGAUGE_SET( REPAIR, SLOT_LAST_CHAIN_VERIFY_FAILED, ctx->metrics->failed_chain_verify_slot );
    1545             : 
    1546           0 :   FD_MCNT_SET( REPAIR, PING_UNKNOWN_PEER,     ctx->metrics->unknown_peer_ping );
    1547           0 :   FD_MCNT_SET( REPAIR, PING_MALFORMED,        ctx->metrics->malformed_ping );
    1548           0 :   FD_MCNT_SET( REPAIR, PING_SIGNATURE_FAILED, ctx->metrics->fail_sigverify_ping );
    1549           0 : }
    1550             : 
    1551             : #undef DEBUG_LOGGING
    1552             : 
    1553             : /* At most one sign request is made in after_credit.  Then at most one
    1554             :    message is published in after_frag. */
    1555           0 : #define STEM_BURST (2UL)
    1556             : 
    1557             : /* Set LAZY to a reasonable value that keeps housekeeping time low.
    1558             :    Repair tile's only reliable consumer is replay. */
    1559           0 : #define STEM_LAZY  (64000)
    1560             : 
    1561           0 : #define STEM_CALLBACK_CONTEXT_TYPE  ctx_t
    1562           0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(ctx_t)
    1563             : 
    1564           0 : #define STEM_CALLBACK_AFTER_CREDIT        after_credit
    1565           0 : #define STEM_CALLBACK_BEFORE_FRAG         before_frag
    1566           0 : #define STEM_CALLBACK_DURING_FRAG         during_frag
    1567           0 : #define STEM_CALLBACK_AFTER_FRAG          after_frag
    1568           0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
    1569           0 : #define STEM_CALLBACK_METRICS_WRITE       metrics_write
    1570             : 
    1571             : #include "../../disco/stem/fd_stem.c"
    1572             : 
    1573             : fd_topo_run_tile_t fd_tile_repair = {
    1574             :   .name                     = "repair",
    1575             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    1576             :   .populate_allowed_fds     = populate_allowed_fds,
    1577             :   .scratch_align            = scratch_align,
    1578             :   .scratch_footprint        = scratch_footprint,
    1579             :   .unprivileged_init        = unprivileged_init,
    1580             :   .privileged_init          = privileged_init,
    1581             :   .run                      = stem_run,
    1582             : };

Generated by: LCOV version 1.14