LCOV - code coverage report
Current view: top level - discof/replay - fd_sched.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 1057 3061 34.5 %
Date: 2026-11-11 12:40:43 Functions: 67 148 45.3 %

          Line data    Source code
       1             : #include <stdio.h> /* for vsnprintf */
       2             : #include <stdarg.h> /* for va_list */
       3             : 
       4             : #include "fd_sched.h"
       5             : #include "../../util/fd_hash32.h" /* for lthash lane maps */
       6             : #include "../../flamenco/alpenglow/fd_block_marker_serde.h"
       7             : #include "fd_execrp.h" /* for poh hash value */
       8             : #include "../../ballet/sha256/fd_sha256.h"
       9             : #include "../../disco/fd_disco_base.h" /* for FD_MAX_TXN_PER_SLOT_SHRED */
      10             : #include "../../disco/fd_txn_p.h"
      11             : #include "../../disco/metrics/fd_metrics.h" /* for fd_metrics_convert_ticks_to_nanoseconds and etc. */
      12             : #include "../../disco/pack/fd_chkdup.h"
      13             : #include "../../disco/shred/fd_shredder.h" /* FD_SHREDDER_CHAINED_FEC_SET_PAYLOAD_SZ */
      14             : #include "../../flamenco/runtime/fd_runtime.h" /* for fd_runtime_load_txn_address_lookup_tables */
      15             : #include "../../flamenco/runtime/fd_system_ids.h"
      16             : #include "../../flamenco/runtime/sysvar/fd_sysvar_slot_hashes.h" /* for ALUTs */
      17             : 
      18          24 : #define FD_SCHED_MAX_PRINT_BUF_SZ          (2UL<<20)
      19             : 
      20             : /* 64 ticks per slot, and a single gigantic microblock containing min
      21             :    size transactions. */
      22             : FD_STATIC_ASSERT( FD_MAX_TXN_PER_SLOT_SHRED==((FD_SHRED_DATA_PAYLOAD_MAX_PER_SLOT-65UL*sizeof(fd_microblock_hdr_t))/FD_TXN_MIN_SERIALIZED_SZ), max_txn_per_slot_shred );
      23             : 
      24             : /* We size the buffer to be able to hold residual data from the previous
      25             :    FEC set that only becomes parseable after the next FEC set is
      26             :    ingested, as well as the incoming FEC set.  The largest minimally
      27             :    parseable unit of data is a transaction.  So that much data may
      28             :    straddle FEC set boundaries.  Other minimally parseable units of data
      29             :    include the microblock header and the microblock count within a
      30             :    batch. */
      31           0 : #define FD_SCHED_MAX_PAYLOAD_PER_FEC       (63985UL)
      32             : FD_STATIC_ASSERT( FD_SCHED_MAX_PAYLOAD_PER_FEC>=FD_SHREDDER_CHAINED_FEC_SET_PAYLOAD_SZ, bump sched fec size bound ); /* FD_SHREDDER_CHAINED_FEC_SET_PAYLOAD_SZ is the bound we want, but older ledgers have larger FEC sets. */
      33             : #define FD_SCHED_MAX_RESIDUAL_SZ           (FD_TXN_MTU>FD_BLOCK_MARKER_SER_MAX ? FD_TXN_MTU : FD_BLOCK_MARKER_SER_MAX)
      34             : #define FD_SCHED_MAX_FEC_BUF_SZ            (FD_SCHED_MAX_PAYLOAD_PER_FEC+FD_SCHED_MAX_RESIDUAL_SZ)
      35             : FD_STATIC_ASSERT( FD_SCHED_MAX_RESIDUAL_SZ>=sizeof(fd_microblock_hdr_t), resize buffer for residual data );
      36             : FD_STATIC_ASSERT( FD_SCHED_MAX_RESIDUAL_SZ>=sizeof(ulong),               resize buffer for residual data );
      37             : 
      38             : /* Shred payload sizes are kept per block only for the shreds that the
      39             :    parser's shred scan has not passed yet.  See shred_split().  The scan
      40             :    trails the parse position by at most the residual, which fits in
      41             :    FD_SCHED_MAX_RESIDUAL_SZ, and an ingest appends one FEC set.  So this
      42             :    covers any FEC set made of sanely sized shreds.  Only a block of
      43             :    absurdly small shreds has more of them outstanding at once.  Such a
      44             :    block loses its shred attribution, which is monitoring only. */
      45      267100 : #define FD_SCHED_SHRED_SZ_WINDOW (256UL)
      46             : FD_STATIC_ASSERT( (FD_SCHED_SHRED_SZ_WINDOW&(FD_SCHED_SHRED_SZ_WINDOW-1UL))==0UL, shred_sz_window );
      47             : 
      48           0 : #define FD_SCHED_MAX_TXN_PER_FEC           ((FD_SCHED_MAX_PAYLOAD_PER_FEC-1UL)/FD_TXN_MIN_SERIALIZED_SZ+1UL) /* 478 */
      49           0 : #define FD_SCHED_MAX_MBLK_PER_FEC          ((FD_SCHED_MAX_PAYLOAD_PER_FEC-1UL)/sizeof(fd_microblock_hdr_t)+1UL) /* 1334 */
      50             : 
      51             : FD_STATIC_ASSERT( FD_SCHED_MIN_DEPTH>=FD_SCHED_MAX_TXN_PER_FEC, limits );
      52             : FD_STATIC_ASSERT( FD_SCHED_MAX_DEPTH<=FD_RDISP_MAX_DEPTH,       limits );
      53             : FD_STATIC_ASSERT( FD_SCHED_MAX_DEPTH<=UINT_MAX,                 txn_idx_width );
      54             : 
      55             : 
      56             : /* Out-of-band LtHash.
      57             : 
      58             :    Recall that a bank's LtHash is the sum over every account of the hash
      59             :    of its current value.  A block that writes an account has to subtract
      60             :    the hash of the value it found and add the hash of the value it
      61             :    leaves.  In-band, the exec tile does both inside the commit phase of
      62             :    every writing transaction, so the hashing sits on the critical path
      63             :    of the block.  Our goal is to take it off that path and hide it in
      64             :    idle exec tile cycles instead.
      65             : 
      66             :    The two hashes have different constraints.  The subtraction depends
      67             :    only on the account's value in the parent fork, so it can run at any
      68             :    time after we have seen that the block writes the account, which we
      69             :    know at parse.  The addition must hash the value the block's last
      70             :    writer leaves, so it has to run after that writer completes.  We let
      71             :    the dispatcher sequence it for us.  An addition is a pseudo
      72             :    transaction that writes the account, created through
      73             :    fd_rdisp_add_pseudo_txn(), and the dispatcher hands it back only once
      74             :    every earlier writer of the account is done.  Once the block has all
      75             :    its FEC sets we know the last writers, and the fec_eos drain creates
      76             :    the pseudo transactions for every account still owed an addition, a
      77             :    chunk at a time.  Before that we may speculate on an account whose
      78             :    writers so far have all completed, guessing that no more are coming,
      79             :    and keep the value in the speculation pool so a later write to the
      80             :    account can undo it.  Subtractions and additions go out in batches of
      81             :    accounts whose hashes the exec tile sums.  A speculation goes out
      82             :    alone.  The scheduler folds the sums into the block's delta, which
      83             :    the caller adds to the bank's LtHash at block end.  Hash tasks go
      84             :    only to tiles that would otherwise sit idle, and speculation is the
      85             :    lowest priority of all.
      86             : 
      87             :    Writes the scheduler cannot see at parse are registered later:
      88             :    start-of-block writes through fd_sched_block_start_writes(), and the
      89             :    writable accounts of lookup tables that failed to resolve, which the
      90             :    exec tile hands back after execution. */
      91             : 
      92             : /* Every account a staged block owes an addition for stays in the
      93             :    dispatcher's writable set, and so in its account pool, potentially
      94             :    past the life of the transactions that reference it, up to
      95             :    FD_RDISP_MAX_WRITERS_PER_BLOCK per lane.  The dispatcher is given
      96             :    that many account entries on top of what live transactions can
      97             :    reference. */
      98        2005 : #define FD_SCHED_LTHASH_ACCT_EXTRA (FD_SCHED_MAX_STAGING_LANES*FD_RDISP_MAX_WRITERS_PER_BLOCK)
      99             : 
     100             : /* A demoted block parks its writable set in the dispatcher's unstaged
     101             :    entries, which draw on the same extra capacity.  Each block hashing
     102             :    out of band reserves one block's worth of it.  See
     103             :    lthash_pick_mode().
     104             : 
     105             :    A block that already has all its FEC sets could reserve less, since
     106             :    its set can only grow by the start-of-block and lookup table
     107             :    registrations.  We can estimate these and bound the reservation more
     108             :    tightly.  We don't bother because it's not likely to squeeze more
     109             :    blocks out of it. */
     110        1000 : #define FD_SCHED_LTHASH_OOB_BLOCK_MAX (FD_SCHED_LTHASH_ACCT_EXTRA/FD_RDISP_MAX_WRITERS_PER_BLOCK)
     111             : FD_STATIC_ASSERT( FD_SCHED_LTHASH_OOB_BLOCK_MAX<=64UL, lthash_oob_reserved_bitmap );
     112             : 
     113             : /* The speculation pool holds the values of speculative additions so a
     114             :    later intra-block rewrite can undo the addition.  Speculation stops
     115             :    when this pool gets full.  There is a threshold for free speculation
     116             :    entries per exec tile before a deactivated block is activated to
     117             :    engage in speculation.  This is meant to reduce churn. */
     118    16517072 : #define FD_SCHED_LTHASH_SPEC_MAX (8192UL)
     119           0 : #define FD_SCHED_LTHASH_SPEC_THRESHOLD_PER_TILE (2UL)
     120             : 
     121             : FD_STATIC_ASSERT( FD_SCHED_LTHASH_BATCH_MAX>=1UL && FD_SCHED_LTHASH_BATCH_MAX<=FD_EXECRP_LTHASH_BATCH_MAX, lthash_batch );
     122             : FD_STATIC_ASSERT( FD_SCHED_LTHASH_BATCH_BUSY_MAX>=1UL && FD_SCHED_LTHASH_BATCH_BUSY_MAX<=FD_SCHED_LTHASH_BATCH_MAX, lthash_batch_busy_max );
     123             : 
     124             : FD_STATIC_ASSERT( FD_SCHED_LTHASH_TXN_WRITERS_MAX+FD_SCHED_LTHASH_START_WRITES_MAX==FD_RDISP_MAX_WRITERS_PER_BLOCK, lthash_writers );
     125             : 
     126             : 
     127        1000 : #define FD_SCHED_MAGIC (0xace8a79c181f89b6UL) /* echo -n "fd_sched_v0" | sha512sum | head -c 16 */
     128             : 
     129             : struct fd_sched_mblk {
     130             :   ulong start_txn_idx; /* inclusive parse idx */
     131             :   ulong end_txn_idx;   /* non-inclusive parse idx */
     132             :   ulong curr_txn_idx;  /* next txn to mixin, parse idx in block */
     133             :   fd_hash_t end_hash[ 1 ];
     134             :   fd_hash_t curr_hash[ 1 ];
     135             :   uint curr_sig_cnt;
     136             :   uint next;
     137             :   uint mixin_txn_idx; /* rdisp pool idx of the next txn to mixin */
     138             :   int is_tick;
     139             : };
     140             : typedef struct fd_sched_mblk fd_sched_mblk_t;
     141             : 
     142             : #define SLIST_NAME  mblk_slist
     143             : #define SLIST_ELE_T fd_sched_mblk_t
     144           0 : #define SLIST_IDX_T uint
     145       23702 : #define SLIST_NEXT  next
     146             : #include "../../util/tmpl/fd_slist.c"
     147             : 
     148             : #define SET_NAME txn_bitset
     149             : #define SET_MAX  FD_SCHED_MAX_DEPTH
     150             : #include "../../util/tmpl/fd_set.c"
     151             : 
     152             : /* Out-of-band LtHash keeps per-account state for computing a block's
     153             :    LtHash delta.  Each staging lane has a map from pubkey to an entry
     154             :    that tracks the subtraction and addition of an account the lane's
     155             :    block writes.  A pool of LtHash values shared by all lanes keeps the
     156             :    values of speculative additions that a later write to the account
     157             :    may need to undo.  A block without a reservation for parking its
     158             :    writable set hashes in band instead and keeps none of this state.
     159             : 
     160             :    An entry is inserted the first time the block sees a write to the
     161             :    account, according to either fd_rdisp_add_txn() or
     162             :    fd_rdisp_add_writable().  It starts SUB_QUEUED and ADD_NONE and lives
     163             :    until the lane is released, which also frees its speculation entry.
     164             :    The state machine implemented for each account is as follows:
     165             : 
     166             :                   dispatched to execrp              execrp done, delta -= value
     167             :      sub: QUEUED ---------------------> DISPATCHED ----------------------------> DONE
     168             : 
     169             :                                                                                                                execrp done, ptxn_idx==PIDX
     170             :                 rdisp_add_pseudo_txn()==PIDX                   rdisp_get_next_ready()==PIDX                    delta += value, ptxn_idx=0
     171             :                 speculative: spec_idx acquired                 and ptxn_idx==PIDX                              spec[spec_idx] = value, if any
     172             :      add: NONE -------------------------------> PENDING(PIDX) -----------------------------> DISPATCHED(PIDX) -------------------------------> DONE
     173             :            ^                                          |                                             |                                           |
     174             :            |                                       rewrite                                       rewrite                                     rewrite
     175             :            |                                     ptxn_idx=0                                    ptxn_idx=0                             delta-=spec[spec_idx]
     176             :            |                              spec_idx released, if any                     spec_idx released, if any                       spec_idx released
     177             :            |                                          |                                             |                                           |
     178             :            |                                          v                                             v                                           v
     179             :            +------------------------------------------+---------------------------------------------+-------------------------------------------+
     180             :                   PIDX surfaces or returns later and !=ptxn_idx: pseudo transaction completed or hash result dropped without effect
     181             : 
     182             :    A rewrite is a new first write to the account or a speculation we
     183             :    gave up.
     184             : 
     185             :    A speculative pseudo transaction is one the scheduler creates before
     186             :    the block has all its FEC sets, betting that the account's last write
     187             :    has happened.  A speculative addition may need to be undone after it
     188             :    is applied, so it requires a speculation entry.  A pseudo transaction
     189             :    created once the block is complete is no longer speculative and
     190             :    acquires no speculation entry, so its addition must not be rewritten
     191             :    once DONE.  This is guaranteed by construction.  Such a pseudo
     192             :    transaction is created only after every transaction of the block has
     193             :    been inserted, because the fec_eos drain happens after the last FEC
     194             :    set.  So the only writes that can still arrive are registrations,
     195             :    and those land before the addition can apply.  Start-of-block
     196             :    registrations run before anything else (including a PENDING
     197             :    addition) in the block gets dispatched.  Late ALT registrations come
     198             :    from serializing transactions.  A serializing transaction blocks
     199             :    every pseudo transaction inserted after it until it completes, and
     200             :    these pseudo transactions are inserted after every transaction of
     201             :    the block, so all of them wait on the last serializing transaction.
     202             :    Each serializing transaction registers at its own completion, no
     203             :    later than the last serializing transaction's completion.  A rewrite
     204             :    therefore finds such an addition PENDING, which only makes it stale
     205             :    and needs no undo. */
     206             : 
     207        2889 : #define FD_SCHED_LTHASH_SUB_QUEUED     (0)
     208           0 : #define FD_SCHED_LTHASH_SUB_DISPATCHED (1)
     209           0 : #define FD_SCHED_LTHASH_SUB_DONE       (2)
     210             : 
     211        2889 : #define FD_SCHED_LTHASH_ADD_NONE       (0)
     212           0 : #define FD_SCHED_LTHASH_ADD_PENDING    (1) /* Pseudo transaction added to rdisp, but not yet surfaced by fd_rdisp_get_next_ready(). */
     213           0 : #define FD_SCHED_LTHASH_ADD_DISPATCHED (2) /* Surfaced and still the addition of record.  Might be batched or immediately hashed. */
     214           0 : #define FD_SCHED_LTHASH_ADD_DONE       (3) /* Applied to the block's LtHash, value cached for potential undo if spec_idx is valid. */
     215             : 
     216             : /* This represents one LtHash account. */
     217             : struct fd_sched_lthash_entry {
     218             :   fd_acct_addr_t acct;
     219             :   uint  map_next;
     220             :   uint  q_next;    /* Subtraction queue link while SUB_QUEUED. */
     221             :   uint  spec_idx;  /* Speculation pool entry or UINT_MAX. */
     222             :   uint  ptxn_idx;  /* Non-zero only while the entry is ADD_PENDING or ADD_DISPATCHED.  High bit set if non-zero, like the index returned from fd_rdisp_get_next_ready(). */
     223             :   uint  bank_idx;
     224             :   uchar sub_state;
     225             :   uchar add_state;
     226             :   uchar pad[ 2 ];
     227             : };
     228             : typedef struct fd_sched_lthash_entry fd_sched_lthash_entry_t;
     229             : FD_STATIC_ASSERT( sizeof(fd_sched_lthash_entry_t)==56UL, lthash_entry );
     230             : 
     231             : #define MAP_NAME          lthash_map
     232             : #define MAP_ELE_T         fd_sched_lthash_entry_t
     233             : #define MAP_KEY_T         fd_acct_addr_t
     234        2889 : #define MAP_KEY           acct
     235        2889 : #define MAP_NEXT          map_next
     236       15570 : #define MAP_IDX_T         uint
     237        8667 : #define MAP_KEY_HASH(k,s) fd_hash32( (k)->b, (s) )
     238           0 : #define MAP_KEY_EQ(k0,k1) (!memcmp( (k0)->b, (k1)->b, 32UL ))
     239             : #include "../../util/tmpl/fd_map_chain.c"
     240             : 
     241             : /* A lane's map holds up to FD_RDISP_MAX_WRITERS_PER_BLOCK accounts.
     242             :    Entries are handed out in index order and only ever freed together
     243             :    when the lane is released, so the live entries are the first
     244             :    entry_cnt of the pool and no free list is needed. */
     245             : struct fd_sched_lthash_lane {
     246             :   fd_sched_lthash_entry_t * entry_pool;
     247             :   lthash_map_t *            map;
     248             :   uint                      entry_cnt;  /* Live entries. */
     249             :   ulong                     bank_idx;   /* Block that owns the lane, ULONG_MAX if none. */
     250             : };
     251             : typedef struct fd_sched_lthash_lane fd_sched_lthash_lane_t;
     252             : 
     253             : /* Half the template's estimate for a full lane, since a lane rarely
     254             :    holds more than a few thousand accounts. */
     255             : static inline ulong
     256       16041 : lthash_chain_cnt( void ) {
     257       16041 :   return fd_ulong_max( lthash_map_chain_cnt_est( FD_RDISP_MAX_WRITERS_PER_BLOCK )>>1, 1UL );
     258       16041 : }
     259             : 
     260             : struct fd_sched_block {
     261             :   ulong               slot;
     262             :   ulong               parent_slot;
     263             :   ulong               parent_idx;  /* Index of the parent in the pool. */
     264             :   ulong               child_idx;   /* Index of the left-child in the pool. */
     265             :   ulong               sibling_idx; /* Index of the right-sibling in the pool. */
     266             : 
     267             :   /* Counters. */
     268             :   uint                txn_parsed_cnt;
     269             :   /*                  txn_queued_cnt = txn_parsed_cnt-txn_in_flight_cnt-txn_done_cnt */
     270             :   uint                txn_exec_in_flight_cnt;
     271             :   uint                txn_exec_done_cnt;
     272             :   uint                txn_sigverify_in_flight_cnt;
     273             :   uint                txn_sigverify_done_cnt;
     274             :   uint                poh_hashing_in_flight_cnt; /* number of in-flight PoH batch tasks */
     275             :   uint                poh_mblk_in_flight_cnt;    /* number of individual in-flight PoH jobs across batches */
     276             :   uint                poh_hashing_done_cnt;
     277             :   uint                poh_hash_cmp_done_cnt; /* poh_hashing_done_cnt==poh_hash_cmp_done_cnt+len(mixin_in_progress) */
     278             :   uint                txn_done_cnt; /* A transaction is considered done when all types of tasks associated with it are done. */
     279             :   uint                shred_cnt;
     280             :   uint                shred_scan_idx; /* First shred boundary not before shred_scan_off. */
     281             :   uint                shred_scan_off; /* Block byte offset at the start of shred_scan_idx. */
     282             :   uint                mblk_cnt;          /* Total number of microblocks, including ticks and non ticks.
     283             :                                             mblk_cnt==len(unhashed)+poh_mblk_in_flight_cnt+len(mixin_in_progress)+hash_cmp_done_cnt */
     284             :   uint                mblk_tick_cnt;     /* Total number of tick microblocks. */
     285             :   uint                mblk_freed_cnt;    /* This is ==hash_cmp_done_cnt in most cases, except for aborted
     286             :                                             blocks, where the freed cnt will catch up to mblk_cnt and surpass
     287             :                                             hash_cmp_done_cnt when the block is reaped. */
     288             :   uint                mblk_unhashed_cnt; /* ==len(unhashed) */
     289             :   ulong               txn_pool_max_popcnt;   /* Peak transaction pool occupancy during the time this block was replaying. */
     290             :   ulong               mblk_pool_max_popcnt;  /* Peak mblk pool occupancy. */
     291             :   ulong               block_pool_max_popcnt; /* Peak block pool occupancy. */
     292             :   uint                txn_idx_tail;           /* Most recently parsed rdisp pool index. */
     293             :   uint                txn_sigverify_next_idx; /* Next rdisp pool index to dispatch for sigverify, or 0 if caught up. */
     294             :   uint                parse_mblk_idx;         /* mblk pool index currently receiving parsed transactions. */
     295             :   uint                footer_in_flight_cnt;
     296             : 
     297             :   /* Out-of-band LtHash.
     298             : 
     299             :      When the block has claim on the lane map, each entry in the map
     300             :      corresponds to one subtraction for the block.
     301             : 
     302             :        entries_cnt==sub_queued_cnt+sub_in_flight_cnt+sub_applied_cnt
     303             : 
     304             :      Additions the block owes but has no pseudo transaction for yet are
     305             : 
     306             :        add_owed_cnt
     307             : 
     308             :      We don't track first writes per block.  Each puts an addition on
     309             :      the owed side, so if we did, they would balance as follows.
     310             : 
     311             :        first_write_cnt==add_owed_cnt+add_created_cnt
     312             : 
     313             :      Unsurfaced additions that rdisp has been instructed to create but
     314             :      hasn't handed back to us yet are
     315             : 
     316             :        add_created_cnt-add_surfaced_cnt
     317             : 
     318             :      In-flight additions, including those staged in a batch and not yet
     319             :      dispatched, are
     320             : 
     321             :        add_surfaced_cnt-add_done_cnt
     322             : 
     323             :      Additions completed without a hash, or whose hash was dropped,
     324             :      because their account was written again within the same block are
     325             : 
     326             :        add_stale_cnt==add_done_cnt-add_applied_cnt
     327             : 
     328             :      The net number of additions absorbed by the block's delta is
     329             : 
     330             :        add_applied_cnt-add_undone_cnt
     331             : 
     332             :      At BLOCK_END every entry has been subtracted and added exactly once.
     333             : 
     334             :        sub_applied_cnt==add_applied_cnt-add_undone_cnt==entries_cnt */
     335             :   uint                lthash_writable_cnt; /* Up to FD_SCHED_LTHASH_TXN_WRITERS_MAX. */
     336             :   uint                lthash_in_flight_cnt; /* Task count, could be either add or sub. */
     337             :   uint                sub_q_head;        /* UINT_MAX if empty.  Linked through entry->q_next. */
     338             :   uint                sub_q_tail;
     339             :   uint                sub_queued_cnt;
     340             :   uint                sub_in_flight_cnt;
     341             :   uint                sub_applied_cnt;
     342             :   uint                add_owed_cnt;      /* First writes whose addition pseudo transaction is not created yet. */
     343             :   uint                add_created_cnt;   /* Addition pseudo transactions that we've instructed rdisp to create and queue up. */
     344             :   uint                add_surfaced_cnt;  /* Returned by fd_rdisp_get_next_ready().  Will be either dispatched or completed on the spot. */
     345             :   uint                add_done_cnt;      /* fd_rdisp_complete_txn() issued for pseudo transactions, with or without a dispatched hashing. */
     346             :   uint                add_applied_cnt;   /* Addition results rolled into the block's delta. */
     347             :   uint                add_undone_cnt;    /* Applied additions rolled back due to a subsequent account rewrite. */
     348             : 
     349             :   /* PoH verify. */
     350             :   fd_hash_t    poh_hash[ 1 ]; /* running end_hash of last parsed mblk */
     351             :   int          last_mblk_is_tick;
     352             :   mblk_slist_t mblks_unhashed[ 1 ]; /* A microblock, once parsed out, is in one of these queues.  It
     353             :                                        progresses from unhashed to mixin.  When a microblock is being
     354             :                                        hashed/in-flight, it'll be transiently out of any of the queues.
     355             :                                        Once a microblock progresses through all stages of work, it'll be
     356             :                                        immediately freed. */
     357             :   mblk_slist_t mblks_mixin_in_progress[ 1 ];
     358             :   uchar bmtree_mem[ FD_BMTREE_COMMIT_FOOTPRINT(0) ] __attribute__((aligned(FD_BMTREE_COMMIT_ALIGN)));
     359             :   fd_bmtree_commit_t * bmtree;
     360             :   ulong tick_height;       /* Block is built off of a parent block with this many ticks, ULONG_MAX until known. */
     361             :   ulong max_tick_height;   /* Block should end with precisely this many ticks. */
     362             : 
     363             :   /* Parser state. */
     364             :   ulong               mblks_rem;    /* Number of microblocks remaining in the current batch. */
     365             :   ulong               txns_rem;     /* Number of transactions remaining in the current microblock. */
     366             :   uint                residual_sz;  /* Bytes of residual data, at most FD_SCHED_MAX_RESIDUAL_SZ. */
     367             :   uint                fec_buf_boff; /* Byte offset into raw block data of the first residual byte, which is the first byte in the parse buffer during ingest. */
     368             :   ushort              shred_sz[ FD_SCHED_SHRED_SZ_WINDOW ]; /* Payload size of shred i at i%FD_SCHED_SHRED_SZ_WINDOW, for the shreds in [shred_scan_idx, shred_cnt). */
     369             :   long                fec_completed_ns; /* Network arrival (wallclock ns) of the FEC set being ingested; txns
     370             :                                            parse out only once the FEC completing their bytes arrives, so this
     371             :                                            is stamped on every txn parsed during the current ingest. */
     372             :   uint                fec_eob:1;    /* FEC end-of-batch: set if the last FEC set in the batch is being
     373             :                                        ingested. */
     374             :   uint                fec_sob:1;    /* FEC start-of-batch: set if the parser expects to be parsing out a
     375             :                                        batch header. */
     376             :   uint                shred_sz_overflow:1; /* Set once more shreds were outstanding than shred_sz holds.  The
     377             :                                               shred attribution of the block's remaining transactions saturates. */
     378             : 
     379             :   /* Block state. */
     380             :   uint                fec_eos:1;                          /* FEC end-of-stream: set if the last FEC set in the block has been
     381             :                                                              ingested. */
     382             :   uint                rooted:1;                           /* Set if the block is rooted. */
     383             :   uint                dying:1;                            /* Set if the block has been abandoned and no transactions should be
     384             :                                                              scheduled from it. */
     385             :   uint                discarded:1;                        /* Set if the block went down with a discarded (not invalid) lineage:
     386             :                                                              its own discard or an ancestor's.  This field, along with
     387             :                                                              dead_reason, can be in one of three possible combinations when a
     388             :                                                              block is dead:
     389             : 
     390             :                                                              dead_reason!=DEAD_ANCESTOR, discarded initialized to 0: invalid block due to dead_reason
     391             :                                                              dead_reason==DEAD_ANCESTOR, discarded==0: invalid lineage
     392             :                                                              dead_reason==DEAD_ANCESTOR||NONE, discarded==1: discarded lineage */
     393             :   uint                refcnt:1;                           /* Starts at 1 when the block is added, set to 0 if caller has been
     394             :                                                              informed to decrement refcnt for sched. */
     395             :   uint                in_sched:1;                         /* Set if the block is being tracked by the scheduler. */
     396             :   uint                in_rdisp:1;                         /* Set if the block is being tracked by the dispatcher, either as staged
     397             :                                                              or unstaged. */
     398             :   uint                block_start_signaled:1;             /* Set if the start-of-block sentinel has been dispatched. */
     399             :   uint                block_end_signaled:1;               /* Set if the end-of-block sentinel has been dispatched. */
     400             :   uint                block_start_done:1;                 /* Set if the start-of-block processing has been completed. */
     401             :   uint                block_end_done:1;                   /* Set if the end-of-block processing has been completed. */
     402             :   uint                staged:1;                           /* Set if the block is in a dispatcher staging lane; a staged block is
     403             :                                                              tracked by the dispatcher. */
     404             :   uint                lthash_drained:1;                   /* Set if fec_eos AND something equivalent to
     405             :                                                              fd_rdisp_add_all_pseudo_txn() has been done since fec_eos.  That is
     406             :                                                              to say, every writable account has an addition pseudo transaction
     407             :                                                              that will capture its final value. */
     408             :   uint                lthash_draining:1;                  /* Counted in sched->lthash_draining_cnt. */
     409             :   uint                lthash_mode_picked:1;               /* Set once the block has picked its hashing mode, at its first drain or its
     410             :                                                              BLOCK_START, whichever comes first. */
     411             :   uint                lthash_inband:1;                    /* Set if the block hashes in band.  The dispatcher tracks no writers for it and the
     412             :                                                              block owes no out-of-band work. */
     413             :   uchar               lthash_oob_reservation;             /* Which out-of-band reservation the block holds, meaningful when
     414             :                                                              lthash_mode_picked && !lthash_inband. */
     415             :   ulong               staging_lane;                       /* Ignored if staged==0. */
     416             : 
     417             :   /* Slot state tracking */
     418             :   long                state_start_tick;                   /* Start of the committed state's interval. */
     419             :   long                state_cand_tick;                    /* Start of the candidate state. */
     420             :   uchar               state;                              /* Committed state, NONE until the first commit. */
     421             :   uchar               state_cand;                         /* Candidate state, committed once held for FD_SCHED_SLOT_STATE_DEBOUNCE_NS. */
     422             :   uchar               state_open;
     423             :   uchar               state_conflict;                     /* Last next_ready found queued txns but none ready. */
     424             :   ulong               luf_depth;                          /* Depth of longest unstaged fork starting from this node; only
     425             :                                                              stageable unstaged descendants are counted. */
     426             :   int                 dead_reason;                        /* One of FD_SCHED_DEAD_REASON_*; records the first reason the block
     427             :                                                              was ruled invalid (set-once via record_dead_reason), NONE otherwise. */
     428             :   uchar               residual[ FD_SCHED_MAX_RESIDUAL_SZ ]; /* Bytes of the previous FEC set that only become parseable once the
     429             :                                                                next FEC set is ingested, at most a transaction, a block marker or
     430             :                                                                a microblock or batch header. */
     431             : 
     432             :   /* Alpenglow block footer, deserialized out of the marker batch at
     433             :      parse time.  footer_seen is set if the block carries a footer
     434             :      marker and it is seen by sched; footer is only meaningful in that
     435             :      case. */
     436             :   fd_block_footer_t footer;
     437             : 
     438             :   /* Alpenglow block structure, mirroring agave's BlockComponentStage
     439             :      as a set of "seen" flags.
     440             :      Together with mblk_cnt they determine what may come next:
     441             : 
     442             :        agave stage               header footer alpentick mblk_cnt
     443             :        PreParentMarker             0      0       0        0
     444             :        AcceptingGenesisOrEntries   1      0       0        0     (and no genesis cert yet)
     445             :        AcceptingEntriesOrFooter    1      0       0        *
     446             :        AcceptingAlpentick          1      1       0        *
     447             :        Done                        1      1       1        *      */
     448             :   uint header_seen        : 1;
     449             :   uint genesis_cert_seen  : 1;
     450             :   uint update_parent_seen : 1;
     451             :   uint footer_seen        : 1;
     452             :   uint cert_cnt           : 3; /* Footer certs to verify.  See fd_alpenglow_footer_certs. */
     453             :   uint cert_next          : 3; /* Footer certs already sent out as tasks. */
     454             :   uint alpentick_seen     : 1;
     455             : 
     456             :   /* Replay started the block at its UpdateParent FEC set, so the
     457             :      UpdateParent stands in for the header as the parent marker. */
     458             :   uint starts_at_update_parent : 1;
     459             :   uint flh_active              : 1; /* alpenglow_fast_leader_handover is active on the block's bank, set at BLOCK_START */
     460             : };
     461             : typedef struct fd_sched_block fd_sched_block_t;
     462             : 
     463             : FD_STATIC_ASSERT( sizeof(fd_sched_mblk_t)==104UL, fd_sched_mblk );
     464             : FD_STATIC_ASSERT( sizeof(fd_sched_txn_info_t)==184UL, fd_sched_txn_info );
     465             : FD_STATIC_ASSERT( sizeof(fd_sched_block_t)==9472UL, fd_sched_block );
     466             : FD_STATIC_ASSERT( sizeof(fd_hash_t)==sizeof(((fd_microblock_hdr_t *)0)->hash), unexpected poh hash size );
     467             : 
     468             : 
     469             : struct fd_sched_metrics {
     470             :   uint  block_added_cnt;
     471             :   uint  block_added_staged_cnt;
     472             :   uint  block_added_unstaged_cnt;
     473             :   uint  block_added_dead_ood_cnt;
     474             :   uint  block_removed_cnt;
     475             :   uint  block_abandoned_cnt;
     476             :   uint  block_bad_cnt;
     477             :   uint  block_promoted_cnt;
     478             :   uint  block_demoted_cnt;
     479             :   uint  deactivate_no_child_cnt;
     480             :   uint  deactivate_no_txn_cnt;
     481             :   uint  deactivate_pruned_cnt;
     482             :   uint  deactivate_abandoned_cnt;
     483             :   uint  lane_switch_cnt;
     484             :   uint  lane_promoted_cnt;
     485             :   uint  lane_demoted_cnt;
     486             :   uint  fork_observed_cnt;
     487             :   uint  alut_success_cnt;
     488             :   uint  alut_serializing_cnt;
     489             :   uint  txn_abandoned_parsed_cnt;
     490             :   uint  txn_abandoned_exec_done_cnt;
     491             :   uint  txn_abandoned_done_cnt;
     492             :   uint  txn_promoted_cnt;
     493             :   uint  txn_max_in_flight_cnt;
     494             :   ulong txn_weighted_in_flight_cnt;
     495             :   ulong txn_weighted_in_flight_tickcount;
     496             :   ulong txn_none_in_flight_tickcount;
     497             :   ulong txn_parsed_cnt;
     498             :   ulong txn_exec_done_cnt;
     499             :   ulong txn_sigverify_done_cnt;
     500             :   ulong txn_mixin_done_cnt;
     501             :   ulong txn_done_cnt;
     502             :   ulong mblk_parsed_cnt;
     503             :   ulong mblk_poh_hashed_cnt;
     504             :   ulong mblk_poh_done_cnt;
     505             :   ulong bytes_ingested_cnt;
     506             :   ulong bytes_ingested_unparsed_cnt;
     507             :   ulong bytes_dropped_cnt;
     508             :   ulong fec_cnt;
     509             : 
     510             :   /* Out-of-band LtHash.  sum(x) sums per-block counter x over the
     511             :      blocks in the scheduler.  The lthash_discarded_* counters are for
     512             :      what abandon and demotion throw away.  We expect the following
     513             :      invariants.
     514             : 
     515             :      Writes observed, by source, and what the dispatcher made of each.
     516             :      A given up speculation puts its account back as written, so it is a
     517             :      source too:
     518             :        lthash_parsed_writable_cnt+lthash_start_reg_cnt+lthash_late_reg_cnt+lthash_demote_reg_cnt+lthash_spec_given_up_cnt==lthash_first_write_cnt+lthash_coalesced_cnt
     519             : 
     520             :      Sometimes rdisp reports a write as the account's first write since
     521             :      its last addition pseudo transaction.  It makes the block owe the
     522             :      account one addition, which a drain or a speculation will get going
     523             :      later.  Writes in between coalesce into that owed addition, which
     524             :      is the add-add coalescing rdisp provides:
     525             :        lthash_first_write_cnt==lthash_add_created_cnt+sum(add_owed_cnt)+lthash_discarded_add_owed_cnt+lthash_inband_add_owed_cnt
     526             : 
     527             :      Additions.  A surfaced addition sits in sched->lthash_add_batch
     528             :      until its batch reaches a tile, so the held batch is a term:
     529             :        lthash_add_created_cnt==lthash_add_surfaced_cnt+sum(add_created_cnt-add_surfaced_cnt)+lthash_discarded_add_pending_cnt
     530             :        lthash_add_surfaced_cnt==lthash_add_surfaced_stale_cnt+lthash_spec_given_up_cnt+lthash_add_dispatched_cnt+(held additions)+lthash_discarded_add_held_cnt
     531             :        lthash_add_dispatched_cnt==lthash_add_dispatched_stale_cnt+lthash_add_applied_cnt+(in-flight additions)
     532             :        lthash_add_applied_cnt-lthash_add_undone_cnt==lthash_frozen_cnt+sum(add_applied_cnt-add_undone_cnt)+lthash_discarded_add_applied_cnt
     533             : 
     534             :      Subtractions:
     535             :        lthash_entry_cnt==lthash_sub_dispatched_cnt+sum(sub_queued_cnt)+lthash_discarded_sub_queued_cnt+lthash_inband_sub_queued_cnt
     536             :        lthash_sub_dispatched_cnt==lthash_sub_applied_cnt+(in-flight subtractions)
     537             :        lthash_sub_applied_cnt==lthash_frozen_cnt+sum(sub_applied_cnt)+lthash_discarded_sub_applied_cnt
     538             : 
     539             :      Speculation.  lthash_spec_cnt of the additions were guesses.  Once
     540             :      the blocks finish, lthash_spec_applied_cnt-lthash_add_undone_cnt of
     541             :      them survive, lthash_spec_stale_cnt were invalidated before their
     542             :      hash applied, and lthash_spec_given_up_cnt were withdrawn before
     543             :      they reached a tile with no write involved.  The rest of the stale
     544             :      counts are drain additions invalidated by a registration that came
     545             :      after the drain.  Draining is a parse time operation, and the
     546             :      registrations that invalidate it could come from either
     547             :      start-of-block processing or ALT expansions.  The owed, pending and
     548             :      queued lthash_discarded_* counters come only from abandon, since a
     549             :      demotable block has nothing queued or unsurfaced and keeps its owed
     550             :      additions.  The two lthash_discarded_*_applied delta counters can
     551             :      come from either abandon or demotion. */
     552             :   ulong lthash_parsed_writable_cnt;       /* Declared writable accounts of parsed transactions.  ALT accounts of a transaction whose tables could not be expanded are not counted here, and instead count as late registrations. */
     553             :   uint  lthash_start_reg_cnt;
     554             :   uint  lthash_late_reg_cnt;              /* Declared writable lookup table accounts registered at transaction completion. */
     555             :   uint  lthash_demote_reg_cnt;            /* Accounts put back into rdisp when their block was demoted.  A later promotion will drain these back out of rdisp.  Not really new writes. */
     556             :   ulong lthash_first_write_cnt;           /* Writes rdisp reported as the account's first since its last addition pseudo transaction. */
     557             :   ulong lthash_coalesced_cnt;             /* Writes to an account the block already owed an addition for. */
     558             :   ulong lthash_entry_cnt;                 /* Lane head account map entries inserted, each owing one subtraction. */
     559             :   ulong lthash_sub_dispatched_cnt;
     560             :   ulong lthash_sub_task_cnt;              /* Subtraction tasks dispatched to a tile, each a batch of the accounts lthash_sub_dispatched_cnt counts. */
     561             :   ulong lthash_sub_applied_cnt;
     562             :   ulong lthash_add_created_cnt;           /* Addition pseudo transactions rdisp handed us. */
     563             :   ulong lthash_spec_cnt;                  /* Of those, speculatively created while the block was still receiving FEC sets. */
     564             :   ulong lthash_add_surfaced_cnt;          /* Pseudo transactions returned by fd_rdisp_get_next_ready(). */
     565             :   ulong lthash_add_surfaced_stale_cnt;    /* Of those, no longer the account's addition of record due to a rewrite.  We will complete these pseudo transactions without hashing. */
     566             :   ulong lthash_add_dispatched_cnt;
     567             :   ulong lthash_add_task_cnt;              /* Addition tasks dispatched to a tile, each a batch of the accounts lthash_add_dispatched_cnt counts. */
     568             :   ulong lthash_add_dispatched_stale_cnt;  /* Of those, rewritten while in flight.  We will drop the hashing result. */
     569             :   ulong lthash_add_batch_held_cnt;        /* Addition batches held because a transaction surfaced behind them, released by a stale addition completed in the same poll, or because a speculative addition went alone. */
     570             :   ulong lthash_add_batch_wait_idle_cnt;   /* Polls that left an addition batch held because of insufficient idle tile. */
     571             :   ulong lthash_add_applied_cnt;
     572             :   ulong lthash_add_undone_cnt;            /* Applied additions rolled back because the account was rewritten. */
     573             :   ulong lthash_spec_applied_cnt;          /* Of lthash_add_applied_cnt, speculative. */
     574             :   ulong lthash_spec_stale_cnt;            /* Speculations rewritten while PENDING or DISPATCHED, invalidated before they applied.  Applied speculations are not included here. */
     575             :   uint  lthash_spec_given_up_cnt;         /* Speculations that surfaced once the block was complete, or when the idleness policy would not let them onto a tile. */
     576             :   uint  lthash_pool_full_cnt;             /* Deactivations of a block that still owed additions, because the speculation pool had no free entry.  Mutually exclusive with deactivate_no_txn_cnt.  Such blocks have a chance to be activated if sufficient entries per exec tile are free again. */
     577             :   uint  lthash_spec_yield_cnt;            /* Deactivations of a block that could only speculate, because it's yielding to another lane head that has must-do work. */
     578             :   ulong lthash_frozen_cnt;                /* Accounts in the delta when BLOCK_END completes.  End-of-block writers are hashed in band and are not counted. */
     579             :   uint  lthash_discarded_add_owed_cnt;    /* Additions owed by abandoned blocks.  Never created. */
     580             :   uint  lthash_discarded_add_pending_cnt; /* Pseudo transactions of abandoned blocks.  Never surfaced. */
     581             :   uint  lthash_discarded_add_held_cnt;    /* Additions of abandoned blocks batched behind a transaction.  Never on a tile. */
     582             :   uint  lthash_discarded_add_applied_cnt; /* Additions in the delta of an abandoned or demoted block, net of undone. */
     583             :   uint  lthash_discarded_sub_queued_cnt;  /* Subtractions of abandoned blocks.  Never dispatched. */
     584             :   uint  lthash_discarded_sub_applied_cnt; /* Subtractions in the delta of an abandoned or demoted block. */
     585             :   uint  lthash_inband_add_owed_cnt;       /* Additions owed by blocks that picked in-band hashing.  Never created. */
     586             :   uint  lthash_inband_sub_queued_cnt;     /* Subtractions of blocks that picked in-band hashing.  Never dispatched. */
     587             :   uint  lthash_inband_block_cnt;          /* Blocks that picked in-band hashing because every out-of-band reservation was taken. */
     588             :   ulong lane_wait_cnt;
     589             : };
     590             : typedef struct fd_sched_metrics fd_sched_metrics_t;
     591             : 
     592             : /* A batch of LtHash entries of one block and one kind, either what a
     593             :    tile is actively hashing or the additions that have been staged but
     594             :    not yet dispatched.  A pseudo transaction is unreadable once it is
     595             :    completed, so we capture the account addresses at batching. */
     596             : struct fd_sched_lthash_batch {
     597             :   ulong          bank_idx;
     598             :   ulong          cnt; /* In [0, FD_SCHED_LTHASH_BATCH_MAX]. */
     599             :   uint           lane;
     600             :   uchar          kind;
     601             :   uint           entry_idx[ FD_SCHED_LTHASH_BATCH_MAX ]; /* Entries in the lane map. */
     602             :   uint           ptxn_idx [ FD_SCHED_LTHASH_BATCH_MAX ]; /* FD_RDISP_LTHASH_PSEUDO_TXN bit set.  0 for a subtraction. */
     603             :   fd_acct_addr_t accts    [ FD_SCHED_LTHASH_BATCH_MAX ];
     604             : };
     605             : typedef struct fd_sched_lthash_batch fd_sched_lthash_batch_t;
     606             : FD_STATIC_ASSERT( alignof(fd_sched_lthash_batch_t)==alignof(ulong), lthash_batch_align ); /* The layout places the records at alignof(ulong). */
     607             : 
     608             : #define DEQUE_NAME ref_q
     609           0 : #define DEQUE_T    ulong
     610             : #include "../../util/tmpl/fd_deque_dynamic.c"
     611             : 
     612             : struct fd_sched {
     613             :   fd_acct_addr_t        aluts[ 256 ]; /* Resolve ALUT accounts into this buffer for more parallelism. */
     614             :   char                  print_buf[ FD_SCHED_MAX_PRINT_BUF_SZ ];
     615             :   ulong                 print_buf_sz;
     616             :   uchar                 fec_buf[ FD_SCHED_MAX_FEC_BUF_SZ ]; /* Parse buffer.  It holds the residual followed by the new FEC set. */
     617             :   uint                  fec_buf_sz;   /* Size of the fec_buf in bytes. */
     618             :   uint                  fec_buf_soff; /* Starting offset into fec_buf for unparsed transactions. */
     619             :   uchar                 txn[ FD_TXN_MAX_SZ ] __attribute__((aligned(alignof(fd_txn_t)))); /* The transaction being parsed out. */
     620             :   ulong                 max_shreds_per_block; /* Immutable. */
     621             :   ulong                 max_txn_per_slot;     /* Immutable. */
     622             :   ulong                 max_mblk_per_slot;    /* Immutable, FD_SCHED_MAX_MBLK_PER_SLOT scaled with max_shreds_per_block. */
     623             :   ulong                 lthash_txn_writers_max; /* FD_SCHED_LTHASH_TXN_WRITERS_MAX unless lowered by a test. */
     624             :   ulong                 lthash_oob_reserved;    /* Bit i set if out-of-band reservation i is held by a block. */
     625             :   ulong                 lthash_oob_block_max;   /* FD_SCHED_LTHASH_OOB_BLOCK_MAX unless lowered by a test. */
     626             :   fd_chkdup_t           chkdup[ 1 ];
     627             :   fd_sched_metrics_t    metrics[ 1 ];
     628             :   ulong                 canary; /* == FD_SCHED_MAGIC */
     629             :   ulong                 depth;         /* Immutable.  Pool depth. */
     630             :   ulong                 block_cnt_max; /* Immutable. */
     631             :   ulong                 exec_cnt;      /* Immutable. */
     632             :   int                   bypass_poh_verify; /* Test/fuzz: skip the PoH end_hash compare in maybe_mixin. */
     633             :   int                   bypass_alut_resolution; /* Test/fuzz: skip ALUT resolution (no accdb). */
     634             :   long                  txn_in_flight_last_tick;
     635             :   long                  next_ready_last_tick;
     636             :   ulong                 next_ready_last_bank_idx;
     637             :   ulong                 root_idx;
     638             :   fd_rdisp_t *          rdisp;
     639             :   ulong                 txn_exec_ready_bitset[ 1 ];
     640             :   ulong                 sigverify_ready_bitset[ 1 ];
     641             :   ulong                 poh_ready_bitset[ 1 ];
     642             :   ulong                 footer_ready_bitset[ 1 ];
     643             :   ulong                 lthash_ready_bitset[ 1 ];
     644             :   ulong                 active_bank_idx; /* Index of the actively replayed block, or ULONG_MAX if no block is
     645             :                                             actively replayed; has to have a transaction to dispatch; staged
     646             :                                             blocks that have no transactions to dispatch are not eligible for
     647             :                                             being active. */
     648             :   ulong                 last_active_bank_idx;
     649             :   ulong                 staged_bitset;    /* Bit i set if staging lane i is occupied. */
     650             :   ulong                 staged_head_bank_idx[ FD_SCHED_MAX_STAGING_LANES ]; /* Head of the linear chain in each staging lane, ignored if bit i is
     651             :                                                                                not set in the bitset. */
     652             :   ulong                 staged_popcnt_wmk;
     653             :   ulong                 txn_pool_free_cnt;
     654             :   fd_txn_p_t *          txn_pool;      /* Just a flat array. */
     655             :   fd_sched_txn_info_t * txn_info_pool; /* Just a flat array. */
     656             :   fd_sched_mblk_t *     mblk_pool;     /* Just a flat array. */
     657             :   ulong                 mblk_pool_free_cnt;
     658             :   uint                  mblk_pool_free_head;
     659             :   ulong                 tile_to_bank_idx[ FD_SCHED_MAX_EXEC_TILE_CNT ]; /* Index of the bank that the exec tile is executing against. */
     660             :   fd_sched_poh_hash_t   poh_inflight[ FD_SCHED_MAX_EXEC_TILE_CNT ]; /* PoH dispatch metadata, indexed by exec tile. */
     661             :   fd_sched_lthash_batch_t * lthash_inflight;  /* What each tile is hashing, FD_SCHED_MAX_EXEC_TILE_CNT records indexed by exec tile. */
     662             :   fd_sched_lthash_batch_t * lthash_add_batch; /* Surfaced additions not yet put on a tile, cnt 0 if none. */
     663             :   fd_sched_lthash_lane_t    lthash_lane[ FD_SCHED_MAX_STAGING_LANES ];
     664             :   fd_lthash_value_t *       lthash_spec_pool; /* FD_SCHED_LTHASH_SPEC_MAX values of speculative additions. */
     665             :   uint *                    lthash_spec_free; /* Stack of the free lthash_spec_pool indices, lthash_spec_free[ lthash_spec_free_cnt-1 ] on top. */
     666             :   ulong                     lthash_spec_free_cnt;
     667             :   fd_lthash_value_t         lthash_delta[ FD_SCHED_LTHASH_OOB_BLOCK_MAX ]; /* Delta of the block holding out-of-band reservation i. */
     668             :   uint                      lthash_draining_cnt;
     669             :   txn_bitset_t          exec_done_set[ txn_bitset_word_cnt ];      /* Indexed by txn_idx. */
     670             :   txn_bitset_t          sigverify_done_set[ txn_bitset_word_cnt ]; /* Indexed by txn_idx. */
     671             :   txn_bitset_t          poh_mixin_done_set[ txn_bitset_word_cnt ]; /* Indexed by txn_idx. */
     672             :   fd_sched_block_t *    block_pool; /* Just a flat array. */
     673             :   ulong                 block_pool_popcnt;
     674             :   ulong *               ref_q;
     675             : 
     676             :   long                  state_debounce_ticks;
     677             :   ulong                 state_bank_idx; /* Block whose slot state is open, ULONG_MAX if none. */
     678             :   ulong                 state_cnt;
     679             :   ulong                 state_drop_cnt;
     680             :   fd_sched_slot_state_t state_buf[ FD_SCHED_SLOT_STATE_MAX ];
     681             : };
     682             : typedef struct fd_sched fd_sched_t;
     683             : 
     684             : 
     685             : /* Internal helpers. */
     686             : 
     687             : static int
     688             : verify_ticks_eager( fd_sched_block_t * block );
     689             : 
     690             : static int
     691             : verify_ticks_final( fd_sched_block_t * block );
     692             : 
     693             : static int
     694             : on_final( fd_sched_block_t * block );
     695             : 
     696             : static void
     697             : add_block( fd_sched_t * sched,
     698             :            ulong        bank_idx,
     699             :            ulong        parent_bank_idx );
     700             : 
     701             : FD_WARN_UNUSED static int
     702             : fd_sched_parse( fd_sched_t * sched, fd_sched_block_t * block, fd_sched_alut_ctx_t * alut_ctx );
     703             : 
     704             : FD_WARN_UNUSED static int
     705             : fd_sched_parse_txn( fd_sched_t * sched, fd_sched_block_t * block, fd_sched_alut_ctx_t * alut_ctx );
     706             : 
     707             : static void
     708             : dispatch_sigverify( fd_sched_t * sched, fd_sched_block_t * block, ulong bank_idx, int exec_tile_idx, fd_sched_task_t * out );
     709             : 
     710             : static uint
     711             : shred_split( fd_sched_block_t * block,
     712             :              uint               query );
     713             : 
     714             : static void
     715             : dispatch_poh( fd_sched_t * sched, fd_sched_block_t * block, ulong bank_idx, int exec_tile_idx, fd_sched_task_t * out );
     716             : 
     717             : static int
     718             : poh_retire_mblk( fd_sched_t * sched, fd_sched_block_t * block, uint mblk_idx );
     719             : 
     720             : FD_WARN_UNUSED static int
     721             : maybe_mixin( fd_sched_t * sched, fd_sched_block_t * block );
     722             : 
     723             : static void
     724           0 : free_mblk( fd_sched_t * sched, fd_sched_block_t * block, uint mblk_idx ) {
     725           0 :   sched->mblk_pool[ mblk_idx ].next = sched->mblk_pool_free_head;
     726           0 :   sched->mblk_pool_free_head = mblk_idx;
     727           0 :   sched->mblk_pool_free_cnt++;
     728           0 :   block->mblk_freed_cnt++;
     729           0 : }
     730             : 
     731             : static void
     732           0 : free_mblk_slist( fd_sched_t * sched, fd_sched_block_t * block, mblk_slist_t * list ) {
     733           0 :   while( !mblk_slist_is_empty( list, sched->mblk_pool ) ) {
     734           0 :     uint idx = (uint)mblk_slist_idx_pop_head( list, sched->mblk_pool );
     735           0 :     free_mblk( sched, block, idx );
     736           0 :   }
     737           0 : }
     738             : 
     739             : static void
     740             : try_activate_block( fd_sched_t * sched );
     741             : 
     742             : static void
     743             : check_or_set_active_block( fd_sched_t * sched );
     744             : 
     745             : static void
     746             : subtree_abandon( fd_sched_t * sched, fd_sched_block_t * block );
     747             : 
     748             : static void
     749             : subtree_release_refcnt( fd_sched_t * sched, fd_sched_block_t * block );
     750             : 
     751             : static void
     752             : subtree_prune( fd_sched_t * sched, ulong bank_idx, ulong except_idx );
     753             : 
     754             : static void
     755             : maybe_switch_block( fd_sched_t * sched, ulong bank_idx );
     756             : 
     757             : static ulong
     758             : compute_longest_unstaged_fork( fd_sched_t * sched, ulong bank_idx );
     759             : 
     760             : static ulong
     761             : promote_from_longest_unstaged_fork( fd_sched_t * sched, ulong bank_idx, int lane_idx );
     762             : 
     763             : static int
     764             : lane_is_demotable( fd_sched_t * sched, int lane_idx );
     765             : 
     766             : static void
     767             : demote_lane( fd_sched_t * sched, int lane_idx );
     768             : 
     769             : static inline fd_sched_block_t *
     770       29288 : block_pool_ele( fd_sched_t * sched, ulong idx ) {
     771       29288 :   FD_TEST( idx<sched->block_cnt_max || idx==ULONG_MAX );
     772       29288 :   return idx==ULONG_MAX ? NULL : sched->block_pool+idx;
     773       29288 : }
     774             : 
     775             : FD_FN_UNUSED static inline int
     776           0 : block_is_void( fd_sched_block_t * block ) {
     777           0 :   /* We've seen everything in the block and no transaction got parsed
     778           0 :      out. */
     779           0 :   return block->fec_eos && block->txn_parsed_cnt==0;
     780           0 : }
     781             : 
     782             : /* Assumes that every serializing transaction has completed, so every
     783             :    late registration has happened. */
     784             : static inline int
     785           0 : block_lthash_settled( fd_sched_block_t * block ) {
     786           0 :   return block->lthash_drained && !block->sub_queued_cnt && !block->sub_in_flight_cnt && block->add_created_cnt==block->add_done_cnt;
     787           0 : }
     788             : 
     789             : static inline int
     790           0 : block_should_signal_end( fd_sched_block_t * block ) {
     791             :   /* Under the current policy of eager synchronous PoH mixin, hashing
     792             :      done plus fec_eos imply that all mixins have been done. */
     793           0 :   if( FD_UNLIKELY( !( !block->fec_eos || ((block->mblk_cnt==block->poh_hashing_done_cnt&&block->mblk_cnt==block->poh_hash_cmp_done_cnt)||block->mblk_cnt!=block->poh_hashing_done_cnt) ) ) ) FD_LOG_CRIT(( "invariant violation: slot %lu fec_eos %d mblk_cnt %u poh_hashing_done_cnt %u poh_hash_cmp_done_cnt %u", block->slot, block->fec_eos, block->mblk_cnt, block->poh_hashing_done_cnt, block->poh_hash_cmp_done_cnt ));
     794           0 :   return block->fec_eos && block->txn_parsed_cnt==block->txn_done_cnt && block->mblk_cnt==block->poh_hashing_done_cnt && block->block_start_done && !block->block_end_signaled && block_lthash_settled( block );
     795           0 : }
     796             : 
     797             : static inline int
     798           0 : block_will_signal_end( fd_sched_block_t * block ) {
     799           0 :   return block->fec_eos && !block->block_end_signaled;
     800           0 : }
     801             : 
     802             : /* Is there something known to be dispatchable in the block?  This is an
     803             :    important liveness property.  A block that doesn't contain any known
     804             :    dispatchable tasks will be deactivated or demoted.  The block
     805             :    boundary tests come first because they settle a complete or an
     806             :    unstarted block with one load each.  The replay tile asks this
     807             :    question of every staged block on every loop while a first FEC set
     808             :    waits for a lane, so this needs to be fast. */
     809             : static inline int
     810        4555 : block_is_dispatchable( fd_sched_block_t * block ) {
     811        4555 :   return !block->block_start_signaled ||
     812        4555 :          block_will_signal_end( block ) ||
     813        4555 :          block->txn_parsed_cnt-block->txn_exec_in_flight_cnt-block->txn_exec_done_cnt>0U || /* exec_queued_cnt */
     814        4555 :          block->txn_parsed_cnt-block->txn_sigverify_in_flight_cnt-block->txn_sigverify_done_cnt>0U || /* sigverify_queued_cnt */
     815        4555 :          block->mblk_cnt-block->poh_mblk_in_flight_cnt-block->poh_hashing_done_cnt>0U || /* poh_queued_cnt */
     816        4555 :          block->sub_queued_cnt>0U ||
     817        4555 :          block->add_created_cnt>block->add_surfaced_cnt;
     818        4555 : }
     819             : 
     820             : /* Tasks of the block on exec tiles, footer tasks included. */
     821             : static inline ulong
     822           0 : block_in_flight_cnt( fd_sched_block_t * block ) {
     823           0 :   return (ulong)block->txn_exec_in_flight_cnt + (ulong)block->txn_sigverify_in_flight_cnt + (ulong)block->poh_hashing_in_flight_cnt + (ulong)block->footer_in_flight_cnt + (ulong)block->lthash_in_flight_cnt;
     824           0 : }
     825             : 
     826             : /* Unlike block_in_flight_cnt, this goes by the LtHash account counters,
     827             :    so an addition batch staged but not yet dispatched keeps the block in
     828             :    flight. */
     829             : static inline int
     830           4 : block_is_in_flight( fd_sched_block_t * block ) {
     831           4 :   return block->txn_exec_in_flight_cnt || block->txn_sigverify_in_flight_cnt || block->poh_hashing_in_flight_cnt || block->footer_in_flight_cnt || (block->block_end_signaled && !block->block_end_done)
     832           4 :       || block->sub_in_flight_cnt || block->add_surfaced_cnt>block->add_done_cnt;
     833           4 : }
     834             : 
     835             : static inline int
     836        6560 : block_is_done( fd_sched_block_t * block ) {
     837        6560 :   return block->fec_eos && block->txn_parsed_cnt==block->txn_done_cnt && block->mblk_cnt==block->poh_hash_cmp_done_cnt && block->block_start_done && block->block_end_done && !block->footer_in_flight_cnt;
     838        6560 : }
     839             : 
     840             : static inline int
     841        5560 : block_is_stageable( fd_sched_block_t * block ) {
     842        5560 :   int rv = !block_is_done( block ) && !block->dying;
     843        5560 :   if( FD_UNLIKELY( rv && !block->in_rdisp ) ) {
     844             :     /* Invariant: stageable blocks may be currently staged or unstaged,
     845             :        but must be in the dispatcher either way.  When a block
     846             :        transitions to DONE, it will be immediately removed from the
     847             :        dispatcher.  When a block transitions to DYING, it will be
     848             :        eventually abandoned from the dispatcher. */
     849           0 :     FD_LOG_CRIT(( "invariant violation: stageable block->in_rdisp==0, txn_parsed_cnt %u, txn_done_cnt %u, fec_eos %u,, slot %lu, parent slot %lu",
     850           0 :                   block->txn_parsed_cnt, block->txn_done_cnt, (uint)block->fec_eos, block->slot, block->parent_slot ));
     851           0 :   }
     852        5560 :   return rv;
     853        5560 : }
     854             : 
     855             : static inline int
     856           4 : block_is_promotable( fd_sched_block_t * block ) {
     857           4 :   return block_is_stageable( block ) && block_is_dispatchable( block ) && !block->staged;
     858           4 : }
     859             : 
     860             : static inline int
     861           0 : block_is_demotable( fd_sched_block_t * block ) {
     862             :   /* A block can only be demoted from rdisp if it is empty, meaning no
     863             :      PENDING, READY, or DISPATCHED transactions.  This is equivalent to
     864             :      having no in-flight transactions (DISPATCHED) and no queued
     865             :      transactions (PENDING or READY).  This function actually implements
     866             :      a stronger requirement.  We consider a block demotable only if
     867             :      there are no in-flight or queued tasks of any kind. */
     868           0 :   return block->staged && !block_is_dispatchable( block ) && !block_is_in_flight( block );
     869           0 : }
     870             : 
     871             : static inline int
     872        4555 : block_is_activatable( fd_sched_block_t * block ) {
     873        4555 :   return block_is_stageable( block ) && block_is_dispatchable( block ) && block->staged;
     874        4555 : }
     875             : 
     876             : static inline ulong
     877        6449 : block_to_idx( fd_sched_t * sched, fd_sched_block_t * block ) { return (ulong)(block-sched->block_pool); }
     878             : 
     879             : static inline int
     880        1001 : block_is_lane_head( fd_sched_t * sched, fd_sched_block_t * block ) {
     881        1001 :   return block->staged && sched->staged_head_bank_idx[ block->staging_lane ]==block_to_idx( sched, block );
     882        1001 : }
     883             : 
     884             : FD_FN_UNUSED static inline int
     885           0 : block_is_lane_tail( fd_sched_t * sched, fd_sched_block_t * block ) {
     886           0 :   if( !block->staged ) return 0;
     887           0 :   fd_rdisp_staging_lane_info_t lane_info[ FD_SCHED_MAX_STAGING_LANES ];
     888           0 :   ulong occupied = fd_rdisp_staging_lane_info( sched->rdisp, lane_info );
     889           0 :   return fd_ulong_extract_bit( occupied, (int)block->staging_lane ) && lane_info[ block->staging_lane ].insert_ready_block==block_to_idx( sched, block );
     890           0 : }
     891             : 
     892             : /* A block with absolutely nothing to dispatch may still speculate on
     893             :    additions when it holds a lane, is still receiving FEC sets, owes an
     894             :    addition the dispatcher offers as ready (meaning every writer of the
     895             :    account so far has completed), and the speculation pool has at least
     896             :    spec_free_min free entries.  Only the active block speculates.  An
     897             :    already speculating block keeps going until the last free entry.  In
     898             :    contrast, deactivated blocks are activated for speculation only if a
     899             :    higher free entry threshold is reached, so a trickle of freed entries
     900             :    does not bounce blocks in and out of the active state. */
     901             : static inline int
     902           0 : block_may_speculate( fd_sched_t * sched, fd_sched_block_t * block, ulong spec_free_min ) {
     903           0 :   if( block->fec_eos || !block->add_owed_cnt ) return 0;
     904           0 :   if( sched->lthash_spec_free_cnt<spec_free_min ) return 0;
     905             :   /* No need to check for tail because we have one block per lane. */
     906           0 :   if( !block_is_lane_head( sched, block ) ) return 0;
     907           0 :   return fd_rdisp_lane_ready_acct_cnt( sched->rdisp, block->staging_lane )>0UL;
     908           0 : }
     909             : 
     910             : static inline int
     911           0 : other_lane_has_real_work( fd_sched_t * sched, fd_sched_block_t * block ) {
     912           0 :   ulong staged_bitset = fd_ulong_clear_bit( sched->staged_bitset, (int)block->staging_lane );
     913           0 :   while( staged_bitset ) {
     914           0 :     int lane_idx  = fd_ulong_find_lsb( staged_bitset );
     915           0 :     staged_bitset = fd_ulong_pop_lsb( staged_bitset );
     916           0 :     fd_sched_block_t * head = block_pool_ele( sched, sched->staged_head_bank_idx[ lane_idx ] );
     917           0 :     if( block_is_done( block_pool_ele( sched, head->parent_idx ) ) && block_is_activatable( head ) ) return 1;
     918           0 :   }
     919           0 :   return 0;
     920           0 : }
     921             : 
     922             : static inline int
     923        3554 : block_should_deactivate( fd_sched_t * sched, fd_sched_block_t * block ) {
     924             :   /* We allow a grace period, during which a block has nothing to
     925             :      dispatch, but has something in-flight.  The block is allowed to
     926             :      stay activated and ingest FEC sets during this time.  The block
     927             :      will be deactivated if there's still nothing to dispatch by the
     928             :      time all in-flight tasks are completed, unless it can spend the
     929             :      idle time speculating on LtHash additions. */
     930        3554 :   return !block_is_activatable( block ) && !block_is_in_flight( block ) && !block_may_speculate( sched, block, 1UL );
     931        3554 : }
     932             : 
     933             : static inline int
     934           2 : block_is_prunable( fd_sched_block_t * block ) {
     935           2 :   return !block->in_rdisp && !block_is_in_flight( block );
     936           2 : }
     937             : 
     938             : static void
     939           0 : slot_state_push( fd_sched_t * sched, fd_sched_block_t * block, long end_tick ) {
     940           0 :   if( FD_UNLIKELY( sched->state_cnt>=FD_SCHED_SLOT_STATE_MAX ) ) { sched->state_drop_cnt++; return; }
     941           0 :   sched->state_buf[ sched->state_cnt++ ] = (fd_sched_slot_state_t){
     942           0 :     .slot       = block->slot,
     943           0 :     .start_tick = block->state_start_tick,
     944           0 :     .end_tick   = end_tick,
     945           0 :     .state      = block->state
     946           0 :   };
     947           0 : }
     948             : 
     949             : static void
     950           0 : slot_state_maybe_commit( fd_sched_t * sched, fd_sched_block_t * block, long now ) {
     951           0 :   if( block->state_cand==block->state || now-block->state_cand_tick<sched->state_debounce_ticks ) return;
     952             :   /* The first committed state starts at the window start. */
     953           0 :   if( FD_LIKELY( block->state!=FD_SCHED_SLOT_STATE_NONE ) ) {
     954           0 :     slot_state_push( sched, block, block->state_cand_tick );
     955           0 :     block->state_start_tick = block->state_cand_tick;
     956           0 :   }
     957           0 :   block->state = block->state_cand;
     958           0 : }
     959             : 
     960             : static void
     961        4554 : slot_state_eval( fd_sched_t * sched, fd_sched_block_t * block ) {
     962        4554 :   if( FD_LIKELY( !block->state_open ) ) return;
     963             : 
     964           1 :   ulong queued_cnt = block->txn_parsed_cnt-block->txn_exec_in_flight_cnt-block->txn_exec_done_cnt;
     965           1 :   ulong free_tiles = sched->txn_exec_ready_bitset[ 0 ] & sched->sigverify_ready_bitset[ 0 ] & sched->poh_ready_bitset[ 0 ] & sched->footer_ready_bitset[ 0 ] & sched->lthash_ready_bitset[ 0 ];
     966           1 :   uchar next;
     967           1 :   if(      block->block_end_signaled )           next = FD_SCHED_SLOT_STATE_FINISH;
     968           1 :   else if( queued_cnt && !free_tiles )           next = FD_SCHED_SLOT_STATE_BLOCKED_TILES;
     969           1 :   else if( queued_cnt && block->state_conflict ) next = FD_SCHED_SLOT_STATE_BLOCKED_CONFLICTS;
     970           1 :   else if( queued_cnt )                          next = block->state_cand; /* ready txn and free tile, dispatch is imminent */
     971           1 :   else if( !block->fec_eos )                     next = FD_SCHED_SLOT_STATE_BLOCKED_NETWORK;
     972           1 :   else                                           next = FD_SCHED_SLOT_STATE_FINISH;
     973             : 
     974           1 :   long now = fd_tickcount();
     975           1 :   slot_state_maybe_commit( sched, block, now );
     976           1 :   if( next!=block->state_cand ) {
     977           0 :     block->state_cand      = next;
     978           0 :     block->state_cand_tick = now;
     979           0 :   }
     980           1 : }
     981             : 
     982             : static void
     983             : slot_state_close( fd_sched_t * sched, fd_sched_block_t * block );
     984             : 
     985             : static void
     986           0 : slot_state_open( fd_sched_t * sched, fd_sched_block_t * block ) {
     987           0 :   ulong bank_idx = block_to_idx( sched, block );
     988           0 :   if( FD_UNLIKELY( sched->state_bank_idx!=ULONG_MAX && sched->state_bank_idx!=bank_idx ) ) {
     989           0 :     slot_state_close( sched, block_pool_ele( sched, sched->state_bank_idx ) );
     990           0 :   }
     991           0 :   sched->state_bank_idx   = bank_idx;
     992           0 :   block->state_open       = 1;
     993           0 :   block->state_conflict   = 0;
     994           0 :   block->state            = FD_SCHED_SLOT_STATE_NONE;
     995           0 :   block->state_cand       = FD_SCHED_SLOT_STATE_NONE;
     996           0 :   block->state_start_tick = block->state_cand_tick = fd_tickcount();
     997           0 : }
     998             : 
     999             : static void
    1000           2 : slot_state_close( fd_sched_t * sched, fd_sched_block_t * block ) {
    1001           2 :   if( FD_LIKELY( !block->state_open ) ) return;
    1002           0 :   block->state_open     = 0;
    1003           0 :   sched->state_bank_idx = ULONG_MAX;
    1004           0 :   long now = fd_tickcount();
    1005           0 :   slot_state_maybe_commit( sched, block, now );
    1006           0 :   if( FD_UNLIKELY( block->state==FD_SCHED_SLOT_STATE_NONE ) ) block->state = block->state_cand; /* window shorter than the debounce */
    1007           0 :   if( FD_UNLIKELY( block->state==FD_SCHED_SLOT_STATE_NONE ) ) return;
    1008           0 :   slot_state_push( sched, block, now );
    1009           0 : }
    1010             : 
    1011             : /* active_bank_set changes the active block.  Slot states are tracked
    1012             :    for at most one block at a time. */
    1013             : 
    1014             : static void
    1015        1003 : active_bank_set( fd_sched_t * sched, ulong bank_idx ) {
    1016        1003 :   sched->active_bank_idx = bank_idx;
    1017        1003 :   if( FD_UNLIKELY( bank_idx==ULONG_MAX ) ) return;
    1018        1001 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    1019        1001 :   if( FD_LIKELY( block->block_start_signaled && !block->block_end_done && !block->dying && !block->state_open ) ) slot_state_open( sched, block );
    1020        1001 : }
    1021             : 
    1022             : __attribute__((format(printf,2,3)))
    1023             : static void
    1024             : fd_sched_printf( fd_sched_t * sched,
    1025             :                  char const * fmt,
    1026          12 :                  ... ) {
    1027          12 :   va_list ap;
    1028          12 :   ulong len;
    1029          12 :   va_start( ap, fmt );
    1030          12 :   int ret = vsnprintf( sched->print_buf+sched->print_buf_sz,
    1031          12 :                        FD_SCHED_MAX_PRINT_BUF_SZ-sched->print_buf_sz,
    1032          12 :                        fmt, ap );
    1033          12 :   va_end( ap );
    1034          12 :   len = fd_ulong_if( ret<0, 0UL, fd_ulong_min( (ulong)ret, FD_SCHED_MAX_PRINT_BUF_SZ-sched->print_buf_sz-1UL ) );
    1035          12 :   sched->print_buf[ sched->print_buf_sz+len ] = '\0';
    1036          12 :   sched->print_buf_sz += len;
    1037          12 : }
    1038             : 
    1039             : FD_FN_UNUSED static void
    1040           0 : print_histogram( fd_sched_t * sched, fd_histf_t * hist, ulong converter, char * title ) {
    1041           0 :   fd_sched_printf( sched, " +---------------------+----------------------+--------------+\n" );
    1042           0 :   fd_sched_printf( sched, " | %-19s |                      | Count        |\n", title );
    1043           0 :   fd_sched_printf( sched, " +---------------------+----------------------+--------------+\n" );
    1044           0 : 
    1045           0 :   ulong total_count = 0;
    1046           0 :   for( ulong i=0UL; i<fd_histf_bucket_cnt( hist ); i++ ) {
    1047           0 :     total_count += fd_histf_cnt( hist, i );
    1048           0 :   }
    1049           0 : 
    1050           0 :   for( ulong i=0UL; i< fd_histf_bucket_cnt( hist ); i++ ) {
    1051           0 :     ulong bucket_count = fd_histf_cnt( hist, i );
    1052           0 : 
    1053           0 :     char * lt_str;
    1054           0 :     char lt_buf[ 64 ];
    1055           0 :     if( FD_UNLIKELY( i==fd_histf_bucket_cnt( hist )-1UL ) ) {
    1056           0 :       lt_str = "+Inf";
    1057           0 :     } else {
    1058           0 :       ulong edge = fd_histf_right( hist, i );
    1059           0 :       if( converter==FD_METRICS_CONVERTER_NANOSECONDS ) {
    1060           0 :         edge = fd_metrics_convert_ticks_to_nanoseconds( edge-1UL );
    1061           0 :         FD_TEST( fd_cstr_printf_check( lt_buf, sizeof( lt_buf ), NULL, "<= %lu nanos", edge ) );
    1062           0 :       } else if( converter==FD_METRICS_CONVERTER_NONE ) {
    1063           0 :         FD_TEST( fd_cstr_printf_check( lt_buf, sizeof( lt_buf ), NULL, "<= %lu", edge-1UL ) );
    1064           0 :       }
    1065           0 :       lt_str = lt_buf;
    1066           0 :     }
    1067           0 : 
    1068           0 :     /* Create visual bar - scale to max 20 characters. */
    1069           0 :     char bar_buf[ 22 ];
    1070           0 :     if( bucket_count>0UL && total_count>0UL ) {
    1071           0 :       ulong bar_length = (bucket_count*20UL)/total_count;
    1072           0 :       if( !bar_length ) bar_length = 1;
    1073           0 :       for( ulong j=0UL; j<bar_length; j++ ) { bar_buf[ j ] = '*'; }
    1074           0 :       bar_buf[ bar_length ] = '\0';
    1075           0 :     } else {
    1076           0 :       bar_buf[ 0 ] = '\0';
    1077           0 :     }
    1078           0 : 
    1079           0 :     fd_sched_printf( sched, " | %19s | %-20s | %12lu |\n", lt_str, bar_buf, bucket_count );
    1080           0 :   }
    1081           0 : }
    1082             : 
    1083             : FD_FN_UNUSED static void
    1084           0 : print_block_metrics( fd_sched_t * sched, fd_sched_block_t * block ) {
    1085           0 :   fd_sched_printf( sched, "block idx %lu, block slot %lu, parent_slot %lu, txn_parsed_cnt %u, txn_exec_done_cnt %u, txn_sigverify_done_cnt %u, poh_hashing_done_cnt %u, poh_hash_cmp_done_cnt %u, txn_done_cnt %u, shred_cnt %u, mblk_cnt %u, mblk_tick_cnt %u, mblk_freed_cnt %u, mblk_unhashed_cnt %u, txn_pool_max_popcnt %lu/%lu, mblk_pool_max_popcnt %lu/%lu, block_pool_max_popcnt %lu/%lu, mblks_rem %lu, txns_rem %lu, residual_sz %u, fec_buf_boff %u, fec_eob %d, fec_sob %d, shred_sz_overflow %d, fec_eos %d, rooted %d\n",
    1086           0 :                    block_to_idx( sched, block ), block->slot, block->parent_slot, block->txn_parsed_cnt, block->txn_exec_done_cnt, block->txn_sigverify_done_cnt, block->poh_hashing_done_cnt, block->poh_hash_cmp_done_cnt, block->txn_done_cnt, block->shred_cnt, block->mblk_cnt, block->mblk_tick_cnt, block->mblk_freed_cnt, block->mblk_unhashed_cnt, block->txn_pool_max_popcnt, sched->depth, block->mblk_pool_max_popcnt, sched->depth, block->block_pool_max_popcnt, sched->block_cnt_max, block->mblks_rem, block->txns_rem, block->residual_sz, block->fec_buf_boff, block->fec_eob, block->fec_sob, block->shred_sz_overflow, block->fec_eos, block->rooted );
    1087           0 : }
    1088             : 
    1089             : FD_FN_UNUSED static void
    1090           8 : print_block_debug( fd_sched_t * sched, fd_sched_block_t * block ) {
    1091           8 :   fd_sched_printf( sched, "block idx %lu, block slot %lu, parent_slot %lu, txn_parsed_cnt %u, txn_exec_in_flight_cnt %u, txn_exec_done_cnt %u, txn_sigverify_in_flight_cnt %u, txn_sigverify_done_cnt %u, poh_hashing_in_flight_cnt %u, poh_mblk_in_flight_cnt %u, poh_hashing_done_cnt %u, poh_hash_cmp_done_cnt %u, footer_in_flight_cnt %u, txn_done_cnt %u, shred_cnt %u, mblk_cnt %u, mblk_tick_cnt %u, mblk_freed_cnt %u, mblk_unhashed_cnt %u, txn_pool_max_popcnt %lu/%lu, mblk_pool_max_popcnt %lu/%lu, block_pool_max_popcnt %lu/%lu, lthash_writable_cnt %u, lthash_in_flight_cnt %u, sub_queued_cnt %u, sub_in_flight_cnt %u, sub_applied_cnt %u, add_owed_cnt %u, add_created_cnt %u, add_surfaced_cnt %u, add_done_cnt %u, add_applied_cnt %u, add_undone_cnt %u, mblks_rem %lu, txns_rem %lu, residual_sz %u, fec_buf_boff %u, fec_eob %d, fec_sob %d, shred_sz_overflow %d, fec_eos %d, rooted %d, dying %d, in_rdisp %d, block_start_signaled %d, block_end_signaled %d, block_start_done %d, block_end_done %d, staged %d, lthash_drained %d, lthash_draining %d, lthash_mode_picked %d, lthash_inband %d, lthash_oob_reservation %u, staging_lane %lu\n",
    1092           8 :                    block_to_idx( sched, block ), block->slot, block->parent_slot, block->txn_parsed_cnt, block->txn_exec_in_flight_cnt, block->txn_exec_done_cnt, block->txn_sigverify_in_flight_cnt, block->txn_sigverify_done_cnt, block->poh_hashing_in_flight_cnt, block->poh_mblk_in_flight_cnt, block->poh_hashing_done_cnt, block->poh_hash_cmp_done_cnt, block->footer_in_flight_cnt, block->txn_done_cnt, block->shred_cnt, block->mblk_cnt, block->mblk_tick_cnt, block->mblk_freed_cnt, block->mblk_unhashed_cnt, block->txn_pool_max_popcnt, sched->depth, block->mblk_pool_max_popcnt, sched->depth, block->block_pool_max_popcnt, sched->block_cnt_max, block->lthash_writable_cnt, block->lthash_in_flight_cnt, block->sub_queued_cnt, block->sub_in_flight_cnt, block->sub_applied_cnt, block->add_owed_cnt, block->add_created_cnt, block->add_surfaced_cnt, block->add_done_cnt, block->add_applied_cnt, block->add_undone_cnt, block->mblks_rem, block->txns_rem, block->residual_sz, block->fec_buf_boff, block->fec_eob, block->fec_sob, block->shred_sz_overflow, block->fec_eos, block->rooted, block->dying, block->in_rdisp, block->block_start_signaled, block->block_end_signaled, block->block_start_done, block->block_end_done, block->staged, block->lthash_drained, block->lthash_draining, block->lthash_mode_picked, block->lthash_inband, (uint)block->lthash_oob_reservation, block->staging_lane );
    1093           8 : }
    1094             : 
    1095             : FD_FN_UNUSED static void
    1096           2 : print_block_and_parent( fd_sched_t * sched, fd_sched_block_t * block ) {
    1097           2 :   print_block_debug( sched, block );
    1098           2 :   fd_sched_block_t * parent = block_pool_ele( sched, block->parent_idx );
    1099           2 :   if( FD_LIKELY( parent ) ) print_block_debug( sched, parent );
    1100           2 : }
    1101             : 
    1102             : FD_FN_UNUSED static void
    1103           2 : print_metrics( fd_sched_t * sched ) {
    1104           2 :     fd_sched_printf( sched, "metrics: block_added_cnt %u, block_added_staged_cnt %u, block_added_unstaged_cnt %u, block_added_dead_ood_cnt %u, block_removed_cnt %u, block_abandoned_cnt %u, block_bad_cnt %u, block_promoted_cnt %u, block_demoted_cnt %u, deactivate_no_child_cnt %u, deactivate_no_txn_cnt %u, deactivate_pruned_cnt %u, deactivate_abandoned_cnt %u, lane_switch_cnt %u, lane_promoted_cnt %u, lane_demoted_cnt %u, fork_observed_cnt %u, alut_success_cnt %u, alut_serializing_cnt %u, txn_abandoned_parsed_cnt %u, txn_abandoned_exec_done_cnt %u, txn_abandoned_done_cnt %u, txn_promoted_cnt %u, txn_max_in_flight_cnt %u, txn_weighted_in_flight_cnt %lu, txn_weighted_in_flight_tickcount %lu, txn_none_in_flight_tickcount %lu, txn_parsed_cnt %lu, txn_exec_done_cnt %lu, txn_sigverify_done_cnt %lu, txn_mixin_done_cnt %lu, txn_done_cnt %lu, mblk_parsed_cnt %lu, mblk_poh_hashed_cnt %lu, mblk_poh_done_cnt %lu, bytes_ingested_cnt %lu, bytes_ingested_unparsed_cnt %lu, bytes_dropped_cnt %lu, fec_cnt %lu, lthash_parsed_writable_cnt %lu, lthash_start_reg_cnt %u, lthash_late_reg_cnt %u, lthash_demote_reg_cnt %u, lthash_first_write_cnt %lu, lthash_coalesced_cnt %lu, lthash_entry_cnt %lu, lthash_sub_dispatched_cnt %lu, lthash_sub_task_cnt %lu, lthash_sub_applied_cnt %lu, lthash_add_created_cnt %lu, lthash_spec_cnt %lu, lthash_add_surfaced_cnt %lu, lthash_add_surfaced_stale_cnt %lu, lthash_add_dispatched_cnt %lu, lthash_add_task_cnt %lu, lthash_add_dispatched_stale_cnt %lu, lthash_add_batch_held_cnt %lu, lthash_add_batch_wait_idle_cnt %lu, lthash_add_applied_cnt %lu, lthash_add_undone_cnt %lu, lthash_spec_applied_cnt %lu, lthash_spec_stale_cnt %lu, lthash_spec_given_up_cnt %u, lthash_pool_full_cnt %u, lthash_spec_yield_cnt %u, lthash_frozen_cnt %lu, lthash_discarded_add_owed_cnt %u, lthash_discarded_add_pending_cnt %u, lthash_discarded_add_held_cnt %u, lthash_discarded_add_applied_cnt %u, lthash_discarded_sub_queued_cnt %u, lthash_discarded_sub_applied_cnt %u, lthash_inband_add_owed_cnt %u, lthash_inband_sub_queued_cnt %u, lthash_inband_block_cnt %u, lane_wait_cnt %lu\n",
    1105           2 :                      sched->metrics->block_added_cnt, sched->metrics->block_added_staged_cnt, sched->metrics->block_added_unstaged_cnt, sched->metrics->block_added_dead_ood_cnt, sched->metrics->block_removed_cnt, sched->metrics->block_abandoned_cnt, sched->metrics->block_bad_cnt, sched->metrics->block_promoted_cnt, sched->metrics->block_demoted_cnt, sched->metrics->deactivate_no_child_cnt, sched->metrics->deactivate_no_txn_cnt, sched->metrics->deactivate_pruned_cnt, sched->metrics->deactivate_abandoned_cnt, sched->metrics->lane_switch_cnt, sched->metrics->lane_promoted_cnt, sched->metrics->lane_demoted_cnt, sched->metrics->fork_observed_cnt, sched->metrics->alut_success_cnt, sched->metrics->alut_serializing_cnt, sched->metrics->txn_abandoned_parsed_cnt, sched->metrics->txn_abandoned_exec_done_cnt, sched->metrics->txn_abandoned_done_cnt, sched->metrics->txn_promoted_cnt, sched->metrics->txn_max_in_flight_cnt, sched->metrics->txn_weighted_in_flight_cnt, sched->metrics->txn_weighted_in_flight_tickcount, sched->metrics->txn_none_in_flight_tickcount, sched->metrics->txn_parsed_cnt, sched->metrics->txn_exec_done_cnt, sched->metrics->txn_sigverify_done_cnt, sched->metrics->txn_mixin_done_cnt, sched->metrics->txn_done_cnt, sched->metrics->mblk_parsed_cnt, sched->metrics->mblk_poh_hashed_cnt, sched->metrics->mblk_poh_done_cnt, sched->metrics->bytes_ingested_cnt, sched->metrics->bytes_ingested_unparsed_cnt, sched->metrics->bytes_dropped_cnt, sched->metrics->fec_cnt, sched->metrics->lthash_parsed_writable_cnt, sched->metrics->lthash_start_reg_cnt, sched->metrics->lthash_late_reg_cnt, sched->metrics->lthash_demote_reg_cnt, sched->metrics->lthash_first_write_cnt, sched->metrics->lthash_coalesced_cnt, sched->metrics->lthash_entry_cnt, sched->metrics->lthash_sub_dispatched_cnt, sched->metrics->lthash_sub_task_cnt, sched->metrics->lthash_sub_applied_cnt, sched->metrics->lthash_add_created_cnt, sched->metrics->lthash_spec_cnt, sched->metrics->lthash_add_surfaced_cnt, sched->metrics->lthash_add_surfaced_stale_cnt, sched->metrics->lthash_add_dispatched_cnt, sched->metrics->lthash_add_task_cnt, sched->metrics->lthash_add_dispatched_stale_cnt, sched->metrics->lthash_add_batch_held_cnt, sched->metrics->lthash_add_batch_wait_idle_cnt, sched->metrics->lthash_add_applied_cnt, sched->metrics->lthash_add_undone_cnt, sched->metrics->lthash_spec_applied_cnt, sched->metrics->lthash_spec_stale_cnt, sched->metrics->lthash_spec_given_up_cnt, sched->metrics->lthash_pool_full_cnt, sched->metrics->lthash_spec_yield_cnt, sched->metrics->lthash_frozen_cnt, sched->metrics->lthash_discarded_add_owed_cnt, sched->metrics->lthash_discarded_add_pending_cnt, sched->metrics->lthash_discarded_add_held_cnt, sched->metrics->lthash_discarded_add_applied_cnt, sched->metrics->lthash_discarded_sub_queued_cnt, sched->metrics->lthash_discarded_sub_applied_cnt, sched->metrics->lthash_inband_add_owed_cnt, sched->metrics->lthash_inband_sub_queued_cnt, sched->metrics->lthash_inband_block_cnt, sched->metrics->lane_wait_cnt );
    1106           2 : }
    1107             : 
    1108             : FD_FN_UNUSED static void
    1109           2 : print_sched( fd_sched_t * sched ) {
    1110           2 :   fd_sched_printf( sched, "sched canary 0x%lx, exec_cnt %lu, root_idx %lu, txn_exec_ready_bitset[ 0 ] 0x%lx, sigverify_ready_bitset[ 0 ] 0x%lx, poh_ready_bitset[ 0 ] 0x%lx, footer_ready_bitset[ 0 ] 0x%lx, lthash_ready_bitset[ 0 ] 0x%lx, active_idx %lu, staged_bitset %lu, staged_head_idx[0] %lu, staged_head_idx[1] %lu, staged_head_idx[2] %lu, staged_head_idx[3] %lu, staged_popcnt_wmk %lu, txn_pool_free_cnt %lu/%lu, lthash_add_batch_cnt %lu, lthash_draining_cnt %u, block_pool_popcnt %lu/%lu\n",
    1111           2 :                    sched->canary, sched->exec_cnt, sched->root_idx, sched->txn_exec_ready_bitset[ 0 ], sched->sigverify_ready_bitset[ 0 ], sched->poh_ready_bitset[ 0 ], sched->footer_ready_bitset[ 0 ], sched->lthash_ready_bitset[ 0 ], sched->active_bank_idx, sched->staged_bitset, sched->staged_head_bank_idx[ 0 ], sched->staged_head_bank_idx[ 1 ], sched->staged_head_bank_idx[ 2 ], sched->staged_head_bank_idx[ 3 ], sched->staged_popcnt_wmk, sched->txn_pool_free_cnt, sched->depth, sched->lthash_add_batch->cnt, sched->lthash_draining_cnt, sched->block_pool_popcnt, sched->block_cnt_max );
    1112           2 :   fd_sched_block_t * active_block = block_pool_ele( sched, sched->active_bank_idx );
    1113           2 :   if( active_block ) print_block_debug( sched, active_block );
    1114          10 :   for( int l=0; l<(int)FD_SCHED_MAX_STAGING_LANES; l++ ) {
    1115           8 :     if( fd_ulong_extract_bit( sched->staged_bitset, l ) ) {
    1116           2 :       fd_sched_block_t * block = block_pool_ele( sched, sched->staged_head_bank_idx[ l ] );
    1117           2 :       print_block_debug( sched, block );
    1118           2 :     }
    1119           8 :   }
    1120           2 : }
    1121             : 
    1122             : FD_FN_UNUSED static void
    1123           2 : print_all( fd_sched_t * sched, fd_sched_block_t * block ) {
    1124           2 :   print_metrics( sched );
    1125           2 :   print_sched( sched );
    1126           2 :   print_block_and_parent( sched, block );
    1127           2 : }
    1128             : 
    1129             : /* How many idle exec tiles a poll leaves free for the critical path
    1130             :    when it hands out anything but a transaction.  We leave one idle
    1131             :    while the block may still get transactions and has none in flight,
    1132             :    so that a transaction that becomes ready dispatches right away.
    1133             :    Once the block has all its FEC sets, a queued transaction only
    1134             :    becomes ready when an in-flight one completes and frees its own
    1135             :    tile, so there is nothing to reserve for.  The same holds while a
    1136             :    transaction is in flight.  A single exec tile is kept busy
    1137             :    regardless. */
    1138             : static inline int
    1139           0 : idle_reserve( fd_sched_t const * sched, fd_sched_block_t const * block ) {
    1140           0 :   return !(block->fec_eos || block->txn_exec_in_flight_cnt>0U || sched->exec_cnt==1UL);
    1141           0 : }
    1142             : 
    1143             : /* The reserve for an LtHash batch.  An LtHash batch may hold a tile for
    1144             :    longer than a sigverify, since its accounts may be large or cold, so
    1145             :    it always leaves one tile idle, except with a single exec tile. */
    1146             : static inline int
    1147           0 : lthash_idle_reserve( fd_sched_t const * sched ) {
    1148           0 :   return sched->exec_cnt>1UL;
    1149           0 : }
    1150             : 
    1151             : /* Each tile runs at most one task at a time, so no tile may be busy
    1152             :    in two bitsets. */
    1153             : static inline void
    1154           0 : exec_ready_bitsets_check( fd_sched_t * sched ) {
    1155           0 :   ulong mask           = fd_ulong_mask_lsb( (int)sched->exec_cnt );
    1156           0 :   ulong exec_busy      = (~sched->txn_exec_ready_bitset [ 0 ])&mask;
    1157           0 :   ulong sigverify_busy = (~sched->sigverify_ready_bitset[ 0 ])&mask;
    1158           0 :   ulong poh_busy       = (~sched->poh_ready_bitset      [ 0 ])&mask;
    1159           0 :   ulong footer_busy      = (~sched->footer_ready_bitset     [ 0 ])&mask;
    1160           0 :   ulong lthash_busy    = (~sched->lthash_ready_bitset   [ 0 ])&mask;
    1161           0 :   if( FD_UNLIKELY( (exec_busy&(sigverify_busy|poh_busy|footer_busy|lthash_busy)) | (sigverify_busy&(poh_busy|footer_busy|lthash_busy)) | (poh_busy&(footer_busy|lthash_busy)) | (footer_busy&lthash_busy) ) ) {
    1162           0 :     FD_LOG_CRIT(( "invariant violation: txn_exec_ready_bitset 0x%lx sigverify_ready_bitset 0x%lx poh_ready_bitset 0x%lx footer_ready_bitset 0x%lx lthash_ready_bitset 0x%lx", sched->txn_exec_ready_bitset[ 0 ], sched->sigverify_ready_bitset[ 0 ], sched->poh_ready_bitset[ 0 ], sched->footer_ready_bitset[ 0 ], sched->lthash_ready_bitset[ 0 ] ));
    1163           0 :   }
    1164           0 : }
    1165             : 
    1166             : /* Every busy tile should be accounted for by an in-flight task of a
    1167             :    staged lane head, which could be an actively dispatching block, or a
    1168             :    dying block waiting for in-flight tasks to drain. */
    1169             : static void
    1170           0 : exec_busy_check( fd_sched_t * sched, fd_sched_block_t * block ) {
    1171           0 :   exec_ready_bitsets_check( sched );
    1172           0 :   ulong total_exec_busy_cnt = sched->exec_cnt-(ulong)fd_ulong_popcnt( sched->txn_exec_ready_bitset[ 0 ]&sched->sigverify_ready_bitset[ 0 ]&sched->poh_ready_bitset[ 0 ]&sched->footer_ready_bitset[ 0 ]&sched->lthash_ready_bitset[ 0 ] );
    1173           0 :   if( FD_UNLIKELY( block_in_flight_cnt( block )!=total_exec_busy_cnt ) ) {
    1174             :     /* Ideally we'd simply assert that the two sides of the equation are
    1175             :        equal.  But abandoned blocks throw a wrench into this.  We allow
    1176             :        abandoned blocks to have in-flight transactions that are
    1177             :        naturally drained while we try to dispatch from another block.
    1178             :        In such cases, the total number of in-flight transactions should
    1179             :        include the abandoned blocks too.  The contract is that blocks
    1180             :        with in-flight transactions cannot be abandoned or demoted from
    1181             :        rdisp.  So a dying block has to be the head of one of the staging
    1182             :        lanes. */
    1183             :     // FIXME This contract no longer true if we implement immediate
    1184             :     // demotion of abandoned blocks.
    1185           0 :     ulong total_in_flight = 0UL;
    1186           0 :     for( int l=0; l<(int)FD_SCHED_MAX_STAGING_LANES; l++ ) {
    1187           0 :       if( fd_ulong_extract_bit( sched->staged_bitset, l ) ) {
    1188           0 :         fd_sched_block_t * staged_block = block_pool_ele( sched, sched->staged_head_bank_idx[ l ] );
    1189           0 :         if( FD_UNLIKELY( block_is_in_flight( staged_block )&&!(staged_block==block||staged_block->dying) ) ) {
    1190           0 :           sched->print_buf_sz = 0UL;
    1191           0 :           print_all( sched, staged_block );
    1192           0 :           FD_LOG_NOTICE(( "%s", sched->print_buf ));
    1193           0 :           FD_LOG_CRIT(( "invariant violation: in-flight block is neither active nor dying" ));
    1194           0 :         }
    1195           0 :         total_in_flight += block_in_flight_cnt( staged_block );
    1196           0 :       }
    1197           0 :     }
    1198           0 :     if( FD_UNLIKELY( total_in_flight!=total_exec_busy_cnt ) ) {
    1199           0 :       sched->print_buf_sz = 0UL;
    1200           0 :       print_all( sched, block );
    1201           0 :       FD_LOG_NOTICE(( "%s", sched->print_buf ));
    1202           0 :       FD_LOG_CRIT(( "invariant violation: total_in_flight %lu != total_exec_busy_cnt %lu", total_in_flight, total_exec_busy_cnt ));
    1203           0 :     }
    1204           0 :     FD_LOG_DEBUG(( "exec_busy_cnt %lu checks out", total_exec_busy_cnt ));
    1205           0 :   }
    1206           0 : }
    1207             : 
    1208             : static inline void
    1209             : record_dead_reason( fd_sched_block_t * block,
    1210           2 :                     int                dead_reason ) {
    1211           2 :   if( FD_LIKELY( block->dead_reason==FD_SCHED_DEAD_REASON_NONE ) ) {
    1212           2 :     block->dead_reason = dead_reason;
    1213           2 :     block->discarded = 0;
    1214           2 :   }
    1215           2 : }
    1216             : 
    1217             : /* Record that the block goes down with its lineage rather than for a
    1218             :    defect of its own, aka DEAD_ANCESTOR.  Also record the parent's
    1219             :    deadness flavor so we can tell a discarded (but not invalid) lineage
    1220             :    from an invalid one.
    1221             : 
    1222             :    Gated on the block having no dead reason of its own, so a verdict
    1223             :    sched already reached is never overwritten.  That gate neither bounds
    1224             :    how often the inherit runs nor sees a ruling the replay tile made,
    1225             :    which leaves dead_reason NONE.  Callers are responsible for invoking
    1226             :    this only on a block that is newly going down. */
    1227             : static inline void
    1228           0 : record_lineage_death( fd_sched_block_t * block, fd_sched_block_t const * parent ) {
    1229           0 :   if( FD_LIKELY( block->dead_reason==FD_SCHED_DEAD_REASON_NONE ) ) {
    1230           0 :     record_dead_reason( block, FD_SCHED_DEAD_REASON_DEAD_ANCESTOR );
    1231           0 :     block->discarded = parent->discarded;
    1232           0 :   }
    1233           0 : }
    1234             : 
    1235             : static void
    1236           2 : handle_bad_block( fd_sched_t * sched, fd_sched_block_t * block, int dead_reason ) {
    1237           2 :   record_dead_reason( block, dead_reason );
    1238           2 :   sched->print_buf_sz = 0UL;
    1239           2 :   print_all( sched, block );
    1240           2 :   FD_LOG_DEBUG(( "%s", sched->print_buf ));
    1241           2 :   subtree_abandon( sched, block );
    1242           2 :   sched->metrics->block_bad_cnt++;
    1243           2 :   check_or_set_active_block( sched );
    1244           2 : }
    1245             : 
    1246             : /* Returns the number of shred boundaries strictly before query, or
    1247             :    UINT_MAX once the block's shred sizes have overflowed their window.
    1248             :    Transaction offsets are visited in nondecreasing order, so each shred
    1249             :    length is scanned at most once per block. */
    1250             : static uint
    1251             : shred_split( fd_sched_block_t * block,
    1252        7638 :              uint               query ) {
    1253        7638 :   if( FD_UNLIKELY( block->shred_sz_overflow ) ) return UINT_MAX;
    1254        7638 :   FD_TEST( block->shred_scan_idx<=block->shred_cnt );
    1255        7638 :   FD_TEST( block->shred_scan_off<=query );
    1256      121334 :   while( block->shred_scan_idx<block->shred_cnt ) {
    1257      121334 :     uint next_off = block->shred_scan_off + (uint)block->shred_sz[ block->shred_scan_idx&(uint)(FD_SCHED_SHRED_SZ_WINDOW-1UL) ];
    1258      121334 :     if( next_off>=query ) break;
    1259      113696 :     block->shred_scan_off = next_off;
    1260      113696 :     block->shred_scan_idx++;
    1261      113696 :   }
    1262        7638 :   return block->shred_scan_idx;
    1263        7638 : }
    1264             : 
    1265             : 
    1266             : /* Out-of-band LtHash helpers. */
    1267             : 
    1268             : static inline int
    1269             : block_owns_lane_map( fd_sched_t *       sched,
    1270        4433 :                      fd_sched_block_t * block ) {
    1271        4433 :   return block->staged && sched->lthash_lane[ block->staging_lane ].bank_idx==block_to_idx( sched, block );
    1272        4433 : }
    1273             : 
    1274             : static inline fd_lthash_value_t *
    1275             : block_lthash_delta( fd_sched_t *       sched,
    1276           0 :                     fd_sched_block_t * block ) {
    1277           0 :   FD_TEST( block->lthash_mode_picked && !block->lthash_inband );
    1278           0 :   return sched->lthash_delta+block->lthash_oob_reservation;
    1279           0 : }
    1280             : 
    1281             : static inline fd_sched_lthash_entry_t *
    1282             : lthash_lane_query( fd_sched_t *           sched,
    1283             :                    ulong                  lane,
    1284        2889 :                    fd_acct_addr_t const * acct ) {
    1285        2889 :   fd_sched_lthash_lane_t * lane_map = sched->lthash_lane+lane;
    1286        2889 :   return lthash_map_ele_query( lane_map->map, acct, NULL, lane_map->entry_pool );
    1287        2889 : }
    1288             : 
    1289             : static void
    1290             : lthash_spec_release( fd_sched_t * sched,
    1291           0 :                      uint         idx ) {
    1292           0 :   if( FD_UNLIKELY( (ulong)idx>=FD_SCHED_LTHASH_SPEC_MAX || sched->lthash_spec_free_cnt>=FD_SCHED_LTHASH_SPEC_MAX ) ) {
    1293           0 :     FD_LOG_CRIT(( "invariant violation: release of speculation entry %u, free cnt %lu", idx, sched->lthash_spec_free_cnt ));
    1294           0 :   }
    1295           0 :   sched->lthash_spec_free[ sched->lthash_spec_free_cnt++ ] = idx;
    1296           0 : }
    1297             : 
    1298             : /* Releases the lane's map.  The entries' speculation entries go back
    1299             :    to the pool and the map forgets the accounts.  Pointers to and
    1300             :    indices of its entries are invalid afterwards. */
    1301             : static void
    1302             : lthash_release_lane_map( fd_sched_t * sched,
    1303           2 :                          ulong        lane ) {
    1304           2 :   fd_sched_lthash_lane_t * lane_map = sched->lthash_lane+lane;
    1305           2 :   if( FD_UNLIKELY( lane_map->bank_idx==ULONG_MAX ) ) FD_LOG_CRIT(( "invariant violation: releasing lane %lu, which no bank owns", lane ));
    1306           2 :   for( ulong i=0UL; i<lane_map->entry_cnt; i++ ) {
    1307           0 :     if( lane_map->entry_pool[ i ].spec_idx!=UINT_MAX ) lthash_spec_release( sched, lane_map->entry_pool[ i ].spec_idx );
    1308           0 :   }
    1309           2 :   lthash_map_reset( lane_map->map );
    1310           2 :   lane_map->entry_cnt = 0U;
    1311           2 :   lane_map->bank_idx  = ULONG_MAX;
    1312           2 : }
    1313             : 
    1314             : /* A new entry for acct in the block's lane map.  The account owes its
    1315             :    one subtraction, so the entry goes on the block's sub queue. */
    1316             : static fd_sched_lthash_entry_t *
    1317             : lthash_entry_new( fd_sched_t *           sched,
    1318             :                   fd_sched_block_t *     block,
    1319        2889 :                   fd_acct_addr_t const * acct ) {
    1320             :   /* A block claims its lane map upon being staged. */
    1321        2889 :   if( FD_UNLIKELY( !block_owns_lane_map( sched, block ) ) ) FD_LOG_CRIT(( "invariant violation: block %lu:%lu owns no lane map", block->slot, block_to_idx( sched, block ) ));
    1322        2889 :   ulong                    lane     = block->staging_lane;
    1323        2889 :   fd_sched_lthash_lane_t * lane_map = sched->lthash_lane+lane;
    1324        2889 :   if( FD_UNLIKELY( lane_map->entry_cnt==FD_RDISP_MAX_WRITERS_PER_BLOCK ) ) FD_LOG_CRIT(( "lane %lu has no free entry", lane ));
    1325        2889 :   if( FD_UNLIKELY( lthash_map_ele_query_const( lane_map->map, acct, NULL, lane_map->entry_pool ) ) ) {
    1326           0 :     FD_BASE58_ENCODE_32_BYTES( acct->b, acct_str );
    1327           0 :     FD_LOG_CRIT(( "invariant violation: account %s already in lane %lu", acct_str, lane ));
    1328           0 :   }
    1329             : 
    1330        2889 :   uint                      idx = lane_map->entry_cnt++;
    1331        2889 :   fd_sched_lthash_entry_t * e   = lane_map->entry_pool+idx;
    1332        2889 :   e->acct      = *acct;
    1333        2889 :   e->q_next    = UINT_MAX;
    1334        2889 :   e->spec_idx  = UINT_MAX;
    1335        2889 :   e->ptxn_idx  = 0U;
    1336        2889 :   e->bank_idx  = (uint)lane_map->bank_idx;
    1337        2889 :   e->sub_state = (uchar)FD_SCHED_LTHASH_SUB_QUEUED;
    1338        2889 :   e->add_state = (uchar)FD_SCHED_LTHASH_ADD_NONE;
    1339        2889 :   lthash_map_idx_insert( lane_map->map, idx, lane_map->entry_pool );
    1340        2889 :   sched->metrics->lthash_entry_cnt++;
    1341        2889 :   if( FD_LIKELY( block->sub_q_tail==UINT_MAX ) ) block->sub_q_head = idx;
    1342        1919 :   else lane_map->entry_pool[ block->sub_q_tail ].q_next = idx;
    1343        2889 :   block->sub_q_tail = idx;
    1344        2889 :   block->sub_queued_cnt++;
    1345        2889 :   return e;
    1346        2889 : }
    1347             : 
    1348             : /* Applies the rewrite rule to the entry.  If its addition was applied,
    1349             :    the value in its speculation entry is subtracted from block_lthash,
    1350             :    the block's delta.  Then the speculation entry is freed, the pseudo
    1351             :    transaction of record is cleared and the entry becomes ADD_NONE, so
    1352             :    the stale pseudo transaction is completed on the spot when it
    1353             :    surfaces, or is ignored when its in-flight hashing finishes.
    1354             :    sub_state is untouched. */
    1355             : static void
    1356             : lthash_entry_clear_add( fd_sched_t *              sched,
    1357             :                         fd_sched_lthash_entry_t * e,
    1358           0 :                         fd_lthash_value_t *       block_lthash ) {
    1359           0 :   uint spec_idx = e->spec_idx;
    1360           0 :   if( e->add_state==FD_SCHED_LTHASH_ADD_DONE ) {
    1361           0 :     if( FD_UNLIKELY( spec_idx==UINT_MAX ) ) FD_LOG_CRIT(( "invariant violation: rewrite of an applied addition with no entry to undo it, bank idx %u", e->bank_idx ));
    1362           0 :     fd_lthash_sub( block_lthash, sched->lthash_spec_pool+spec_idx );
    1363           0 :   }
    1364           0 :   if( spec_idx!=UINT_MAX ) {
    1365           0 :     lthash_spec_release( sched, spec_idx );
    1366           0 :     e->spec_idx = UINT_MAX;
    1367           0 :   }
    1368           0 :   e->ptxn_idx  = 0U;
    1369           0 :   e->add_state = (uchar)FD_SCHED_LTHASH_ADD_NONE;
    1370           0 : }
    1371             : 
    1372             : /* A write to the entry's account arrived after its addition was
    1373             :    created, which undoes the addition. */
    1374             : static void
    1375             : lthash_entry_rewrite( fd_sched_t *              sched,
    1376             :                       fd_sched_block_t *        block,
    1377           0 :                       fd_sched_lthash_entry_t * e ) {
    1378           0 :   int undone = e->add_state==FD_SCHED_LTHASH_ADD_DONE;
    1379           0 :   sched->metrics->lthash_add_undone_cnt += (ulong)undone;
    1380           0 :   sched->metrics->lthash_spec_stale_cnt += (ulong)(!undone && e->spec_idx!=UINT_MAX);
    1381           0 :   block->add_undone_cnt                 += (uint)undone;
    1382           0 :   lthash_entry_clear_add( sched, e, block_lthash_delta( sched, block ) );
    1383           0 : }
    1384             : 
    1385             : static void
    1386             : lthash_entry_new_write_observed( fd_sched_t *           sched,
    1387             :                                  fd_sched_block_t *     block,
    1388        2889 :                                  fd_acct_addr_t const * acct ) {
    1389        2889 :   fd_sched_lthash_entry_t * e = lthash_lane_query( sched, block->staging_lane, acct );
    1390        2889 :   if( FD_LIKELY( !e ) ) lthash_entry_new( sched, block, acct );
    1391           0 :   else                  lthash_entry_rewrite( sched, block, e );
    1392        2889 : }
    1393             : 
    1394             : /* The dispatcher created addition pseudo transaction ptxn_idx for the
    1395             :    block.  The pseudo transaction's account gets stamped with ptxn_idx
    1396             :    as its addition of record.  A speculative addition reserves a
    1397             :    speculation entry so the addition can be undone later if needed.
    1398             :    Caller is responsible for ensuring a speculation entry is free. */
    1399             : static void
    1400             : lthash_add_created( fd_sched_t *       sched,
    1401             :                     fd_sched_block_t * block,
    1402             :                     ulong              ptxn_idx,
    1403           0 :                     int                speculative ) {
    1404           0 :   FD_TEST( ptxn_idx & FD_RDISP_LTHASH_PSEUDO_TXN );
    1405           0 :   block->add_created_cnt++;
    1406           0 :   sched->metrics->lthash_add_created_cnt++;
    1407           0 :   fd_acct_addr_t acct[ 1 ];
    1408           0 :   fd_rdisp_pseudo_txn_to_addr( sched->rdisp, ptxn_idx, acct );
    1409           0 :   fd_sched_lthash_entry_t * e = lthash_lane_query( sched, block->staging_lane, acct );
    1410           0 :   if( FD_LIKELY( !e ) ) {
    1411             :     /* The account may have no entry yet, which happens when the block
    1412             :        was not the lane's owner while its transactions parsed, and then
    1413             :        one is created now. */
    1414           0 :     e = lthash_entry_new( sched, block, acct );
    1415           0 :   } else if( FD_UNLIKELY( e->add_state!=FD_SCHED_LTHASH_ADD_NONE || e->ptxn_idx || e->spec_idx!=UINT_MAX ) ) {
    1416             :     /* Otherwise the entry holds no addition of record.  The dispatcher
    1417             :        only creates an addition for an account whose last reference in
    1418             :        the block is a write, and there are three ways an account gets
    1419             :        one.  A parsed transaction writes it, start-of-block or late
    1420             :        registration writes it, or a given up speculation hands it back
    1421             :        as written.  Each of those cleared the entry's addition on the
    1422             :        way. */
    1423           0 :     FD_LOG_CRIT(( "invariant violation: addition created for an account with an addition of record, block %lu:%lu, ptxn_idx 0x%lx, add_state %u, entry ptxn_idx 0x%x, spec_idx %u", block->slot, block_to_idx( sched, block ), ptxn_idx, (uint)e->add_state, e->ptxn_idx, e->spec_idx ));
    1424           0 :   }
    1425           0 :   e->add_state = (uchar)FD_SCHED_LTHASH_ADD_PENDING;
    1426           0 :   e->ptxn_idx  = (uint)ptxn_idx;
    1427           0 :   if( FD_LIKELY( speculative ) ) {
    1428           0 :     FD_TEST( sched->lthash_spec_free_cnt );
    1429           0 :     e->spec_idx = sched->lthash_spec_free[ --sched->lthash_spec_free_cnt ];
    1430           0 :   }
    1431           0 : }
    1432             : 
    1433             : /* The block was just staged and claims its lane's map.  A lane the
    1434             :    block already owns is left alone. */
    1435             : static void
    1436             : lthash_claim_lane_map( fd_sched_t *       sched,
    1437        1001 :                        fd_sched_block_t * block ) {
    1438        1001 :   FD_TEST( block_is_lane_head( sched, block ) );
    1439        1001 :   fd_sched_lthash_lane_t * lane_map = sched->lthash_lane+block->staging_lane;
    1440        1001 :   ulong                    bank_idx = block_to_idx( sched, block );
    1441        1001 :   if( lane_map->bank_idx==bank_idx ) return;
    1442        1001 :   if( FD_UNLIKELY( lane_map->bank_idx!=ULONG_MAX ) ) FD_LOG_CRIT(( "invariant violation: claiming lane %lu for bank %lu, owned by bank %lu", block->staging_lane, bank_idx, lane_map->bank_idx ));
    1443        1001 :   FD_TEST( bank_idx<UINT_MAX && !lane_map->entry_cnt );
    1444        1001 :   lane_map->bank_idx = bank_idx;
    1445        1001 : }
    1446             : 
    1447             : /* Which hashing mode does a block get?  We would love to hash out of
    1448             :    band wherever we can, and the one thing that stands in the way is
    1449             :    demotion.  A demoted block parks its writable set in the dispatcher's
    1450             :    unstaged entries, and that parking lot has limited capacity.  We
    1451             :    don't wanna be stuck in a scenario where we have to demote but we
    1452             :    can't due to limited parking space.  So a block reserves space when
    1453             :    it picks out-of-band hashing mode.  Mode picking happens the first
    1454             :    time a block would do out-of-band work beyond the dispatcher tracking
    1455             :    its writers, which is its first drain or its BLOCK_START, whichever
    1456             :    comes first.  A child starts only once its parent is done, so the
    1457             :    blocks holding reservations are the executing frontier of each fork
    1458             :    plus whatever demoted blocks still wait to be promoted.  Wide forking
    1459             :    is the only way to run out.
    1460             : 
    1461             :    If a reservation cannot be made, the block commits to in-band
    1462             :    hashing.  Such a block parks nothing when demoted, so every demotion
    1463             :    fits and a lane can always be given up.  So replay keeps making
    1464             :    progress, albeit a tad bit slower. */
    1465             : static void
    1466             : lthash_pick_mode( fd_sched_t *       sched,
    1467           0 :                   fd_sched_block_t * block ) {
    1468           0 :   FD_TEST( !block->lthash_mode_picked );
    1469           0 :   FD_TEST( !block->lthash_in_flight_cnt && !block->sub_in_flight_cnt && !block->add_created_cnt );
    1470           0 :   block->lthash_mode_picked = 1;
    1471           0 :   ulong free_reservations = (~sched->lthash_oob_reserved)&fd_ulong_mask_lsb( (int)sched->lthash_oob_block_max );
    1472           0 :   if( FD_LIKELY( free_reservations ) ) {
    1473           0 :     int reservation = fd_ulong_find_lsb( free_reservations );
    1474           0 :     FD_TEST( fd_lthash_is_zero( sched->lthash_delta+reservation ) );
    1475           0 :     sched->lthash_oob_reserved    = fd_ulong_set_bit( sched->lthash_oob_reserved, reservation );
    1476           0 :     block->lthash_oob_reservation = (uchar)reservation;
    1477           0 :     return;
    1478           0 :   }
    1479           0 :   ulong bank_idx = block_to_idx( sched, block );
    1480           0 :   block->lthash_inband = 1;
    1481           0 :   sched->metrics->lthash_inband_block_cnt++;
    1482           0 :   sched->metrics->lthash_inband_add_owed_cnt   += block->add_owed_cnt;
    1483           0 :   sched->metrics->lthash_inband_sub_queued_cnt += block->sub_queued_cnt;
    1484           0 :   block->add_owed_cnt   = 0U;
    1485           0 :   block->sub_queued_cnt = 0U;
    1486           0 :   block->sub_q_head     = UINT_MAX;
    1487           0 :   block->sub_q_tail     = UINT_MAX;
    1488           0 :   if( FD_LIKELY( block_owns_lane_map( sched, block ) ) ) lthash_release_lane_map( sched, block->staging_lane );
    1489           0 :   if( FD_UNLIKELY( fd_rdisp_clear_writable_set( sched->rdisp, bank_idx ) ) ) FD_LOG_CRIT(( "fd_rdisp_clear_writable_set failed for block %lu:%lu", block->slot, bank_idx ));
    1490           0 : }
    1491             : 
    1492             : static void
    1493             : lthash_release_reservation( fd_sched_t *       sched,
    1494           2 :                             fd_sched_block_t * block ) {
    1495           2 :   if( block->lthash_mode_picked && !block->lthash_inband ) sched->lthash_oob_reserved = fd_ulong_clear_bit( sched->lthash_oob_reserved, (int)block->lthash_oob_reservation );
    1496           2 :   block->lthash_mode_picked = 0;
    1497           2 :   block->lthash_inband      = 0;
    1498           2 : }
    1499             : 
    1500             : /* Marks a staged fec_eos block as draining.  Polls that find nothing to
    1501             :    dispatch then drain it a chunk at a time.  Creating pseudo
    1502             :    transactions costs replay tile time, so the drain yields to
    1503             :    everything the block must do and overlaps with exec tile work. */
    1504             : static void
    1505             : lthash_drain_later( fd_sched_t *       sched,
    1506         992 :                     fd_sched_block_t * block ) {
    1507         992 :   if( !block->staged || !block->fec_eos || block->lthash_drained || block->lthash_draining ) return;
    1508         992 :   sched->lthash_draining_cnt++;
    1509         992 :   block->lthash_draining = 1;
    1510         992 : }
    1511             : 
    1512             : /* Pops up to max of the accounts that rdisp is still tracking for us,
    1513             :    such that each gets an addition pseudo transaction.  The block must
    1514             :    be insert ready.  This is a no-op for an unstaged block, which drains
    1515             :    when promoted.  An in-band block has nothing to drain and counts as
    1516             :    drained.  Returns the number of pseudo transactions created. */
    1517             : static ulong
    1518             : lthash_maybe_drain( fd_sched_t *       sched,
    1519             :                     fd_sched_block_t * block,
    1520           0 :                     ulong              max ) {
    1521           0 :   if( !block->staged || !block->fec_eos || block->lthash_drained ) return 0UL;
    1522           0 :   if( FD_UNLIKELY( !block->lthash_mode_picked ) ) lthash_pick_mode( sched, block );
    1523           0 :   if( FD_UNLIKELY( block->lthash_inband ) ) {
    1524           0 :     sched->lthash_draining_cnt -= (uint)block->lthash_draining;
    1525           0 :     block->lthash_draining      = 0;
    1526           0 :     block->lthash_drained       = 1;
    1527           0 :     return 0UL;
    1528           0 :   }
    1529           0 :   ulong bank_idx = block_to_idx( sched, block );
    1530             :   /* fd_rdisp_add_pseudo_txn() returns 0 for a block that is not insert
    1531             :      ready, which we would mistake for a finished drain.  A staged block
    1532             :      is alone on its lane, so it is insert ready by construction, as
    1533             :      long as it in fact holds its lane.  So we assert being head, which
    1534             :      implies being tail. */
    1535           0 :   FD_TEST( block_is_lane_head( sched, block ) );
    1536           0 :   ulong pop_cnt = 0UL;
    1537           0 :   while( pop_cnt<max ) {
    1538           0 :     ulong ptxn_idx = fd_rdisp_add_pseudo_txn( sched->rdisp, bank_idx );
    1539           0 :     if( FD_UNLIKELY( !ptxn_idx ) ) break;
    1540           0 :     block->add_owed_cnt--;
    1541           0 :     lthash_add_created( sched, block, ptxn_idx, 0 );
    1542           0 :     pop_cnt++;
    1543           0 :   }
    1544           0 :   if( FD_UNLIKELY( pop_cnt==max ) ) {
    1545             :     /* The set may hold more, so the next call continues. */
    1546           0 :     sched->lthash_draining_cnt += (uint)!block->lthash_draining;
    1547           0 :     block->lthash_draining      = 1;
    1548           0 :     return pop_cnt;
    1549           0 :   }
    1550           0 :   sched->lthash_draining_cnt -= (uint)block->lthash_draining;
    1551           0 :   block->lthash_draining      = 0;
    1552           0 :   block->lthash_drained       = 1;
    1553           0 :   return pop_cnt;
    1554           0 : }
    1555             : 
    1556             : /* Returns the draining block we should work on next.  We prioritize
    1557             :    draining the active block, otherwise we work on the draining block on
    1558             :    the lowest lane.  A block that is not active cannot run its additions
    1559             :    until it's activated, so draining it ahead of the active block only
    1560             :    delays the active block's end.  Assumes lthash_draining_cnt is
    1561             :    non-zero. */
    1562             : static fd_sched_block_t *
    1563           0 : lthash_find_draining_block( fd_sched_t * sched ) {
    1564           0 :   fd_sched_block_t * active = block_pool_ele( sched, sched->active_bank_idx );
    1565           0 :   if( FD_LIKELY( active && active->lthash_draining ) ) return active;
    1566           0 :   for( ulong lanes=sched->staged_bitset; lanes; lanes=fd_ulong_pop_lsb( lanes ) ) {
    1567           0 :     fd_sched_block_t * block = block_pool_ele( sched, sched->staged_head_bank_idx[ fd_ulong_find_lsb( lanes ) ] );
    1568           0 :     if( block->lthash_draining ) return block;
    1569           0 :   }
    1570           0 :   FD_LOG_CRIT(( "invariant violation: %u blocks draining but no staged block is", sched->lthash_draining_cnt ));
    1571           0 : }
    1572             : 
    1573             : /* Paced at up to once per fd_sched_task_next_ready(), from the points
    1574             :    where it has nothing left to hand out. */
    1575             : static inline ulong
    1576           0 : lthash_drain_chunk( fd_sched_t * sched ) {
    1577           0 :   if( FD_LIKELY( !sched->lthash_draining_cnt ) ) return 0UL;
    1578           0 :   return lthash_maybe_drain( sched, lthash_find_draining_block( sched ), FD_SCHED_LTHASH_DRAIN_CHUNK );
    1579           0 : }
    1580             : 
    1581             : /* Returns the lowest lane whose head can be readily demoted, or -1 if
    1582             :    there is none.
    1583             : 
    1584             :    If we ever want to, we could instead pick the lane with the fewest
    1585             :    lane map entries to make demotion cheaper. */
    1586             : static int
    1587           0 : pick_idle_lane( fd_sched_t * sched ) {
    1588           0 :   for( ulong lanes=sched->staged_bitset; lanes; lanes=fd_ulong_pop_lsb( lanes ) ) {
    1589           0 :     int lane_idx = fd_ulong_find_lsb( lanes );
    1590           0 :     if( sched->staged_head_bank_idx[ lane_idx ]==sched->active_bank_idx ) continue;
    1591           0 :     if( lane_is_demotable( sched, lane_idx ) ) return lane_idx;
    1592           0 :   }
    1593           0 :   return -1;
    1594           0 : }
    1595             : 
    1596             : /* Should a FEC set wait for a staging lane?  Recall that our aim is to
    1597             :    maximize replay throughput.  Lane switching is cheap.  So as long as
    1598             :    some lane has work queued up, we'd be able to keep exec tiles busy.
    1599             :    In that case, we shouldn't let the FEC set in if it needs a lane and
    1600             :    we don't have one, because it would just enter as unstaged, and later
    1601             :    down the road we would incur a promotion stall.  Recall that the
    1602             :    scheduler's job is not being the biggest buffer holding all received
    1603             :    transactions.  We're buffering transactions only to uncover the
    1604             :    critical path, and to bridge a bursty FEC producer with multiple exec
    1605             :    tiles consuming tasks at different rates.  So the only reason we'd
    1606             :    ingest a FEC as unstaged is it could potentially unblock other FEC
    1607             :    sets queued behind it.  Some of those held up FEC sets might complete
    1608             :    a staged block and thus free a lane.  However, recall that the chain
    1609             :    rarely forks wide.  Usually, it's one or two forks that may run for a
    1610             :    while.  In that case, with the lanes we have, we'd be able to easily
    1611             :    switch between the few forks and overlap execution on one fork with
    1612             :    ingestion on another fork, without incurring any promotion or
    1613             :    demotion.
    1614             : 
    1615             :    With all that being said, in the common case where the lanes are just
    1616             :    full for holding blocks down one or two forks, there's always replay
    1617             :    progress to be made, and so we ask the FEC to wait if it needs a
    1618             :    lane.  Only when no progress is to be made with any of the lanes we
    1619             :    have, which again happens only under wide forking, do we allow the
    1620             :    FEC in as potentially unstaged.  The FEC would then actually trigger
    1621             :    a demotion upon entering, and take the lane of the demoted block.
    1622             :    Note that this implies demotion really only happens under wide
    1623             :    forking combined with incomplete frontiers.  Also note that we really
    1624             :    do have to demote something in this case, otherwise replay would just
    1625             :    wedge, because the incomplete frontiers might be blocked behind this
    1626             :    upcoming FEC set.
    1627             : 
    1628             :    Finally, what do we consider to be a FEC set that needs a lane?
    1629             :    There are some cases that disqualifies a FEC from being given a lane.
    1630             :    Beyond that, a block's first FEC set needs a lane. */
    1631             : static int
    1632             : fec_waits_for_lane( fd_sched_t * sched,
    1633             :                     ulong        parent_bank_idx,
    1634           0 :                     int          is_first_in_block ) {
    1635           0 :   if( FD_LIKELY( !is_first_in_block ) ) return 0;
    1636           0 :   if( FD_LIKELY( sched->staged_bitset!=fd_ulong_mask_lsb( FD_SCHED_MAX_STAGING_LANES ) ) ) return 0;
    1637             :   /* A ULONG_MAX parent index is only used for backfill, where at the
    1638             :      time of capacity check, the target ancestor is not known yet.  So
    1639             :      we just assume we'll need a lane in that case.  Any other parent
    1640             :      index should be valid. */
    1641           0 :   if( FD_LIKELY( parent_bank_idx<sched->block_cnt_max ) ) {
    1642           0 :     fd_sched_block_t * parent = block_pool_ele( sched, parent_bank_idx );
    1643           0 :     FD_TEST( parent->in_sched );
    1644             :     /* A child of a dying block is dead on arrival and doesn't need a
    1645             :        lane. */
    1646           0 :     if( FD_UNLIKELY( parent->dying ) ) return 0;
    1647             :     /* A child of an unstaged stageable block enters unstaged. */
    1648           0 :     if( !parent->staged && block_is_stageable( parent ) ) return 0;
    1649           0 :   }
    1650           0 :   for( ulong lanes=sched->staged_bitset; lanes; lanes=fd_ulong_pop_lsb( lanes ) ) {
    1651           0 :     fd_sched_block_t * head = block_pool_ele( sched, sched->staged_head_bank_idx[ fd_ulong_find_lsb( lanes ) ] );
    1652           0 :     if( block_is_dispatchable( head ) || block_is_in_flight( head ) ) return 1;
    1653           0 :   }
    1654           0 :   return 0;
    1655           0 : }
    1656             : 
    1657             : /* Returns the lowest free lane.  Assumes at least one lane is free. */
    1658             : static int
    1659        1001 : pick_free_lane( fd_sched_t * sched ) {
    1660        1001 :   ulong free_lanes = (~sched->staged_bitset)&fd_ulong_mask_lsb( FD_SCHED_MAX_STAGING_LANES );
    1661        1001 :   FD_TEST( free_lanes );
    1662        1001 :   return fd_ulong_find_lsb( free_lanes );
    1663        1001 : }
    1664             : 
    1665             : /* The block owes the addition of an account, a write that rdisp did not
    1666             :    see through a transaction at parse time and was just told of through
    1667             :    fd_rdisp_add_writable(), whose return value is rv.  The caller has
    1668             :    brought the account's entry up to date.  See to it that the addition
    1669             :    gets created whether or not the block has drained. */
    1670             : static void
    1671             : lthash_owe_addition( fd_sched_t *       sched,
    1672             :                      fd_sched_block_t * block,
    1673           0 :                      int                rv ) {
    1674           0 :   ulong bank_idx = block_to_idx( sched, block );
    1675           0 :   if( FD_LIKELY( rv==1 ) ) {
    1676           0 :     sched->metrics->lthash_first_write_cnt++;
    1677           0 :     block->add_owed_cnt++;
    1678           0 :     if( block->lthash_drained ) {
    1679             :       /* Re-drain.  We might not get another chance to create a pseudo
    1680             :          transaction for the account, if this were for example a late
    1681             :          ALT expansion for an already fec_eos block.  A block still
    1682             :          draining pops the account with the rest. */
    1683           0 :       block->lthash_drained = 0;
    1684           0 :       lthash_drain_later( sched, block );
    1685           0 :     }
    1686           0 :     return;
    1687           0 :   }
    1688           0 :   if( FD_LIKELY( rv==0 ) ) {
    1689             :     /* We got lucky.  This account is already tracked by rdisp, which
    1690             :        essentially deduped the addition for us.  We can later instruct
    1691             :        rdisp to create a pseudo transaction for the account, and it'll
    1692             :        capture the final value of the account. */
    1693           0 :     sched->metrics->lthash_coalesced_cnt++;
    1694           0 :     return;
    1695           0 :   }
    1696             : 
    1697             :   /* A staged block is alone on its lane and an unstaged block is insert
    1698             :      ready as well, so the dispatcher should never refuse a write. */
    1699           0 :   FD_LOG_CRIT(( "fd_rdisp_add_writable returned %d for block %lu:%lu", rv, block->slot, bank_idx ));
    1700           0 : }
    1701             : 
    1702             : /* For registering account writes that rdisp did not see through a
    1703             :    transaction at parse time.  Typically, that would come from
    1704             :    start-of-block processing or an ALT expansion failure. */
    1705             : static void
    1706             : lthash_register( fd_sched_t *           sched,
    1707             :                  fd_sched_block_t *     block,
    1708           0 :                  fd_acct_addr_t const * addr ) {
    1709           0 :   FD_TEST( block_is_lane_head( sched, block ) );
    1710           0 :   int rv = fd_rdisp_add_writable( sched->rdisp, block_to_idx( sched, block ), addr );
    1711           0 :   if( FD_LIKELY( rv==1 ) ) lthash_entry_new_write_observed( sched, block, addr );
    1712           0 :   lthash_owe_addition( sched, block, rv );
    1713           0 : }
    1714             : 
    1715             : static void
    1716      129026 : lthash_block_init( fd_sched_block_t * block ) {
    1717      129026 :   block->lthash_writable_cnt  = 0U;
    1718      129026 :   block->lthash_in_flight_cnt = 0U;
    1719      129026 :   block->add_owed_cnt         = 0U;
    1720      129026 :   block->sub_q_head           = UINT_MAX;
    1721      129026 :   block->sub_q_tail           = UINT_MAX;
    1722      129026 :   block->sub_queued_cnt       = 0U;
    1723      129026 :   block->sub_in_flight_cnt    = 0U;
    1724      129026 :   block->sub_applied_cnt      = 0U;
    1725      129026 :   block->add_created_cnt      = 0U;
    1726      129026 :   block->add_surfaced_cnt     = 0U;
    1727      129026 :   block->add_done_cnt         = 0U;
    1728      129026 :   block->add_applied_cnt      = 0U;
    1729      129026 :   block->add_undone_cnt       = 0U;
    1730      129026 :   block->lthash_drained       = 0;
    1731      129026 :   block->lthash_draining      = 0;
    1732      129026 : }
    1733             : 
    1734             : /* Forgets the block's LtHash progress when it leaves the pipeline:
    1735             :    finished, abandoned, or demoted.  A demoted block re-derives
    1736             :    everything if promoted.  An abandoned block may still be draining.
    1737             :    The dispatcher forgets what is left of its set when the lane empties.
    1738             :    The delta is found through the block's reservation, so this needs to
    1739             :    come before the reservation is released. */
    1740             : static void
    1741             : lthash_block_reset( fd_sched_t *       sched,
    1742           2 :                     fd_sched_block_t * block ) {
    1743           2 :   sched->lthash_draining_cnt -= (uint)block->lthash_draining;
    1744           2 :   int owns = block_owns_lane_map( sched, block );
    1745           2 :   lthash_block_init( block );
    1746           2 :   if( FD_LIKELY( owns ) ) lthash_release_lane_map( sched, block->staging_lane );
    1747           2 :   if( block->lthash_mode_picked && !block->lthash_inband ) fd_lthash_zero( sched->lthash_delta+block->lthash_oob_reservation );
    1748           2 : }
    1749             : 
    1750             : static void
    1751           0 : lthash_metrics_check( fd_sched_t * sched ) {
    1752           0 :   fd_sched_metrics_t const * m = sched->metrics;
    1753           0 :   ulong add_owed_cnt = 0UL, add_pending_cnt = 0UL, add_delta_cnt = 0UL, sub_queued_cnt = 0UL, sub_in_flight_cnt = 0UL, sub_delta_cnt = 0UL, task_in_flight_cnt = 0UL;
    1754           0 :   for( ulong i=0UL; i<sched->block_cnt_max; i++ ) {
    1755           0 :     fd_sched_block_t const * block = sched->block_pool+i;
    1756           0 :     if( !block->in_sched ) continue;
    1757           0 :     add_owed_cnt       += block->add_owed_cnt;
    1758           0 :     add_pending_cnt    += block->add_created_cnt-block->add_surfaced_cnt;
    1759           0 :     add_delta_cnt      += block->add_applied_cnt-block->add_undone_cnt;
    1760           0 :     sub_queued_cnt     += block->sub_queued_cnt;
    1761           0 :     sub_in_flight_cnt  += block->sub_in_flight_cnt;
    1762           0 :     sub_delta_cnt      += block->sub_applied_cnt;
    1763           0 :     task_in_flight_cnt += block->lthash_in_flight_cnt;
    1764           0 :   }
    1765           0 :   ulong add_on_tile_cnt = 0UL, sub_on_tile_cnt = 0UL, task_on_tile_cnt = 0UL;
    1766           0 :   for( ulong busy = (~sched->lthash_ready_bitset[ 0 ])&fd_ulong_mask_lsb( (int)sched->exec_cnt ); busy; busy = fd_ulong_pop_lsb( busy ) ) {
    1767           0 :     fd_sched_lthash_batch_t const * rec = sched->lthash_inflight+fd_ulong_find_lsb( busy );
    1768           0 :     if( rec->kind==FD_SCHED_TT_LTHASH_ADD ) add_on_tile_cnt += rec->cnt;
    1769           0 :     else                                    sub_on_tile_cnt += rec->cnt;
    1770           0 :     task_on_tile_cnt++;
    1771           0 :   }
    1772           0 :   ulong add_held_cnt = sched->lthash_add_batch->cnt;
    1773           0 :   FD_TEST( task_in_flight_cnt==task_on_tile_cnt );
    1774           0 :   FD_TEST( sub_in_flight_cnt==sub_on_tile_cnt );
    1775           0 :   FD_TEST( m->lthash_parsed_writable_cnt+m->lthash_start_reg_cnt+m->lthash_late_reg_cnt+m->lthash_demote_reg_cnt+m->lthash_spec_given_up_cnt==m->lthash_first_write_cnt+m->lthash_coalesced_cnt );
    1776           0 :   FD_TEST( m->lthash_first_write_cnt==m->lthash_add_created_cnt+add_owed_cnt+m->lthash_discarded_add_owed_cnt+m->lthash_inband_add_owed_cnt );
    1777           0 :   FD_TEST( m->lthash_add_created_cnt==m->lthash_add_surfaced_cnt+add_pending_cnt+m->lthash_discarded_add_pending_cnt );
    1778           0 :   FD_TEST( m->lthash_add_surfaced_cnt==m->lthash_add_surfaced_stale_cnt+m->lthash_spec_given_up_cnt+m->lthash_add_dispatched_cnt+add_held_cnt+m->lthash_discarded_add_held_cnt );
    1779           0 :   FD_TEST( m->lthash_add_dispatched_cnt==m->lthash_add_dispatched_stale_cnt+m->lthash_add_applied_cnt+add_on_tile_cnt );
    1780           0 :   FD_TEST( m->lthash_add_applied_cnt-m->lthash_add_undone_cnt==m->lthash_frozen_cnt+add_delta_cnt+m->lthash_discarded_add_applied_cnt );
    1781           0 :   FD_TEST( m->lthash_entry_cnt==m->lthash_sub_dispatched_cnt+sub_queued_cnt+m->lthash_discarded_sub_queued_cnt+m->lthash_inband_sub_queued_cnt );
    1782           0 :   FD_TEST( m->lthash_sub_dispatched_cnt==m->lthash_sub_applied_cnt+sub_on_tile_cnt );
    1783           0 :   FD_TEST( m->lthash_sub_applied_cnt==m->lthash_frozen_cnt+sub_delta_cnt+m->lthash_discarded_sub_applied_cnt );
    1784           0 : }
    1785             : 
    1786             : /* Rolls the block's LtHash progress back before it leaves its lane.
    1787             :    Each account goes back into rdisp, to be re-derived with exactly one
    1788             :    subtraction and one addition when we later promote the block.
    1789             :    Rehashing a demoted block's progress is the price of keeping the lane
    1790             :    map per lane. */
    1791             : static void
    1792             : lthash_demote( fd_sched_t *       sched,
    1793           0 :                fd_sched_block_t * block ) {
    1794           0 :   ulong bank_idx = block_to_idx( sched, block );
    1795           0 :   ulong lane     = block->staging_lane;
    1796           0 :   FD_TEST( !block->sub_queued_cnt && !block->sub_in_flight_cnt && !block->lthash_in_flight_cnt && block->add_created_cnt==block->add_done_cnt );
    1797             :   /* A draining block has fec_eos set and has not signaled BLOCK_END,
    1798             :      so block_will_signal_end keeps it dispatchable and it is never
    1799             :      demotable. */
    1800           0 :   FD_TEST( !block->lthash_draining );
    1801             : 
    1802             :   /* Owed additions are respected by rdisp demotion.  In addition, we
    1803             :      need to stick the writable accounts that we are tracking back into
    1804             :      rdisp. */
    1805           0 :   uint add_owed_cnt = block->add_owed_cnt;
    1806           0 :   if( block_owns_lane_map( sched, block ) ) {
    1807           0 :     fd_sched_lthash_lane_t * lane_map = sched->lthash_lane+lane;
    1808           0 :     for( ulong i=0UL; i<lane_map->entry_cnt; i++ ) {
    1809           0 :       fd_sched_lthash_entry_t * e = lane_map->entry_pool+i;
    1810             :       /* A demotable block has nothing queued or in flight, so every
    1811             :          entry should be SUB_DONE with no pending or dispatched
    1812             :          addition. */
    1813           0 :       FD_TEST( e->sub_state==FD_SCHED_LTHASH_SUB_DONE && (e->add_state==FD_SCHED_LTHASH_ADD_NONE || e->add_state==FD_SCHED_LTHASH_ADD_DONE) );
    1814           0 :       int rv = fd_rdisp_add_writable( sched->rdisp, bank_idx, &e->acct );
    1815             :       /* A staged block is alone on its lane, so it is insert ready. */
    1816           0 :       if( FD_UNLIKELY( rv<0 ) ) FD_LOG_CRIT(( "fd_rdisp_add_writable failed for demoted block %lu:%lu", block->slot, bank_idx ));
    1817             :       /* A 0 means rdisp already tracks the account and owes it an
    1818             :          addition.  We don't count that as a coalesce, so
    1819             :          lthash_coalesced_cnt only counts coalescing under normal
    1820             :          operation. */
    1821           0 :       sched->metrics->lthash_demote_reg_cnt  += (uint)(rv==1);
    1822           0 :       sched->metrics->lthash_first_write_cnt += (ulong)(rv==1);
    1823           0 :       add_owed_cnt                           += (uint)(rv==1);
    1824           0 :     }
    1825           0 :   }
    1826             :   /* Of the six discarded counters only the two delta counters below
    1827             :      apply here.  The check above ensures nothing queued and nothing
    1828             :      unsurfaced.  The owed additions are not lost, since rdisp will
    1829             :      carry them through demotion, and they stay in the live counter. */
    1830           0 :   sched->metrics->lthash_discarded_sub_applied_cnt += block->sub_applied_cnt;
    1831           0 :   sched->metrics->lthash_discarded_add_applied_cnt += block->add_applied_cnt-block->add_undone_cnt;
    1832           0 :   lthash_block_reset( sched, block );
    1833           0 :   block->add_owed_cnt = add_owed_cnt;
    1834           0 : }
    1835             : 
    1836             : static inline void
    1837             : lthash_batch_init( fd_sched_block_t *        block,
    1838             :                    fd_sched_lthash_batch_t * batch,
    1839             :                    ulong                     bank_idx,
    1840           0 :                    ulong                     kind ) {
    1841           0 :   batch->bank_idx = bank_idx;
    1842           0 :   batch->cnt      = 0UL;
    1843           0 :   batch->lane     = (uint)block->staging_lane;
    1844           0 :   batch->kind     = (uchar)kind;
    1845           0 : }
    1846             : 
    1847             : static inline void
    1848             : lthash_batch_append( fd_sched_t *              sched,
    1849             :                      fd_sched_lthash_batch_t * batch,
    1850             :                      fd_sched_lthash_entry_t * e,
    1851           0 :                      ulong                     ptxn_idx ) {
    1852           0 :   FD_TEST( batch->cnt<FD_SCHED_LTHASH_BATCH_MAX );
    1853           0 :   ulong i = batch->cnt++;
    1854           0 :   batch->entry_idx[ i ] = (uint)(e-sched->lthash_lane[ batch->lane ].entry_pool);
    1855           0 :   batch->ptxn_idx [ i ] = (uint)ptxn_idx;
    1856           0 :   batch->accts    [ i ] = e->acct;
    1857           0 : }
    1858             : 
    1859             : static void
    1860             : lthash_dispatch( fd_sched_t *                    sched,
    1861             :                  fd_sched_block_t *              block,
    1862             :                  fd_sched_lthash_batch_t const * batch,
    1863             :                  int                             exec_tile_idx,
    1864           0 :                  fd_sched_task_t *               out ) {
    1865           0 :   FD_TEST( batch->cnt && batch->cnt<=FD_SCHED_LTHASH_BATCH_MAX );
    1866           0 :   fd_sched_lthash_batch_t * rec = sched->lthash_inflight+exec_tile_idx;
    1867           0 :   if( rec!=batch ) { /* FIXME optimize for end-of-block additions */
    1868           0 :     rec->bank_idx = batch->bank_idx;
    1869           0 :     rec->cnt      = batch->cnt;
    1870           0 :     rec->lane     = batch->lane;
    1871           0 :     rec->kind     = batch->kind;
    1872           0 :     fd_memcpy( rec->entry_idx, batch->entry_idx, batch->cnt*sizeof(uint)           );
    1873           0 :     fd_memcpy( rec->ptxn_idx,  batch->ptxn_idx,  batch->cnt*sizeof(uint)           );
    1874           0 :     fd_memcpy( rec->accts,     batch->accts,     batch->cnt*sizeof(fd_acct_addr_t) );
    1875           0 :   }
    1876             : 
    1877           0 :   out->task_type        = rec->kind;
    1878           0 :   out->lthash->bank_idx = rec->bank_idx;
    1879           0 :   out->lthash->exec_idx = (ulong)exec_tile_idx;
    1880           0 :   out->lthash->cnt      = rec->cnt;
    1881           0 :   out->lthash->accts    = rec->accts;
    1882             : 
    1883           0 :   sched->lthash_ready_bitset[ 0 ] = fd_ulong_clear_bit( sched->lthash_ready_bitset[ 0 ], exec_tile_idx );
    1884           0 :   sched->tile_to_bank_idx[ exec_tile_idx ] = rec->bank_idx;
    1885           0 :   block->lthash_in_flight_cnt++;
    1886           0 :   if( rec->kind==FD_SCHED_TT_LTHASH_ADD ) {
    1887           0 :     sched->metrics->lthash_add_dispatched_cnt += rec->cnt;
    1888           0 :     sched->metrics->lthash_add_task_cnt++;
    1889           0 :   } else {
    1890           0 :     sched->metrics->lthash_sub_dispatched_cnt += rec->cnt;
    1891           0 :     sched->metrics->lthash_sub_task_cnt++;
    1892           0 :   }
    1893           0 :   exec_busy_check( sched, block );
    1894           0 : }
    1895             : 
    1896             : /* A pseudo transaction surfaced from fd_rdisp_get_next_ready() for the
    1897             :    active block.  If it is still the account's pseudo transaction of
    1898             :    record, mark its entry ADD_DISPATCHED, meaning it has left the
    1899             :    dispatcher whether or not it will be staged for batching, and return
    1900             :    the entry.  Otherwise the account has a later rewrite, so complete
    1901             :    the pseudo transaction without hashing and return NULL. */
    1902             : static fd_sched_lthash_entry_t *
    1903             : lthash_surface( fd_sched_t *       sched,
    1904             :                 fd_sched_block_t * block,
    1905             :                 ulong              bank_idx,
    1906           0 :                 ulong              ptxn_idx ) {
    1907           0 :   block->add_surfaced_cnt++;
    1908           0 :   sched->metrics->lthash_add_surfaced_cnt++;
    1909           0 :   fd_acct_addr_t acct[ 1 ];
    1910           0 :   fd_rdisp_pseudo_txn_to_addr( sched->rdisp, ptxn_idx, acct );
    1911           0 :   fd_sched_lthash_entry_t * e = lthash_lane_query( sched, block->staging_lane, acct );
    1912           0 :   if( FD_UNLIKELY( !e ) ) {
    1913             :     /* Every pseudo transaction of a head block must have an entry. */
    1914           0 :     sched->print_buf_sz = 0UL;
    1915           0 :     print_all( sched, block );
    1916           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
    1917           0 :     FD_LOG_CRIT(( "invariant violation: pseudo transaction 0x%lx of block %lu:%lu has no entry", ptxn_idx, block->slot, bank_idx ));
    1918           0 :   }
    1919           0 :   FD_TEST( e->bank_idx==(uint)bank_idx );
    1920           0 :   if( FD_UNLIKELY( e->ptxn_idx!=(uint)ptxn_idx ) ) {
    1921           0 :     sched->metrics->lthash_add_surfaced_stale_cnt++;
    1922           0 :     block->add_done_cnt++;
    1923           0 :     fd_rdisp_complete_txn( sched->rdisp, ptxn_idx, 1 );
    1924           0 :     return NULL;
    1925           0 :   }
    1926           0 :   FD_TEST( e->add_state==FD_SCHED_LTHASH_ADD_PENDING );
    1927           0 :   e->add_state = (uchar)FD_SCHED_LTHASH_ADD_DISPATCHED;
    1928           0 :   return e;
    1929           0 : }
    1930             : 
    1931             : static void
    1932             : lthash_dispatch_one_add( fd_sched_t *              sched,
    1933             :                          fd_sched_block_t *        block,
    1934             :                          ulong                     bank_idx,
    1935             :                          fd_sched_lthash_entry_t * e,
    1936             :                          ulong                     ptxn_idx,
    1937             :                          int                       exec_tile_idx,
    1938           0 :                          fd_sched_task_t *         out ) {
    1939           0 :   fd_sched_lthash_batch_t * rec = sched->lthash_inflight+exec_tile_idx;
    1940           0 :   lthash_batch_init( block, rec, bank_idx, FD_SCHED_TT_LTHASH_ADD );
    1941           0 :   lthash_batch_append( sched, rec, e, ptxn_idx );
    1942           0 :   lthash_dispatch( sched, block, rec, exec_tile_idx, out );
    1943           0 : }
    1944             : 
    1945             : /* A speculative addition that surfaced when the block may no longer
    1946             :    speculate on it, or when the idleness policy would not let it onto a
    1947             :    tile, is given up.  The surfaced pseudo transaction is completed
    1948             :    without hashing.  Its entry is rewritten, which frees the speculation
    1949             :    entry, and the account goes back to the dispatcher as written, the
    1950             :    way any write does, so the block owes the addition again.  No
    1951             :    transaction actually rewrote the account, so this is neither a stale
    1952             :    addition nor an invalidated speculation for metric purposes. */
    1953             : static void
    1954             : lthash_spec_give_up( fd_sched_t *              sched,
    1955             :                      fd_sched_block_t *        block,
    1956             :                      ulong                     bank_idx,
    1957             :                      fd_sched_lthash_entry_t * e,
    1958           0 :                      ulong                     ptxn_idx ) {
    1959           0 :   sched->metrics->lthash_spec_given_up_cnt++;
    1960             :   /* Complete the pseudo transaction first, so that it is no longer the
    1961             :      account's last reference in rdisp if a re-drain below creates the
    1962             :      new one. */
    1963           0 :   block->add_done_cnt++;
    1964           0 :   fd_rdisp_complete_txn( sched->rdisp, ptxn_idx, 1 );
    1965           0 :   lthash_entry_clear_add( sched, e, block_lthash_delta( sched, block ) );
    1966           0 :   lthash_owe_addition( sched, block, fd_rdisp_add_writable( sched->rdisp, bank_idx, &e->acct ) );
    1967           0 : }
    1968             : 
    1969             : static void
    1970             : lthash_dispatch_held_batch( fd_sched_t *       sched,
    1971             :                             fd_sched_block_t * block,
    1972             :                             ulong              bank_idx,
    1973             :                             int                exec_tile_idx,
    1974           0 :                             fd_sched_task_t *  out ) {
    1975           0 :   fd_sched_lthash_batch_t * batch = sched->lthash_add_batch;
    1976             :   /* The held batch belongs to the active block.  Its surfaced,
    1977             :      unfinished additions keep the block in flight, so it is not
    1978             :      deactivated, demoted or pruned and does not signal BLOCK_END, and
    1979             :      no other block is activated behind the batch.  The one way out is
    1980             :      abandon, which releases the batch where it marks the block dying.
    1981             :      So a held batch is either dispatched or released before its block
    1982             :      goes anywhere. */
    1983           0 :   FD_TEST( batch->cnt && batch->kind==FD_SCHED_TT_LTHASH_ADD && batch->bank_idx==bank_idx && batch->lane==(uint)block->staging_lane );
    1984             : 
    1985           0 :   for( ulong i=0UL; i<batch->cnt; i++ ) {
    1986           0 :     fd_sched_lthash_entry_t * e = sched->lthash_lane[ batch->lane ].entry_pool+batch->entry_idx[ i ];
    1987           0 :     if( FD_UNLIKELY( e->ptxn_idx!=batch->ptxn_idx[ i ] || e->add_state!=FD_SCHED_LTHASH_ADD_DISPATCHED ) ) {
    1988             :       /* A non-speculative addition cannot become stale after it has
    1989             :          surfaced.  See the account state machine.  So no batched pseudo
    1990             :          transaction can actually be invalidated by a rewrite. */
    1991           0 :       sched->print_buf_sz = 0UL;
    1992           0 :       print_all( sched, block );
    1993           0 :       FD_LOG_NOTICE(( "%s", sched->print_buf ));
    1994           0 :       FD_LOG_CRIT(( "invariant violation: held addition %lu of %lu of block %lu:%lu was rewritten, ticket 0x%x, entry 0x%x state %u", i, batch->cnt, block->slot, bank_idx, batch->ptxn_idx[ i ], e->ptxn_idx, (uint)e->add_state ));
    1995           0 :     }
    1996           0 :   }
    1997           0 :   lthash_dispatch( sched, block, batch, exec_tile_idx, out );
    1998           0 :   batch->cnt = 0UL;
    1999           0 : }
    2000             : 
    2001             : static void
    2002             : lthash_release_held_batch( fd_sched_t *       sched,
    2003           2 :                            fd_sched_block_t * block ) {
    2004           2 :   fd_sched_lthash_batch_t * batch = sched->lthash_add_batch;
    2005           2 :   if( FD_LIKELY( !batch->cnt || batch->bank_idx!=block_to_idx( sched, block ) ) ) return;
    2006           0 :   for( ulong i=0UL; i<batch->cnt; i++ ) {
    2007           0 :     fd_sched_lthash_entry_t * e = sched->lthash_lane[ batch->lane ].entry_pool+batch->entry_idx[ i ];
    2008           0 :     if( FD_LIKELY( e->ptxn_idx==batch->ptxn_idx[ i ] ) ) {
    2009           0 :       e->ptxn_idx  = 0U;
    2010           0 :       e->add_state = (uchar)FD_SCHED_LTHASH_ADD_NONE;
    2011           0 :     }
    2012           0 :     block->add_done_cnt++;
    2013           0 :     fd_rdisp_complete_txn( sched->rdisp, (ulong)batch->ptxn_idx[ i ], 1 );
    2014           0 :   }
    2015           0 :   sched->metrics->lthash_discarded_add_held_cnt += (uint)batch->cnt;
    2016           0 :   batch->cnt = 0UL;
    2017           0 : }
    2018             : 
    2019             : /* Dispatches up to cap of the block's oldest queued subtractions as one
    2020             :    task to the highest idle tile in ready_bitset, if more than threshold
    2021             :    tiles are idle.  cap is in [1, FD_SCHED_LTHASH_BATCH_MAX].  Returns 1
    2022             :    if a task was written to out, 0 otherwise. */
    2023             : static int
    2024             : lthash_dispatch_sub( fd_sched_t *       sched,
    2025             :                      fd_sched_block_t * block,
    2026             :                      ulong              bank_idx,
    2027             :                      ulong              ready_bitset,
    2028             :                      int                threshold,
    2029             :                      ulong              cap,
    2030           0 :                      fd_sched_task_t *  out ) {
    2031           0 :   if( FD_UNLIKELY( !block->sub_queued_cnt ) ) return 0;
    2032           0 :   if( FD_UNLIKELY( fd_ulong_popcnt( ready_bitset )<=threshold ) ) return 0;
    2033             : 
    2034           0 :   int   exec_tile_idx = fd_ulong_find_msb( ready_bitset );
    2035           0 :   ulong lane          = block->staging_lane;
    2036           0 :   fd_sched_lthash_batch_t * batch = sched->lthash_inflight+exec_tile_idx;
    2037           0 :   lthash_batch_init( block, batch, bank_idx, FD_SCHED_TT_LTHASH_SUB );
    2038           0 :   while( block->sub_queued_cnt && batch->cnt<cap ) {
    2039           0 :     uint entry_idx = block->sub_q_head;
    2040           0 :     FD_TEST( entry_idx!=UINT_MAX );
    2041           0 :     fd_sched_lthash_entry_t * e = sched->lthash_lane[ lane ].entry_pool+entry_idx;
    2042           0 :     FD_TEST( e->sub_state==FD_SCHED_LTHASH_SUB_QUEUED && e->bank_idx==(uint)bank_idx );
    2043             : 
    2044           0 :     block->sub_q_head = e->q_next;
    2045           0 :     if( FD_UNLIKELY( block->sub_q_head==UINT_MAX ) ) block->sub_q_tail = UINT_MAX;
    2046           0 :     e->q_next    = UINT_MAX;
    2047           0 :     e->sub_state = (uchar)FD_SCHED_LTHASH_SUB_DISPATCHED;
    2048           0 :     block->sub_queued_cnt--;
    2049           0 :     block->sub_in_flight_cnt++;
    2050           0 :     lthash_batch_append( sched, batch, e, 0UL );
    2051           0 :   }
    2052           0 :   lthash_dispatch( sched, block, batch, exec_tile_idx, out );
    2053           0 :   return 1;
    2054           0 : }
    2055             : 
    2056             : /* The block is assumed to be active, and nothing else of it could go
    2057             :    out this poll, either because nothing is queued or because every
    2058             :    queued transaction is blocked by something in-flight.  The block is
    2059             :    also assumed to satisfy block_may_speculate().  The dispatcher hands
    2060             :    us an account whose writers have all completed, so the guess is READY
    2061             :    at once and goes straight to a tile.  The one exception is a
    2062             :    serializing transaction still outstanding, which every pseudo
    2063             :    transaction created after it waits behind.  Speculation is paused for
    2064             :    a moment until that serializing transaction completes.
    2065             : 
    2066             :    Returns 1 with a task in out, or 0 when a pseudo transaction was
    2067             :    created but waits on a serializing transaction, so the caller returns
    2068             :    without a task, or -1 when speculation does not apply and the caller
    2069             :    proceeds. */
    2070             : static inline int
    2071             : lthash_speculate( fd_sched_t *       sched,
    2072             :                   fd_sched_block_t * block,
    2073             :                   ulong              bank_idx,
    2074             :                   ulong              ready_bitset,
    2075           0 :                   fd_sched_task_t *  out ) {
    2076             :   /* Any unsurfaced pseudo transaction is a speculation, since with
    2077             :      fec_eos clear there has been no drain. */
    2078           0 :   uint spec_pending = block->add_created_cnt-block->add_surfaced_cnt;
    2079           0 :   if( spec_pending ) {
    2080             :     /* We speculate one addition at a time. */
    2081           0 :     FD_TEST( spec_pending==1U );
    2082           0 :     return -1;
    2083           0 :   }
    2084             : 
    2085           0 :   if( fd_ulong_popcnt( ready_bitset )<=idle_reserve( sched, block ) ) return -1;
    2086             : 
    2087           0 :   ulong ptxn_idx = fd_rdisp_add_ready_pseudo_txn( sched->rdisp, bank_idx );
    2088           0 :   if( FD_UNLIKELY( !ptxn_idx ) ) FD_LOG_CRIT(( "invariant violation: block %lu:%lu owes %u additions and has a ready account but fd_rdisp_add_ready_pseudo_txn returned none", block->slot, bank_idx, block->add_owed_cnt ));
    2089           0 :   sched->metrics->lthash_spec_cnt++;
    2090           0 :   block->add_owed_cnt--;
    2091           0 :   lthash_add_created( sched, block, ptxn_idx, 1 );
    2092             : 
    2093             :   /* We expect to get either the only pseudo transaction, the one we
    2094             :      just instructed rdisp to create, or nothing because it is blocked
    2095             :      by a serializing transaction.  There should otherwise be no task
    2096             :      queued, especially no other pseudo transaction queued. */
    2097           0 :   ulong idx = fd_rdisp_get_next_ready( sched->rdisp, bank_idx );
    2098           0 :   if( FD_LIKELY( idx==ptxn_idx ) ) {
    2099           0 :     fd_sched_lthash_entry_t * e = lthash_surface( sched, block, bank_idx, idx );
    2100           0 :     FD_TEST( e );
    2101           0 :     lthash_dispatch_one_add( sched, block, bank_idx, e, idx, fd_ulong_find_msb( ready_bitset ), out );
    2102           0 :     return 1;
    2103           0 :   }
    2104           0 :   if( FD_UNLIKELY( idx ) ) FD_LOG_CRIT(( "invariant violation: fd_rdisp_get_next_ready returned 0x%lx instead of pseudo transaction 0x%lx of block %lu:%lu", idx, ptxn_idx, block->slot, bank_idx ));
    2105             : 
    2106             :   /* Pseudo transaction pending behind a serializing transaction.  It'll
    2107             :      be eligible for surfacing once that transaction completes. */
    2108           0 :   return 0;
    2109           0 : }
    2110             : 
    2111             : 
    2112             : /* Public functions. */
    2113             : 
    2114             : ulong
    2115       12023 : fd_sched_align( void ) {
    2116       12023 :   return fd_ulong_max( alignof(fd_sched_t),
    2117       12023 :          fd_ulong_max( fd_rdisp_align(),
    2118       12023 :          fd_ulong_max( alignof(fd_sched_block_t), 64UL ))); /* Minimally cache line aligned. */
    2119       12023 : }
    2120             : 
    2121             : static inline ulong
    2122        4021 : sched_lthash_batch_sz( void ) {
    2123             :   /* One batch per exec tile, plus one staging area for the addition
    2124             :      batch being assembled or held. */
    2125        4021 :   return (FD_SCHED_MAX_EXEC_TILE_CNT+1UL)*sizeof(fd_sched_lthash_batch_t);
    2126        4021 : }
    2127             : 
    2128             : ulong
    2129             : fd_sched_footprint( ulong depth,
    2130             :                     ulong block_cnt_max,
    2131             :                     ulong max_shreds_per_block,
    2132        1005 :                     ulong max_txn_per_slot ) {
    2133        1005 :   if( FD_UNLIKELY( depth<FD_SCHED_MIN_DEPTH || depth>FD_SCHED_MAX_DEPTH ) ) return 0UL; /* bad depth */
    2134        1005 :   if( FD_UNLIKELY( !block_cnt_max ) ) return 0UL; /* bad block_cnt_max */
    2135        1005 :   if( FD_UNLIKELY( depth>UINT_MAX-1UL ) ) return 0UL; /* mblk_pool use uint as pointers */
    2136        1005 :   if( FD_UNLIKELY( !max_shreds_per_block || max_shreds_per_block>UINT_MAX ) ) return 0UL; /* shred_cnt is uint */
    2137        1005 :   if( FD_UNLIKELY( !max_txn_per_slot     || max_txn_per_slot    >UINT_MAX ) ) return 0UL; /* txn_parsed_cnt is uint */
    2138             : 
    2139        1005 :   ulong rdisp_footprint = fd_rdisp_footprint( depth, block_cnt_max, FD_SCHED_LTHASH_ACCT_EXTRA );
    2140        1005 :   if( FD_UNLIKELY( !rdisp_footprint ) ) return 0UL;
    2141             : 
    2142        1005 :   ulong l = FD_LAYOUT_INIT;
    2143        1005 :   l = FD_LAYOUT_APPEND( l, fd_sched_align(),             sizeof(fd_sched_t)                                 );
    2144        1005 :   l = FD_LAYOUT_APPEND( l, fd_rdisp_align(),             rdisp_footprint                                    ); /* dispatcher */
    2145        1005 :   l = FD_LAYOUT_APPEND( l, alignof(fd_sched_block_t),    block_cnt_max*sizeof(fd_sched_block_t)             ); /* block pool */
    2146        1005 :   l = FD_LAYOUT_APPEND( l, ref_q_align(),                ref_q_footprint( block_cnt_max )                   );
    2147        1005 :   l = FD_LAYOUT_APPEND( l, alignof(fd_txn_p_t),          depth*sizeof(fd_txn_p_t)                           ); /* txn_pool */
    2148        1005 :   l = FD_LAYOUT_APPEND( l, alignof(fd_sched_txn_info_t), depth*sizeof(fd_sched_txn_info_t)                  ); /* txn_info_pool */
    2149        1005 :   l = FD_LAYOUT_APPEND( l, alignof(fd_sched_mblk_t),     depth*sizeof(fd_sched_mblk_t)                      ); /* mblk_pool */
    2150        5017 :   for( ulong i=0UL; i<FD_SCHED_MAX_STAGING_LANES; i++ ) {
    2151        4012 :     l = FD_LAYOUT_APPEND( l, alignof(fd_sched_lthash_entry_t), FD_RDISP_MAX_WRITERS_PER_BLOCK*sizeof(fd_sched_lthash_entry_t) );
    2152        4012 :     l = FD_LAYOUT_APPEND( l, lthash_map_align(),               lthash_map_footprint( lthash_chain_cnt() )                     );
    2153        4012 :   }
    2154        1005 :   l = FD_LAYOUT_APPEND( l, alignof(fd_lthash_value_t),   FD_SCHED_LTHASH_SPEC_MAX*sizeof(fd_lthash_value_t) ); /* lthash_spec_pool */
    2155        1005 :   l = FD_LAYOUT_APPEND( l, alignof(uint),                FD_SCHED_LTHASH_SPEC_MAX*sizeof(uint)              ); /* lthash_spec_free */
    2156        1005 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),               sched_lthash_batch_sz()                            ); /* lthash_inflight, lthash_add_batch */
    2157        1005 :   return FD_LAYOUT_FINI( l, fd_sched_align() );
    2158        1005 : }
    2159             : 
    2160             : void *
    2161             : fd_sched_new( void *     mem,
    2162             :               fd_rng_t * rng,
    2163             :               ulong      depth,
    2164             :               ulong      block_cnt_max,
    2165             :               ulong      max_shreds_per_block,
    2166             :               ulong      max_txn_per_slot,
    2167        1000 :               ulong      exec_cnt ) {
    2168             : 
    2169        1000 :   if( FD_UNLIKELY( !mem ) ) {
    2170           0 :     FD_LOG_WARNING(( "NULL mem" ));
    2171           0 :     return NULL;
    2172           0 :   }
    2173             : 
    2174        1000 :   if( FD_UNLIKELY( !rng ) ) {
    2175           0 :     FD_LOG_WARNING(( "NULL rng" ));
    2176           0 :     return NULL;
    2177           0 :   }
    2178             : 
    2179        1000 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_sched_align() ) ) ) {
    2180           0 :     FD_LOG_WARNING(( "misaligned mem (%p)", mem ));
    2181           0 :     return NULL;
    2182           0 :   }
    2183             : 
    2184        1000 :   if( FD_UNLIKELY( depth<FD_SCHED_MIN_DEPTH || depth>FD_SCHED_MAX_DEPTH ) ) {
    2185           0 :     FD_LOG_WARNING(( "bad depth (%lu)", depth ));
    2186           0 :     return NULL;
    2187           0 :   }
    2188             : 
    2189        1000 :   if( FD_UNLIKELY( !block_cnt_max ) ) {
    2190           0 :     FD_LOG_WARNING(( "bad block_cnt_max (%lu)", block_cnt_max ));
    2191           0 :     return NULL;
    2192           0 :   }
    2193             : 
    2194        1000 :   if( FD_UNLIKELY( depth>UINT_MAX-1UL ) ) {
    2195           0 :     FD_LOG_WARNING(( "bad depth (%lu)", depth ));
    2196           0 :     return NULL;
    2197           0 :   }
    2198             : 
    2199        1000 :   if( FD_UNLIKELY( !max_shreds_per_block || max_shreds_per_block>UINT_MAX ) ) {
    2200           0 :     FD_LOG_WARNING(( "bad max_shreds_per_block (%lu)", max_shreds_per_block ));
    2201           0 :     return NULL;
    2202           0 :   }
    2203             : 
    2204        1000 :   if( FD_UNLIKELY( !max_txn_per_slot || max_txn_per_slot>UINT_MAX ) ) {
    2205           0 :     FD_LOG_WARNING(( "bad max_txn_per_slot (%lu)", max_txn_per_slot ));
    2206           0 :     return NULL;
    2207           0 :   }
    2208             : 
    2209        1000 :   if( FD_UNLIKELY( !exec_cnt || exec_cnt>FD_SCHED_MAX_EXEC_TILE_CNT ) ) {
    2210           0 :     FD_LOG_WARNING(( "bad exec_cnt (%lu)", exec_cnt ));
    2211           0 :     return NULL;
    2212           0 :   }
    2213             : 
    2214        1000 :   FD_SCRATCH_ALLOC_INIT( l, mem );
    2215        1000 :   fd_sched_t *          sched          = FD_SCRATCH_ALLOC_APPEND( l, fd_sched_align(),             sizeof(fd_sched_t)                                 );
    2216        1000 :   void *                _rdisp         = FD_SCRATCH_ALLOC_APPEND( l, fd_rdisp_align(),             fd_rdisp_footprint( depth, block_cnt_max, FD_SCHED_LTHASH_ACCT_EXTRA ) );
    2217        1000 :   void *                _bpool         = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sched_block_t),    block_cnt_max*sizeof(fd_sched_block_t)             );
    2218        1000 :   void *                _ref_q         = FD_SCRATCH_ALLOC_APPEND( l, ref_q_align(),                ref_q_footprint( block_cnt_max )                   );
    2219        1000 :   fd_txn_p_t *          _txn_pool      = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txn_p_t),          depth*sizeof(fd_txn_p_t)                           );
    2220        1000 :   fd_sched_txn_info_t * _txn_info_pool = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sched_txn_info_t), depth*sizeof(fd_sched_txn_info_t)                  );
    2221        1000 :   fd_sched_mblk_t *     _mblk_pool     = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sched_mblk_t),     depth*sizeof(fd_sched_mblk_t)                      );
    2222        5015 :   for( ulong i=0UL; i<FD_SCHED_MAX_STAGING_LANES; i++ ) {
    2223        4015 :     /* */                                FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sched_lthash_entry_t), FD_RDISP_MAX_WRITERS_PER_BLOCK*sizeof(fd_sched_lthash_entry_t) );
    2224        4015 :     void *              _lane_map      = FD_SCRATCH_ALLOC_APPEND( l, lthash_map_align(),               lthash_map_footprint( lthash_chain_cnt() )                     );
    2225        4015 :     FD_TEST( lthash_map_new( _lane_map, lthash_chain_cnt(), fd_rng_ulong( rng ) ) );
    2226        4015 :     sched->lthash_lane[ i ].entry_cnt = 0U;
    2227        4015 :     sched->lthash_lane[ i ].bank_idx  = ULONG_MAX;
    2228        4015 :   }
    2229        2000 :   /* */                                  FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_lthash_value_t),   FD_SCHED_LTHASH_SPEC_MAX*sizeof(fd_lthash_value_t) );
    2230        1000 :   uint *                _spec_free     = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),                FD_SCHED_LTHASH_SPEC_MAX*sizeof(uint)              );
    2231        1000 :   void *                _lthash_batch  = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong),               sched_lthash_batch_sz()                            );
    2232        1000 :   FD_SCRATCH_ALLOC_FINI( l, fd_sched_align() );
    2233             : 
    2234        1000 :   sched->txn_pool      = _txn_pool;
    2235        1000 :   sched->txn_info_pool = _txn_info_pool;
    2236        1000 :   sched->mblk_pool     = _mblk_pool;
    2237             : 
    2238        1000 :   FD_TEST( fd_rdisp_new( _rdisp, depth, block_cnt_max, FD_SCHED_LTHASH_ACCT_EXTRA, fd_rng_ulong( rng ) ) );
    2239             : 
    2240             :   /* Acquire is LIFO, so this hands out speculation entries 0, 1, 2, ... */
    2241     8258536 :   for( ulong i=0UL; i<FD_SCHED_LTHASH_SPEC_MAX; i++ ) _spec_free[ i ] = (uint)(FD_SCHED_LTHASH_SPEC_MAX-1UL-i);
    2242        1000 :   sched->lthash_spec_free_cnt = FD_SCHED_LTHASH_SPEC_MAX;
    2243        1000 :   fd_memset( sched->lthash_delta, 0, sizeof(sched->lthash_delta) );
    2244        1000 :   fd_memset( _lthash_batch, 0, sched_lthash_batch_sz() );
    2245             : 
    2246        1000 :   fd_sched_block_t * bpool = (fd_sched_block_t *)_bpool;
    2247      130024 :   for( ulong i=0; i<block_cnt_max; i++ ) {
    2248      129024 :     bpool[ i ].in_sched = 0;
    2249      129024 :     lthash_block_init( bpool+i );
    2250      129024 :     mblk_slist_new( bpool[ i ].mblks_unhashed );
    2251      129024 :     mblk_slist_new( bpool[ i ].mblks_mixin_in_progress );
    2252      129024 :   }
    2253             : 
    2254        1000 :   FD_TEST( fd_chkdup_new( sched->chkdup, rng ) );
    2255             : 
    2256        1000 :   fd_memset( sched->metrics, 0, sizeof(fd_sched_metrics_t) );
    2257        1000 :   sched->txn_in_flight_last_tick  = fd_tickcount(); /* boot counts as idle */
    2258        1000 :   sched->next_ready_last_tick     = LONG_MAX;
    2259        1000 :   sched->next_ready_last_bank_idx = ULONG_MAX;
    2260        1000 :   sched->state_debounce_ticks     = (long)( (double)FD_SCHED_SLOT_STATE_DEBOUNCE_NS*fd_tempo_tick_per_ns( NULL ) );
    2261        1000 :   sched->state_bank_idx           = ULONG_MAX;
    2262        1000 :   sched->state_cnt                = 0UL;
    2263        1000 :   sched->state_drop_cnt           = 0UL;
    2264             : 
    2265        1000 :   sched->canary                 = FD_SCHED_MAGIC;
    2266        1000 :   sched->depth                  = depth;
    2267        1000 :   sched->lthash_draining_cnt    = 0U;
    2268        1000 :   sched->block_cnt_max          = block_cnt_max;
    2269        1000 :   sched->max_shreds_per_block   = max_shreds_per_block;
    2270        1000 :   sched->max_txn_per_slot       = max_txn_per_slot;
    2271        1000 :   sched->max_mblk_per_slot      = fd_ulong_min( FD_SCHED_MAX_MBLK_PER_SLOT*max_shreds_per_block/FD_SHRED_BLK_MAX, UINT_MAX ); /* mblk_cnt is uint */
    2272        1000 :   sched->exec_cnt               = exec_cnt;
    2273        1000 :   sched->bypass_poh_verify      = 0;
    2274        1000 :   sched->bypass_alut_resolution = 0;
    2275        1000 :   sched->lthash_txn_writers_max = FD_SCHED_LTHASH_TXN_WRITERS_MAX;
    2276        1000 :   sched->lthash_oob_reserved    = 0UL;
    2277        1000 :   sched->lthash_oob_block_max   = FD_SCHED_LTHASH_OOB_BLOCK_MAX;
    2278        1000 :   sched->root_idx               = ULONG_MAX;
    2279        1000 :   sched->active_bank_idx        = ULONG_MAX;
    2280        1000 :   sched->last_active_bank_idx   = ULONG_MAX;
    2281        1000 :   sched->staged_bitset          = 0UL;
    2282        5032 :   for( ulong l=0UL; l<FD_SCHED_MAX_STAGING_LANES; l++ ) sched->staged_head_bank_idx[ l ] = ULONG_MAX;
    2283        1000 :   sched->staged_popcnt_wmk      = 0UL;
    2284             : 
    2285        1000 :   sched->txn_exec_ready_bitset[ 0 ]  = fd_ulong_mask_lsb( (int)exec_cnt );
    2286        1000 :   sched->sigverify_ready_bitset[ 0 ] = fd_ulong_mask_lsb( (int)exec_cnt );
    2287        1000 :   sched->poh_ready_bitset[ 0 ]       = fd_ulong_mask_lsb( (int)exec_cnt );
    2288        1000 :   sched->footer_ready_bitset[ 0 ]    = fd_ulong_mask_lsb( (int)exec_cnt );
    2289        1000 :   sched->lthash_ready_bitset[ 0 ]    = fd_ulong_mask_lsb( (int)exec_cnt );
    2290             : 
    2291        1000 :   sched->txn_pool_free_cnt = depth-1UL; /* -1 because index 0 is unusable as a sentinel reserved by the dispatcher */
    2292             : 
    2293     4128760 :   for( ulong i=0UL; i<depth-1UL; i++ ) sched->mblk_pool[ i ].next = (uint)(i+1UL);
    2294        1000 :   sched->mblk_pool[ depth-1UL ].next = UINT_MAX;
    2295        1000 :   sched->mblk_pool_free_head = 0U;
    2296        1000 :   sched->mblk_pool_free_cnt  = depth;
    2297             : 
    2298        1000 :   txn_bitset_new( sched->exec_done_set );
    2299        1000 :   txn_bitset_new( sched->sigverify_done_set );
    2300        1000 :   txn_bitset_new( sched->poh_mixin_done_set );
    2301             : 
    2302        1000 :   sched->block_pool_popcnt = 0UL;
    2303             : 
    2304        1000 :   ref_q_new( _ref_q, block_cnt_max );
    2305             : 
    2306        1000 :   return sched;
    2307        1000 : }
    2308             : 
    2309             : fd_sched_t *
    2310        1008 : fd_sched_join( void * mem ) {
    2311             : 
    2312        1008 :   if( FD_UNLIKELY( !mem ) ) {
    2313           0 :     FD_LOG_WARNING(( "NULL mem" ));
    2314           0 :     return NULL;
    2315           0 :   }
    2316             : 
    2317        1008 :   fd_sched_t * sched         = (fd_sched_t *)mem;
    2318        1008 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    2319        1008 :   ulong        depth                = sched->depth;
    2320        1008 :   ulong        block_cnt_max        = sched->block_cnt_max;
    2321             : 
    2322        1008 :   FD_SCRATCH_ALLOC_INIT( l, mem );
    2323        1008 :   /*                        */ FD_SCRATCH_ALLOC_APPEND( l, fd_sched_align(),             sizeof(fd_sched_t)                                 );
    2324        1008 :   void *           _rdisp    = FD_SCRATCH_ALLOC_APPEND( l, fd_rdisp_align(),             fd_rdisp_footprint( depth, block_cnt_max, FD_SCHED_LTHASH_ACCT_EXTRA ) );
    2325        1008 :   void *           _bpool    = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sched_block_t),    block_cnt_max*sizeof(fd_sched_block_t)             );
    2326        1008 :   void *           _ref_q    = FD_SCRATCH_ALLOC_APPEND( l, ref_q_align(),                ref_q_footprint( block_cnt_max )                   );
    2327        1008 :   /*                        */ FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txn_p_t),          depth*sizeof(fd_txn_p_t)                           );
    2328        1008 :   /*                        */ FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sched_txn_info_t), depth*sizeof(fd_sched_txn_info_t)                  );
    2329        1008 :   /*                        */ FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sched_mblk_t),     depth*sizeof(fd_sched_mblk_t)                      );
    2330        5040 :   for( ulong i=0UL; i<FD_SCHED_MAX_STAGING_LANES; i++ ) {
    2331        4032 :     void *         _pool     = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sched_lthash_entry_t), FD_RDISP_MAX_WRITERS_PER_BLOCK*sizeof(fd_sched_lthash_entry_t) );
    2332        4032 :     void *         _map      = FD_SCRATCH_ALLOC_APPEND( l, lthash_map_align(),               lthash_map_footprint( lthash_chain_cnt() )                     );
    2333           0 :     sched->lthash_lane[ i ].entry_pool = (fd_sched_lthash_entry_t *)_pool;
    2334        4032 :     sched->lthash_lane[ i ].map        = lthash_map_join( _map );
    2335        4032 :     FD_TEST( sched->lthash_lane[ i ].map );
    2336        4032 :   }
    2337        1008 :   sched->lthash_spec_pool    = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_lthash_value_t),   FD_SCHED_LTHASH_SPEC_MAX*sizeof(fd_lthash_value_t) );
    2338        1008 :   sched->lthash_spec_free    = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),                FD_SCHED_LTHASH_SPEC_MAX*sizeof(uint)              );
    2339        1008 :   void *           _batch    = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong),               sched_lthash_batch_sz()                            );
    2340        1008 :   FD_SCRATCH_ALLOC_FINI( l, fd_sched_align() );
    2341             : 
    2342        1008 :   sched->rdisp      = fd_rdisp_join( _rdisp );
    2343        1008 :   sched->ref_q      = ref_q_join( _ref_q );
    2344        1008 :   sched->block_pool = _bpool;
    2345        1008 :   sched->lthash_inflight  = (fd_sched_lthash_batch_t *)_batch;
    2346        1008 :   sched->lthash_add_batch = sched->lthash_inflight+FD_SCHED_MAX_EXEC_TILE_CNT;
    2347             : 
    2348      130032 :   for( ulong i=0; i<block_cnt_max; i++ ) {
    2349      129024 :     mblk_slist_join( sched->block_pool[ i ].mblks_unhashed );
    2350      129024 :     mblk_slist_join( sched->block_pool[ i ].mblks_mixin_in_progress );
    2351      129024 :   }
    2352             : 
    2353        1008 :   txn_bitset_join( sched->exec_done_set );
    2354        1008 :   txn_bitset_join( sched->sigverify_done_set );
    2355        1008 :   txn_bitset_join( sched->poh_mixin_done_set );
    2356             : 
    2357        1008 :   return sched;
    2358        1008 : }
    2359             : 
    2360             : int
    2361        4557 : fd_sched_fec_can_ingest( fd_sched_t * sched, fd_sched_fec_t * fec ) {
    2362        4557 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    2363        4557 :   FD_TEST( fec->bank_idx<sched->block_cnt_max );
    2364        4557 :   FD_TEST( fec->parent_bank_idx<sched->block_cnt_max );
    2365             : 
    2366        4557 :   if( FD_UNLIKELY( fec->fec->data_sz>FD_SCHED_MAX_PAYLOAD_PER_FEC ) ) {
    2367           0 :     sched->print_buf_sz = 0UL;
    2368           0 :     print_metrics( sched );
    2369           0 :     print_sched( sched );
    2370           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
    2371           0 :     FD_LOG_CRIT(( "invalid FEC set: fec->data_sz %u, slot %lu, parent slot %lu", fec->fec->data_sz, fec->slot, fec->parent_slot ));
    2372           0 :   }
    2373             : 
    2374        4557 :   ulong fec_buf_sz = 0UL;
    2375        4557 :   fd_sched_block_t * block = block_pool_ele( sched, fec->bank_idx );
    2376        4557 :   if( FD_LIKELY( !fec->is_first_in_block ) ) {
    2377        3556 :     fec_buf_sz += block->residual_sz;
    2378        3556 :   } else {
    2379             :     /* No residual data as this is a fresh new block. */
    2380        1001 :   }
    2381             :   /* Addition is safe and won't overflow because we checked the FEC set
    2382             :      size above. */
    2383        4557 :   fec_buf_sz += fec->fec->data_sz;
    2384             :   /* Assuming every transaction is min size, do we have enough free
    2385             :      entries in the txn pool?  For a more precise txn count, we would
    2386             :      have to do some parsing. */
    2387        4557 :   return sched->txn_pool_free_cnt>=fec_buf_sz/FD_TXN_MIN_SERIALIZED_SZ && sched->mblk_pool_free_cnt>=fec_buf_sz/sizeof(fd_microblock_hdr_t);
    2388        4557 : }
    2389             : 
    2390             : ulong
    2391           0 : fd_sched_can_ingest_cnt( fd_sched_t * sched ) {
    2392           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    2393             :   /* Worst case, we need one byte from the incoming data to extract a
    2394             :      transaction out of the residual data, and the rest of the incoming
    2395             :      data contributes toward min sized transactions. */
    2396           0 :   return fd_ulong_min( sched->txn_pool_free_cnt/FD_SCHED_MAX_TXN_PER_FEC, sched->mblk_pool_free_cnt/FD_SCHED_MAX_MBLK_PER_FEC );
    2397           0 : }
    2398             : 
    2399             : int
    2400             : fd_sched_fec_waits_for_lane( fd_sched_t * sched,
    2401             :                              ulong        parent_bank_idx,
    2402           0 :                              int          is_first_in_block ) {
    2403           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    2404           0 :   if( FD_LIKELY( !fec_waits_for_lane( sched, parent_bank_idx, is_first_in_block ) ) ) return 0;
    2405           0 :   sched->metrics->lane_wait_cnt++;
    2406           0 :   return 1;
    2407           0 : }
    2408             : 
    2409             : int
    2410           0 : fd_sched_is_drained( fd_sched_t * sched ) {
    2411           0 :   int nothing_inflight = sched->exec_cnt==(ulong)fd_ulong_popcnt( sched->txn_exec_ready_bitset[ 0 ]&sched->sigverify_ready_bitset[ 0 ]&sched->poh_ready_bitset[ 0 ]&sched->footer_ready_bitset[ 0 ]&sched->lthash_ready_bitset[ 0 ] );
    2412           0 :   int nothing_queued = sched->active_bank_idx==ULONG_MAX;
    2413           0 :   return nothing_inflight && nothing_queued;
    2414           0 : }
    2415             : 
    2416             : ulong
    2417             : fd_sched_footer_dispatch( fd_sched_t * sched,
    2418             :                         ulong        bank_idx,
    2419           0 :                         ulong        cnt ) {
    2420           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    2421           0 :   FD_TEST( bank_idx==sched->active_bank_idx );
    2422           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    2423           0 :   FD_TEST( block->block_end_signaled && !block->block_end_done );
    2424           0 :   ulong idle    = sched->txn_exec_ready_bitset[ 0 ]&sched->sigverify_ready_bitset[ 0 ]&sched->poh_ready_bitset[ 0 ]&sched->footer_ready_bitset[ 0 ]&sched->lthash_ready_bitset[ 0 ];
    2425           0 :   ulong claimed = 0UL;
    2426           0 :   for( ; idle && cnt; cnt--, idle=fd_ulong_pop_lsb( idle ) ) {
    2427           0 :     int t = fd_ulong_find_lsb( idle );
    2428           0 :     claimed = fd_ulong_set_bit( claimed, t );
    2429           0 :     sched->tile_to_bank_idx[ t ] = bank_idx;
    2430           0 :     block->footer_in_flight_cnt++;
    2431           0 :   }
    2432           0 :   sched->footer_ready_bitset[ 0 ] &= ~claimed;
    2433           0 :   return claimed;
    2434           0 : }
    2435             : 
    2436             : FD_WARN_UNUSED int
    2437             : fd_sched_fec_ingest( fd_sched_t *     sched,
    2438        4556 :                      fd_sched_fec_t * fec ) {
    2439        4556 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    2440        4556 :   FD_TEST( fec->bank_idx<sched->block_cnt_max );
    2441        4556 :   FD_TEST( fec->parent_bank_idx<sched->block_cnt_max );
    2442        4556 :   FD_TEST( ref_q_empty( sched->ref_q ) );
    2443             : 
    2444        4556 :   fd_sched_block_t * block = block_pool_ele( sched, fec->bank_idx );
    2445             : 
    2446        4556 :   if( FD_UNLIKELY( fec->fec->data_sz>FD_SCHED_MAX_PAYLOAD_PER_FEC ) ) {
    2447           0 :     sched->print_buf_sz = 0UL;
    2448           0 :     print_all( sched, block );
    2449           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
    2450           0 :     FD_LOG_CRIT(( "invalid FEC set: fec->data_sz %u, slot %lu, parent slot %lu", fec->fec->data_sz, fec->slot, fec->parent_slot ));
    2451           0 :   }
    2452             : 
    2453        4556 :   sched->metrics->fec_cnt++;
    2454             : 
    2455        4556 :   if( FD_UNLIKELY( fec->is_first_in_block ) ) {
    2456             :     /* This is a new block. */
    2457        1000 :     add_block( sched, fec->bank_idx, fec->parent_bank_idx );
    2458        1000 :     block->slot                    = fec->slot;
    2459        1000 :     block->parent_slot             = fec->parent_slot;
    2460        1000 :     block->starts_at_update_parent = fec->starts_at_update_parent;
    2461             : 
    2462        1000 :     if( FD_UNLIKELY( block->dying ) ) {
    2463             :       /* The child of a dead block is also dead.  We added it to our
    2464             :          fork tree just so we could track an entire lineage of dead
    2465             :          children and propagate the dead property to the entire lineage,
    2466             :          in case there were frags for more than one dead children
    2467             :          in-flight at the time the parent was abandoned.  That being
    2468             :          said, we shouldn't need to add the dead child to the
    2469             :          dispatcher. */
    2470           0 :       sched->metrics->block_added_dead_ood_cnt++;
    2471             : 
    2472             :       /* Release the refcnt right away since we don't intend to replay
    2473             :          it at all.  We do this so its bank does not linger and stall
    2474             :          root advancement until the next root notify. */
    2475           0 :       subtree_release_refcnt( sched, block );
    2476             : 
    2477             :       /* Ignore the FEC set for a dead block. */
    2478           0 :       sched->metrics->bytes_dropped_cnt += fec->fec->data_sz;
    2479           0 :       return 0;
    2480           0 :     }
    2481             : 
    2482             :     /* A block whose parent is staged or done takes a free lane of its
    2483             :        own.  A child of an unstaged stageable parent will be unstaged
    2484             :        too.  This is not only lazy, sparing a promotion of the parent
    2485             :        right now, but an invariant that keeps a lane shortage from
    2486             :        deadlocking replay, since a staged block then never has an
    2487             :        unstaged parent that still has to run.  See the lane eviction
    2488             :        comments in try_activate_block(). */
    2489        1000 :     fd_sched_block_t * parent_block = block_pool_ele( sched, fec->parent_bank_idx );
    2490        1001 :     int needs_lane = parent_block->staged || !block_is_stageable( parent_block );
    2491        1000 :     if( FD_UNLIKELY( needs_lane && sched->staged_bitset==fd_ulong_mask_lsb( FD_SCHED_MAX_STAGING_LANES ) ) ) {
    2492             :       /* Every lane is taken.  A demotable block is empty and waiting
    2493             :          for FEC sets and loses nothing by waiting unstaged, so we give
    2494             :          its lane to the new block.  This is how the replay tile's wait
    2495             :          for a lane ends when no lane voluntarily frees up.  A caller
    2496             :          that chooses to ingest while there are no demotable lanes will
    2497             :          get the new block unstaged. */
    2498           0 :       int lane_idx = pick_idle_lane( sched );
    2499           0 :       if( FD_LIKELY( lane_idx>=0 ) ) {
    2500           0 :         demote_lane( sched, lane_idx );
    2501           0 :         sched->metrics->lane_demoted_cnt++;
    2502           0 :       }
    2503           0 :     }
    2504        1001 :     if( FD_LIKELY( needs_lane && sched->staged_bitset!=fd_ulong_mask_lsb( FD_SCHED_MAX_STAGING_LANES ) ) ) {
    2505             :       /* A lane is free. */
    2506        1001 :       int lane_idx = pick_free_lane( sched );
    2507        1001 :       sched->staged_bitset = fd_ulong_set_bit( sched->staged_bitset, lane_idx );
    2508        1001 :       sched->staged_head_bank_idx[ lane_idx ] = fec->bank_idx;
    2509        1001 :       sched->staged_popcnt_wmk = fd_ulong_max( sched->staged_popcnt_wmk, (ulong)fd_ulong_popcnt( sched->staged_bitset ) );
    2510        1001 :       block->in_rdisp     = 1;
    2511        1001 :       block->staged       = 1;
    2512        1001 :       block->staging_lane = (ulong)lane_idx;
    2513        1001 :       fd_rdisp_add_block( sched->rdisp, fec->bank_idx, block->staging_lane );
    2514        1001 :       lthash_claim_lane_map( sched, block );
    2515        1001 :       sched->metrics->block_added_cnt++;
    2516        1001 :       sched->metrics->block_added_staged_cnt++;
    2517        1001 :       FD_LOG_DEBUG(( "block %lu:%lu entered lane %lu: add", block->slot, fec->bank_idx, block->staging_lane ));
    2518 >1844*10^16 :     } else {
    2519             :       /* The parent is unstaged, or no lane is free.  The block will be
    2520             :          unstaged until it is promoted. */
    2521 >1844*10^16 :       block->in_rdisp = 1;
    2522 >1844*10^16 :       block->staged   = 0;
    2523 >1844*10^16 :       fd_rdisp_add_block( sched->rdisp, fec->bank_idx, FD_RDISP_UNSTAGED );
    2524 >1844*10^16 :       sched->metrics->block_added_cnt++;
    2525 >1844*10^16 :       sched->metrics->block_added_unstaged_cnt++;
    2526 >1844*10^16 :       FD_LOG_DEBUG(( "block %lu:%lu entered lane unstaged: add", block->slot, fec->bank_idx ));
    2527 >1844*10^16 :     }
    2528        1000 :   }
    2529             : 
    2530        4556 :   block->txn_pool_max_popcnt   = fd_ulong_max( block->txn_pool_max_popcnt, sched->depth - sched->txn_pool_free_cnt - 1UL );
    2531        4556 :   block->mblk_pool_max_popcnt  = fd_ulong_max( block->mblk_pool_max_popcnt, sched->depth - sched->mblk_pool_free_cnt );
    2532        4556 :   block->block_pool_max_popcnt = fd_ulong_max( block->block_pool_max_popcnt, sched->block_pool_popcnt );
    2533             : 
    2534        4556 :   if( FD_UNLIKELY( block->dying ) ) {
    2535             :     /* Ignore the FEC set for a dead block. */
    2536           0 :     sched->metrics->bytes_dropped_cnt += fec->fec->data_sz;
    2537           0 :     return 1;
    2538           0 :   }
    2539             : 
    2540        4556 :   if( FD_UNLIKELY( !block->in_rdisp ) ) {
    2541             :     /* Invariant: block must be in the dispatcher at this point. */
    2542           0 :     sched->print_buf_sz = 0UL;
    2543           0 :     print_all( sched, block );
    2544           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
    2545           0 :     FD_LOG_CRIT(( "invariant violation: block->in_rdisp==0, slot %lu, parent slot %lu",
    2546           0 :                   block->slot, block->parent_slot ));
    2547           0 :   }
    2548             : 
    2549        4556 :   if( FD_UNLIKELY( block->fec_eos ) ) {
    2550             :     /* This means something is wrong upstream.  We're getting more FEC
    2551             :        sets for a block that has already ended, or so we were told. */
    2552           0 :     sched->print_buf_sz = 0UL;
    2553           0 :     print_all( sched, block );
    2554           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
    2555           0 :     FD_LOG_CRIT(( "invariant violation: block->fec_eos set but getting more FEC sets, slot %lu, parent slot %lu", fec->slot, fec->parent_slot ));
    2556           0 :   }
    2557        4556 :   if( FD_UNLIKELY( block->fec_eob ) ) {
    2558             :     /* fec_eob is only ever set from is_last_in_batch, and the parser is
    2559             :        expected to finish a batch in full upon getting the FEC set that
    2560             :        ends the batch.  An incomplete parse is caught eagerly below,
    2561             :        which marks the block dying and is caught above.  Getting here
    2562             :        means the eager check below regressed. */
    2563           0 :     sched->print_buf_sz = 0UL;
    2564           0 :     print_all( sched, block );
    2565           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
    2566           0 :     FD_LOG_CRIT(( "invariant violation: block->fec_eob set but getting more FEC sets, slot %lu, parent slot %lu", fec->slot, fec->parent_slot ));
    2567           0 :   }
    2568        4556 :   if( FD_UNLIKELY( block->child_idx!=ULONG_MAX ) ) {
    2569             :     /* This means something is wrong upstream.  FEC sets are not being
    2570             :        delivered in replay order.  We got a child block FEC set before
    2571             :        this block was completely delivered. */
    2572           0 :     sched->print_buf_sz = 0UL;
    2573           0 :     print_all( sched, block );
    2574           0 :     fd_sched_block_t * child_block = block_pool_ele( sched, block->child_idx );
    2575           0 :     print_block_debug( sched, child_block );
    2576           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
    2577           0 :     FD_LOG_CRIT(( "invariant violation: block->child_idx %lu, slot %lu, parent slot %lu", block->child_idx, fec->slot, fec->parent_slot ));
    2578           0 :   }
    2579             : 
    2580             :   /* The parse buffer takes the residual from the previous FEC set
    2581             :      within the same batch, followed by the new FEC set.  Both are
    2582             :      bounded, so this always fits. */
    2583        4556 :   FD_TEST( block->residual_sz<=FD_SCHED_MAX_RESIDUAL_SZ );
    2584        4556 :   fd_memcpy( sched->fec_buf, block->residual, block->residual_sz );
    2585        4556 :   fd_memcpy( sched->fec_buf+block->residual_sz, fec->data, fec->fec->data_sz );
    2586        4556 :   sched->fec_buf_sz   = block->residual_sz+(uint)fec->fec->data_sz;
    2587        4556 :   sched->fec_buf_soff = 0U;
    2588        4556 :   sched->metrics->bytes_ingested_cnt += fec->fec->data_sz;
    2589             : 
    2590        4556 :   block->fec_eob = fec->is_last_in_batch;
    2591        4556 :   block->fec_eos = fec->is_last_in_block;
    2592             : 
    2593        4556 :   ulong tracked_shred_cnt = fd_ulong_min( fec->shred_cnt, FD_FEC_SHRED_CNT );
    2594        4556 :   uint  tracked_data_sz   = 0U;
    2595      150316 :   for( ulong i=0UL; i<tracked_shred_cnt; i++ ) tracked_data_sz += fec->fec->shred_sz[ i ];
    2596        4556 :   if( FD_LIKELY( fec->shred_cnt<=FD_FEC_SHRED_CNT ) ) FD_TEST( tracked_data_sz==fec->fec->data_sz );
    2597           1 :   else                                                FD_TEST( tracked_data_sz<=fec->fec->data_sz );
    2598             : 
    2599        4556 :   uint prev_shred_off = 0U;
    2600      150288 :   for( ulong i=0; i<fec->shred_cnt; i++ ) {
    2601      145732 :     FD_TEST( block->shred_cnt<sched->max_shreds_per_block );
    2602      145732 :     uint shred_off;
    2603      145733 :     if( FD_LIKELY( i<FD_FEC_SHRED_CNT ) ) {
    2604      145733 :       shred_off = prev_shred_off + fec->fec->shred_sz[ i ];
    2605 >1844*10^16 :     } else if( FD_UNLIKELY( i!=fec->shred_cnt-1UL ) ) {
    2606             :       /* We don't track shred boundaries after 32 shreds, assume they're
    2607             :          sized uniformly */
    2608           0 :       ulong num_overflow_shreds = fec->shred_cnt-FD_FEC_SHRED_CNT;
    2609           0 :       ulong overflow_idx        = i-FD_FEC_SHRED_CNT;
    2610           0 :       ulong overflow_data_sz    = (ulong)fec->fec->data_sz-tracked_data_sz;
    2611           0 :       shred_off = tracked_data_sz + (uint)(overflow_data_sz / num_overflow_shreds * (overflow_idx + 1UL));
    2612 >1844*10^16 :     } else {
    2613 >1844*10^16 :       shred_off = (uint)fec->fec->data_sz;
    2614 >1844*10^16 :     }
    2615      145732 :     FD_TEST( shred_off>=prev_shred_off && shred_off-prev_shred_off<=USHORT_MAX );
    2616      145766 :     if( FD_LIKELY( block->shred_cnt-block->shred_scan_idx<FD_SCHED_SHRED_SZ_WINDOW ) ) {
    2617      145766 :       block->shred_sz[ block->shred_cnt&(uint)(FD_SCHED_SHRED_SZ_WINDOW-1UL) ] = (ushort)(shred_off-prev_shred_off);
    2618 >1844*10^16 :     } else {
    2619 >1844*10^16 :       block->shred_sz_overflow = 1;
    2620 >1844*10^16 :     }
    2621      145732 :     block->shred_cnt++;
    2622      145732 :     prev_shred_off = shred_off;
    2623      145732 :   }
    2624             : 
    2625        4556 :   block->fec_completed_ns = fec->completed_ns;
    2626             : 
    2627        4556 :   int err = fd_sched_parse( sched, block, fec->alut_ctx );
    2628             : 
    2629        4556 :   if( FD_UNLIKELY( err!=FD_SCHED_DEAD_REASON_NONE ) ) {
    2630           0 :     handle_bad_block( sched, block, err );
    2631           0 :     sched->metrics->bytes_dropped_cnt += sched->fec_buf_sz-sched->fec_buf_soff;
    2632           0 :     return 0;
    2633           0 :   }
    2634             : 
    2635        4556 :   if( FD_UNLIKELY( (fec->is_last_in_batch||fec->is_last_in_block) && (block->txns_rem||block->mblks_rem||block->fec_eob) ) ) {
    2636             :     /* A malformed block that fails to parse out exactly as many
    2637             :        transactions and microblocks as it should.
    2638             : 
    2639             :        Upon getting a last-in-batch FEC, everything in the ongoing batch
    2640             :        should completely parse, and the eob flag should be reset.  There
    2641             :        should be no go around for a last-in-batch FEC. */
    2642           2 :     FD_LOG_INFO(( "bad block: SHORT_BLOCK, bytes_rem %u, txns_rem %lu, mblks_rem %lu, fec_eob %d, slot %lu, parent slot %lu", sched->fec_buf_sz-sched->fec_buf_soff, block->txns_rem, block->mblks_rem, block->fec_eob, block->slot, block->parent_slot ));
    2643           2 :     handle_bad_block( sched, block, FD_SCHED_DEAD_REASON_SHORT_BLOCK );
    2644           2 :     return 0;
    2645           2 :   }
    2646             : 
    2647        4554 :   if( FD_UNLIKELY( block->fec_eos && !block->last_mblk_is_tick ) ) {
    2648             :     /* The last microblock should be a tick.
    2649             : 
    2650             :        Note that this early parse-time detection could cause us to throw
    2651             :        a slightly different error from Agave, in the case that there are
    2652             :        too few ticks, since the tick count check precedes the trailing
    2653             :        entry check in Agave.  That being said, ultimately a
    2654             :        TRAILING_ENTRY renders a block invalid, regardless of anything
    2655             :        else. */
    2656           0 :     FD_LOG_INFO(( "bad block: TRAILING_ENTRY, slot %lu, parent slot %lu, mblk_cnt %u", block->slot, block->parent_slot, block->mblk_cnt ));
    2657           0 :     handle_bad_block( sched, block, FD_SCHED_DEAD_REASON_TRAILING_ENTRY );
    2658           0 :     return 0;
    2659           0 :   }
    2660             : 
    2661        4554 :   if( FD_UNLIKELY( block->fec_eos ) ) {
    2662         991 :     int dead_reason = on_final( block );
    2663         991 :     if( FD_UNLIKELY( dead_reason!=FD_SCHED_DEAD_REASON_NONE ) ) {
    2664           0 :       handle_bad_block( sched, block, dead_reason );
    2665           0 :       return 0;
    2666           0 :     }
    2667         991 :   }
    2668             : 
    2669             :   /* Park the residual until the next FEC set.  The parser stops only on
    2670             :      a parseable unit it has not fully received.  A parseable unit
    2671             :      includes a transaction, a block marker, or a microblock or batch
    2672             :      header.  The parser drops the trailing bytes of a finished batch.
    2673             :      So a residual larger than any of those parseable units is malformed
    2674             :      content.  Instead of crashing, we consider the block invalid. */
    2675        4554 :   uint residual_sz = sched->fec_buf_sz-sched->fec_buf_soff;
    2676        4554 :   if( FD_UNLIKELY( residual_sz>FD_SCHED_MAX_RESIDUAL_SZ ) ) {
    2677           0 :     FD_LOG_INFO(( "bad block: UNPARSEABLE_CONTENT, residual_sz %u, slot %lu, parent slot %lu", residual_sz, fec->slot, fec->parent_slot ));
    2678           0 :     handle_bad_block( sched, block, FD_SCHED_DEAD_REASON_UNPARSEABLE_CONTENT );
    2679           0 :     sched->metrics->bytes_dropped_cnt += residual_sz;
    2680           0 :     return 0;
    2681           0 :   }
    2682        4554 :   fd_memcpy( block->residual, sched->fec_buf+sched->fec_buf_soff, residual_sz );
    2683        4554 :   block->fec_buf_boff += sched->fec_buf_soff;
    2684        4554 :   block->residual_sz   = residual_sz;
    2685             :   /* Pass the shreds the next transaction cannot start in, so that few
    2686             :      shred sizes stay outstanding between FEC sets. */
    2687        4554 :   shred_split( block, block->fec_buf_boff );
    2688             : 
    2689             :   /* We just received a FEC set, which may have made all transactions in
    2690             :      a partially parsed microblock available.  If this were a malformed
    2691             :      block that ends in a non-tick microblock, there's not going to be a
    2692             :      hashing task from the missing ending tick to drain the mixin queue.
    2693             :      So we try to drain the mixin queue right here.  Another option is
    2694             :      to drain it at dispatch time, when we are about to dispatch the end
    2695             :      of block signal, right before the check for whether block should
    2696             :      end. */
    2697        4554 :   int mixin_res;
    2698        4554 :   while( (mixin_res=maybe_mixin( sched, block )) ) {
    2699           0 :     if( FD_UNLIKELY( mixin_res==-1 ) ) {
    2700           0 :       handle_bad_block( sched, block, FD_SCHED_DEAD_REASON_ENTRY_HASH_MISMATCH_INGEST );
    2701           0 :       return 0;
    2702           0 :     }
    2703           0 :     FD_TEST( mixin_res==1||mixin_res==2 );
    2704           0 :   }
    2705             : 
    2706             :   /* Every account the block intends to write has now been observed.
    2707             :      Each gets a pseudo transaction for its LtHash addition, created by
    2708             :      polls that find nothing to dispatch. */
    2709        4554 :   if( FD_UNLIKELY( fec->is_last_in_block ) ) lthash_drain_later( sched, block );
    2710             : 
    2711        4554 :   slot_state_eval( sched, block );
    2712             : 
    2713             :   /* Check if we need to set the active block. */
    2714        4554 :   check_or_set_active_block( sched );
    2715             : 
    2716        4554 :   return 1;
    2717        4554 : }
    2718             : 
    2719             : /* A cert holds a tile for about a millisecond.  So footer cert verify
    2720             :    tasks follow the LtHash batch policy.  They go out only when no
    2721             :    transaction is ready, leave the hash batch reserve idle, and are
    2722             :    offered from the footer's parse until BLOCK_END.  Like a hash batch
    2723             :    it takes the highest idle tile.  Returns 1 if a task was written to
    2724             :    out, 0 otherwise. */
    2725             : static int
    2726             : cert_dispatch( fd_sched_t *       sched,
    2727             :                fd_sched_block_t * block,
    2728             :                ulong              bank_idx,
    2729             :                ulong              ready_bitset,
    2730             :                int                reserve,
    2731           0 :                fd_sched_task_t *  out ) {
    2732           0 :   if( FD_LIKELY( !block->footer_seen || block->cert_next>=block->cert_cnt || block->block_end_signaled ) ) return 0;
    2733           0 :   if( FD_UNLIKELY( (ulong)fd_ulong_popcnt( ready_bitset )<=(ulong)reserve ) ) return 0;
    2734           0 :   int t = fd_ulong_find_msb( ready_bitset );
    2735           0 :   sched->footer_ready_bitset[ 0 ] = fd_ulong_clear_bit( sched->footer_ready_bitset[ 0 ], t );
    2736           0 :   sched->tile_to_bank_idx[ t ]    = bank_idx;
    2737           0 :   block->footer_in_flight_cnt++;
    2738           0 :   out->task_type             = FD_SCHED_TT_CERT_VERIFY;
    2739           0 :   out->cert_verify->bank_idx = bank_idx;
    2740           0 :   out->cert_verify->cert_idx = (ulong)block->cert_next++;
    2741           0 :   out->cert_verify->exec_idx = (ulong)t;
    2742           0 :   return 1;
    2743           0 : }
    2744             : 
    2745             : /* If may_drain is set, allow one drain chunk once nothing else can be
    2746             :    dispatched.  The re-entry the chunk earns passes 0, so a poll drains
    2747             :    at most once. */
    2748             : static ulong
    2749           0 : task_next_ready( fd_sched_t * sched, fd_sched_task_t * out, int may_drain ) {
    2750           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    2751           0 :   FD_TEST( ref_q_empty( sched->ref_q ) );
    2752             : 
    2753           0 :   ulong exec_ready_bitset0 = sched->txn_exec_ready_bitset[ 0 ];
    2754           0 :   ulong exec_fully_ready_bitset = sched->sigverify_ready_bitset[ 0 ] & sched->poh_ready_bitset[ 0 ] & sched->footer_ready_bitset[ 0 ] & sched->lthash_ready_bitset[ 0 ] & exec_ready_bitset0;
    2755           0 :   if( FD_UNLIKELY( !exec_fully_ready_bitset ) ) {
    2756             :     /* Early exit if no exec tiles available.  Also let's make some
    2757             :        progress on a pending drain. */
    2758           0 :     lthash_drain_chunk( sched );
    2759           0 :     return 0UL;
    2760           0 :   }
    2761             : 
    2762           0 :   if( FD_UNLIKELY( sched->active_bank_idx==ULONG_MAX ) ) {
    2763             :     /* No need to try activating a block.  If we're in this state,
    2764             :        there's truly nothing to execute.  We will activate something
    2765             :        when we ingest a FEC set with transactions. */
    2766           0 :     return 0UL;
    2767           0 :   }
    2768             : 
    2769           0 :   out->task_type = FD_SCHED_TT_NULL;
    2770             : 
    2771             :   /* We could in theory reevaluate staging lane allocation here and do
    2772             :      promotion/demotion as needed.  It's a policy decision to minimize
    2773             :      fork churn for now and just execute down the same active fork. */
    2774             : 
    2775           0 :   ulong bank_idx = sched->active_bank_idx;
    2776           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    2777           0 :   if( FD_UNLIKELY( block_should_deactivate( sched, block ) ) ) {
    2778           0 :     sched->print_buf_sz = 0UL;
    2779           0 :     print_all( sched, block );
    2780           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
    2781           0 :     FD_LOG_CRIT(( "invariant violation: active block %lu:%lu is not activatable nor has anything in-flight", block->slot, sched->active_bank_idx ));
    2782           0 :   }
    2783             : 
    2784           0 :   block->txn_pool_max_popcnt   = fd_ulong_max( block->txn_pool_max_popcnt, sched->depth - sched->txn_pool_free_cnt - 1UL );
    2785           0 :   block->mblk_pool_max_popcnt  = fd_ulong_max( block->mblk_pool_max_popcnt, sched->depth - sched->mblk_pool_free_cnt );
    2786           0 :   block->block_pool_max_popcnt = fd_ulong_max( block->block_pool_max_popcnt, sched->block_pool_popcnt );
    2787             : 
    2788           0 :   if( FD_UNLIKELY( !block->block_start_signaled ) ) {
    2789           0 :     if( !block->lthash_mode_picked ) lthash_pick_mode( sched, block );
    2790           0 :     out->task_type = FD_SCHED_TT_BLOCK_START;
    2791           0 :     out->block_start->bank_idx        = bank_idx;
    2792           0 :     out->block_start->parent_bank_idx = block->parent_idx;
    2793           0 :     out->block_start->slot            = block->slot;
    2794           0 :     out->block_start->lthash_inband   = block->lthash_inband;
    2795           0 :     block->block_start_signaled = 1;
    2796           0 :     slot_state_open( sched, block );
    2797           0 :     sched->next_ready_last_tick     = fd_tickcount();
    2798           0 :     sched->next_ready_last_bank_idx = bank_idx;
    2799           0 :     return 1UL;
    2800           0 :   }
    2801             : 
    2802             :   /* Transactions, PoH and sigverify take the lowest idle tile.  LtHash
    2803             :      batches take the highest. */
    2804           0 :   ulong exec_tile_idx0 = fd_ulong_if( !!exec_fully_ready_bitset, (ulong)fd_ulong_find_lsb( exec_fully_ready_bitset ), ULONG_MAX );
    2805           0 :   ulong exec_tile_idx1 = fd_ulong_if( !!exec_fully_ready_bitset, (ulong)fd_ulong_find_msb( exec_fully_ready_bitset ), ULONG_MAX );
    2806           0 :   ulong idle_cnt       = (ulong)fd_ulong_popcnt( exec_fully_ready_bitset );
    2807             : 
    2808             :   /* Every task but a transaction leaves reserve tiles idle, a hash
    2809             :      batch lthash_reserve.  A hash batch carries at most lthash_cap
    2810             :      accounts. */
    2811           0 :   int   reserve        = idle_reserve( sched, block );
    2812           0 :   int   lthash_reserve = lthash_idle_reserve( sched );
    2813           0 :   ulong lthash_cap     = fd_ulong_if( block->txn_exec_in_flight_cnt>0U, FD_SCHED_LTHASH_BATCH_BUSY_MAX, FD_SCHED_LTHASH_BATCH_MAX );
    2814             : 
    2815           0 :   ulong exec_queued_cnt = block->txn_parsed_cnt-block->txn_exec_in_flight_cnt-block->txn_exec_done_cnt;
    2816           0 :   int   add_unsurfaced  = block->add_created_cnt>block->add_surfaced_cnt; /* Addition pseudo transactions are queued in rdisp along with real transactions. */
    2817           0 :   int   add_held        = sched->lthash_add_batch->cnt>0UL;
    2818           0 :   ulong txn_idx = 0UL;
    2819           0 :   if( FD_LIKELY( (exec_queued_cnt>0UL || add_unsurfaced || add_held) && idle_cnt ) ) { /* Optimize for no fork switching. */
    2820             :     /* Transaction execution has the highest priority.  Current mainnet
    2821             :        block times are very much dominated by critical path transaction
    2822             :        execution.  To achieve the fastest block replay speed, we can't
    2823             :        afford to make any mistake in critical path dispatching.  Any
    2824             :        deviation from perfect critical path dispatching is basically
    2825             :        irrecoverable.  As such, we try to keep all the exec tiles busy
    2826             :        with transaction execution, but we allow at most one transaction
    2827             :        to be in-flight per exec tile.  This is to ensure that whenever a
    2828             :        critical path transaction completes, we have at least one exec
    2829             :        tile, e.g. the one that just completed said transaction, readily
    2830             :        available to continue executing down the critical path. */
    2831           0 :     ulong appended_cnt = 0UL; /* Additions this poll batched. */
    2832           0 :     txn_idx = fd_rdisp_get_next_ready( sched->rdisp, bank_idx );
    2833           0 :     while( FD_UNLIKELY( txn_idx & FD_RDISP_LTHASH_PSEUDO_TXN ) ) {
    2834             :       /* A pseudo transaction surfaced.  By rdisp's scoring policy we
    2835             :          know that this implies no real transaction is ready. */
    2836           0 :       block->state_conflict = (uchar)( exec_queued_cnt>0UL );
    2837           0 :       fd_sched_lthash_entry_t * e = lthash_surface( sched, block, bank_idx, txn_idx );
    2838           0 :       if( FD_LIKELY( e ) ) {
    2839             :         /* ... and the addition is not stale. */
    2840           0 :         fd_sched_lthash_batch_t * batch = sched->lthash_add_batch;
    2841           0 :         if( FD_UNLIKELY( e->spec_idx!=UINT_MAX ) ) {
    2842             :           /* This is a speculative addition that waited behind a
    2843             :              serializing transaction.  The speculation goes out under
    2844             :              the same idleness policy as lthash_speculate(), and could
    2845             :              be given up rather than forced onto the last idle tile.
    2846             :              Once the block is complete it is given up outright.  Giving
    2847             :              up creates an addition sequenced behind every real
    2848             :              transaction, as desired.  If we did a speculation here,
    2849             :              there's a small chance it might be undone. */
    2850           0 :           if( FD_LIKELY( !block->fec_eos && idle_cnt>(ulong)reserve ) ) {
    2851           0 :             lthash_dispatch_one_add( sched, block, bank_idx, e, txn_idx, (int)exec_tile_idx1, out );
    2852           0 :             sched->metrics->lthash_add_batch_held_cnt += (ulong)(batch->cnt>0UL);
    2853           0 :             sched->next_ready_last_tick     = fd_tickcount();
    2854           0 :             sched->next_ready_last_bank_idx = bank_idx;
    2855           0 :             return 1UL;
    2856           0 :           }
    2857           0 :           lthash_spec_give_up( sched, block, bank_idx, e, txn_idx );
    2858           0 :         } else {
    2859           0 :           if( FD_UNLIKELY( !batch->cnt ) ) lthash_batch_init( block, batch, bank_idx, FD_SCHED_TT_LTHASH_ADD );
    2860           0 :           lthash_batch_append( sched, batch, e, txn_idx );
    2861           0 :           appended_cnt++;
    2862           0 :           if( FD_UNLIKELY( batch->cnt==FD_SCHED_LTHASH_BATCH_MAX ) ) {
    2863             :             /* A full batch goes out even if we're down to the last idle
    2864             :                tile, since the next poll could give us an addition and
    2865             :                we'll have no space to batch it. */
    2866           0 :             lthash_dispatch_held_batch( sched, block, bank_idx, (int)exec_tile_idx1, out );
    2867           0 :             sched->next_ready_last_tick     = fd_tickcount();
    2868           0 :             sched->next_ready_last_bank_idx = bank_idx;
    2869           0 :             return 1UL;
    2870           0 :           }
    2871           0 :           if( FD_UNLIKELY( batch->cnt>=lthash_cap ) ) {
    2872             :             /* The batch is big enough to go out.  The addition just
    2873             :                appended implies no transaction is ready, otherwise rdisp
    2874             :                would have returned the transaction instead of the
    2875             :                addition.  So we're not leaving critical path work on the
    2876             :                table. */
    2877           0 :             txn_idx = 0UL;
    2878           0 :             break;
    2879           0 :           }
    2880           0 :         }
    2881           0 :       }
    2882           0 :       txn_idx = fd_rdisp_get_next_ready( sched->rdisp, bank_idx );
    2883           0 :     }
    2884           0 :     block->state_conflict = (uchar)( !txn_idx && exec_queued_cnt>0UL );
    2885           0 :     if( FD_UNLIKELY( sched->lthash_add_batch->cnt ) ) {
    2886           0 :       if( FD_LIKELY( !txn_idx ) ) {
    2887             :         /* Nothing is ready behind the batch, or the batch is at its
    2888             :            cap. */
    2889           0 :         if( FD_LIKELY( idle_cnt>(ulong)lthash_reserve ) ) {
    2890           0 :           lthash_dispatch_held_batch( sched, block, bank_idx, (int)exec_tile_idx1, out );
    2891           0 :           sched->next_ready_last_tick     = fd_tickcount();
    2892           0 :           sched->next_ready_last_bank_idx = bank_idx;
    2893           0 :           return 1UL;
    2894           0 :         }
    2895             :         /* Too few idle tiles for a hash batch.  The batch stays held
    2896             :            and goes out from a later call, once a poll finds nothing
    2897             :            ready and enough tiles are idle. */
    2898           0 :         sched->metrics->lthash_add_batch_wait_idle_cnt++;
    2899           0 :       } else if( appended_cnt ) {
    2900             :         /* A real transaction surfaced after we've batched some
    2901             :            additions in this poll. */
    2902           0 :         sched->metrics->lthash_add_batch_held_cnt++;
    2903           0 :       }
    2904           0 :     }
    2905           0 :     if( FD_UNLIKELY( txn_idx==0UL && exec_queued_cnt>0UL ) ) {
    2906             :       /* There are transactions queued but none ready for execution.
    2907             :          This implies that there must be in-flight transactions on whose
    2908             :          completion the queued transactions depend.  A queued
    2909             :          transaction may also be waiting on a dispatched speculative
    2910             :          addition. */
    2911           0 :       if( FD_UNLIKELY( !block->txn_exec_in_flight_cnt && block->add_surfaced_cnt==block->add_done_cnt ) ) {
    2912           0 :         sched->print_buf_sz = 0UL;
    2913           0 :         print_all( sched, block );
    2914           0 :         FD_LOG_NOTICE(( "%s", sched->print_buf ));
    2915           0 :         FD_LOG_CRIT(( "invariant violation: no ready transaction found but block->txn_exec_in_flight_cnt==0" ));
    2916           0 :       }
    2917             : 
    2918             :       /* Next up are PoH tasks.  Same dispatching policy as sigverify
    2919             :          tasks. */
    2920           0 :       ulong poh_ready_bitset = exec_fully_ready_bitset;
    2921           0 :       ulong poh_hashing_queued_cnt = block->mblk_cnt-block->poh_mblk_in_flight_cnt-block->poh_hashing_done_cnt;
    2922           0 :       int   poh_threshold = reserve;
    2923           0 :       if( FD_LIKELY( poh_hashing_queued_cnt>0UL && fd_ulong_popcnt( poh_ready_bitset )>poh_threshold ) ) {
    2924           0 :         dispatch_poh( sched, block, bank_idx, fd_ulong_find_lsb( poh_ready_bitset ), out );
    2925           0 :         sched->next_ready_last_tick     = fd_tickcount();
    2926           0 :         sched->next_ready_last_bank_idx = bank_idx;
    2927           0 :         return 1UL;
    2928           0 :       }
    2929             : 
    2930             :       /* Dispatch more sigverify tasks to completely idle tiles over the
    2931             :          reserve, at most one in flight per tile. */
    2932           0 :       ulong sigverify_ready_bitset = exec_fully_ready_bitset;
    2933           0 :       ulong sigverify_queued_cnt = block->txn_parsed_cnt-block->txn_sigverify_in_flight_cnt-block->txn_sigverify_done_cnt;
    2934           0 :       if( FD_LIKELY( sigverify_queued_cnt>0UL && fd_ulong_popcnt( sigverify_ready_bitset )>reserve ) ) {
    2935           0 :         dispatch_sigverify( sched, block, bank_idx, fd_ulong_find_lsb( sigverify_ready_bitset ), out );
    2936           0 :         sched->next_ready_last_tick = sched->txn_info_pool[ out->txn_sigverify->txn_idx ].tick_sigverify_disp = fd_tickcount();
    2937           0 :         sched->next_ready_last_bank_idx = bank_idx;
    2938           0 :         return 1UL;
    2939           0 :       }
    2940             : 
    2941             :       /* Then footer certs and LtHash subtractions, under the hash batch
    2942             :          reserve. */
    2943           0 :       if( FD_UNLIKELY( cert_dispatch( sched, block, bank_idx, exec_fully_ready_bitset, lthash_reserve, out ) ) ) {
    2944           0 :         sched->next_ready_last_tick     = fd_tickcount();
    2945           0 :         sched->next_ready_last_bank_idx = bank_idx;
    2946           0 :         return 1UL;
    2947           0 :       }
    2948           0 :       if( FD_UNLIKELY( lthash_dispatch_sub( sched, block, bank_idx, exec_fully_ready_bitset, lthash_reserve, lthash_cap, out ) ) ) {
    2949           0 :         sched->next_ready_last_tick     = fd_tickcount();
    2950           0 :         sched->next_ready_last_bank_idx = bank_idx;
    2951           0 :         return 1UL;
    2952           0 :       }
    2953             :       /* Nothing the block must do could go out, so let's make some
    2954             :          progress on a pending drain.  A pseudo transaction the drain
    2955             :          created may be ready, so poll once more. */
    2956           0 :       if( may_drain && lthash_drain_chunk( sched ) ) return task_next_ready( sched, out, 0 );
    2957             : 
    2958             :       /* Now try to dispatch a speculative addition, the same way we do
    2959             :          in the branch where the transaction queue is empty.  Every
    2960             :          account we select as a speculation candidate has seen its last
    2961             :          parsed writer finish, so the speculation doesn't block any
    2962             :          queued transactions.  While having lthash additions in flight
    2963             :          could in theory slightly delay transaction dispatch when the
    2964             :          blocker transaction returns, the delay should be minimal.  FECs
    2965             :          also tend to end on a long serial transaction path, so there's
    2966             :          typically not a wide DAG phase being starved by lthash
    2967             :          additions.  More often, not allowing lthash speculations to go
    2968             :          out in this branch causes them to be clustered at the end of a
    2969             :          mid-slot FEC, after the last transaction of the FEC has been
    2970             :          dispatched.  This has a good chance to lead to lthash
    2971             :          speculations finishing well after the last transaction of the
    2972             :          FEC has returned, which is not ideal. */
    2973           0 :       if( FD_LIKELY( block_may_speculate( sched, block, 1UL ) ) ) {
    2974           0 :         int spec = lthash_speculate( sched, block, bank_idx, exec_fully_ready_bitset, out );
    2975           0 :         if( FD_UNLIKELY( spec>=0 ) ) {
    2976           0 :           if( spec ) {
    2977           0 :             sched->next_ready_last_tick     = fd_tickcount();
    2978           0 :             sched->next_ready_last_bank_idx = bank_idx;
    2979           0 :           }
    2980           0 :           return (ulong)spec;
    2981           0 :         }
    2982           0 :       }
    2983           0 :       return 0UL;
    2984           0 :     }
    2985           0 :   }
    2986           0 :   if( FD_LIKELY( txn_idx ) ) {
    2987           0 :     out->task_type = FD_SCHED_TT_TXN_EXEC;
    2988           0 :     out->txn_exec->txn_idx  = txn_idx;
    2989           0 :     out->txn_exec->bank_idx = bank_idx;
    2990           0 :     out->txn_exec->slot     = block->slot;
    2991           0 :     out->txn_exec->exec_idx = exec_tile_idx0;
    2992           0 :     FD_TEST( out->txn_exec->exec_idx!=ULONG_MAX );
    2993             : 
    2994           0 :     long now = fd_tickcount();
    2995           0 :     ulong delta = (ulong)(now-sched->txn_in_flight_last_tick);
    2996           0 :     ulong txn_exec_busy_cnt = sched->exec_cnt-(ulong)fd_ulong_popcnt( exec_ready_bitset0 );
    2997           0 :     sched->metrics->txn_none_in_flight_tickcount     += fd_ulong_if( txn_exec_busy_cnt==0UL, delta, 0UL );
    2998           0 :     sched->metrics->txn_weighted_in_flight_tickcount += fd_ulong_if( txn_exec_busy_cnt!=0UL, delta, 0UL );
    2999           0 :     sched->metrics->txn_weighted_in_flight_cnt       += delta*txn_exec_busy_cnt;
    3000           0 :     sched->txn_in_flight_last_tick = now;
    3001             : 
    3002           0 :     sched->txn_info_pool[ out->txn_exec->txn_idx ].tick_exec_disp = now;
    3003           0 :     sched->txn_info_pool[ out->txn_exec->txn_idx ].exec_tile_idx  = exec_tile_idx0;
    3004             : 
    3005           0 :     sched->txn_exec_ready_bitset[ 0 ] = fd_ulong_clear_bit( exec_ready_bitset0, (int)exec_tile_idx0);
    3006           0 :     sched->tile_to_bank_idx[ exec_tile_idx0 ] = bank_idx;
    3007             : 
    3008           0 :     block->txn_exec_in_flight_cnt++;
    3009           0 :     sched->metrics->txn_max_in_flight_cnt = fd_uint_max( sched->metrics->txn_max_in_flight_cnt, block->txn_exec_in_flight_cnt );
    3010             : 
    3011           0 :     exec_busy_check( sched, block );
    3012           0 :     sched->next_ready_last_tick     = now;
    3013           0 :     sched->next_ready_last_bank_idx = bank_idx;
    3014           0 :     return 1UL;
    3015           0 :   }
    3016             : 
    3017             :   /* At this point txn_queued_cnt==0.  Any unsurfaced addition waits on
    3018             :      an in-flight writer or a serializing transaction. */
    3019             : 
    3020             :   /* Next up are PoH tasks.  Same dispatching policy as sigverify. */
    3021           0 :   ulong poh_ready_bitset = exec_fully_ready_bitset;
    3022           0 :   ulong poh_hashing_queued_cnt = block->mblk_cnt-block->poh_mblk_in_flight_cnt-block->poh_hashing_done_cnt;
    3023           0 :   int   poh_threshold = reserve;
    3024           0 :   if( FD_LIKELY( poh_hashing_queued_cnt>0UL && fd_ulong_popcnt( poh_ready_bitset )>poh_threshold ) ) {
    3025           0 :     dispatch_poh( sched, block, bank_idx, fd_ulong_find_lsb( poh_ready_bitset ), out );
    3026           0 :     sched->next_ready_last_tick     = fd_tickcount();
    3027           0 :     sched->next_ready_last_bank_idx = bank_idx;
    3028           0 :     return 1UL;
    3029           0 :   }
    3030             : 
    3031             :   /* Try to dispatch a sigverify task to a qualified idle tile. */
    3032           0 :   ulong sigverify_ready_bitset = exec_fully_ready_bitset;
    3033           0 :   ulong sigverify_queued_cnt = block->txn_parsed_cnt-block->txn_sigverify_in_flight_cnt-block->txn_sigverify_done_cnt;
    3034           0 :   if( FD_LIKELY( sigverify_queued_cnt>0UL && fd_ulong_popcnt( sigverify_ready_bitset )>reserve ) ) {
    3035           0 :     dispatch_sigverify( sched, block, bank_idx, fd_ulong_find_lsb( sigverify_ready_bitset ), out );
    3036           0 :     sched->next_ready_last_tick     = sched->txn_info_pool[ out->txn_sigverify->txn_idx ].tick_sigverify_disp = fd_tickcount();
    3037           0 :     sched->next_ready_last_bank_idx = bank_idx;
    3038           0 :     return 1UL;
    3039           0 :   }
    3040             : 
    3041             :   /* Then footer certs and LtHash subtractions, under the hash batch
    3042             :      reserve. */
    3043           0 :   if( FD_UNLIKELY( cert_dispatch( sched, block, bank_idx, exec_fully_ready_bitset, lthash_reserve, out ) ) ) {
    3044           0 :     sched->next_ready_last_tick     = fd_tickcount();
    3045           0 :     sched->next_ready_last_bank_idx = bank_idx;
    3046           0 :     return 1UL;
    3047           0 :   }
    3048           0 :   if( FD_UNLIKELY( lthash_dispatch_sub( sched, block, bank_idx, exec_fully_ready_bitset, lthash_reserve, lthash_cap, out ) ) ) {
    3049           0 :     sched->next_ready_last_tick     = fd_tickcount();
    3050           0 :     sched->next_ready_last_bank_idx = bank_idx;
    3051           0 :     return 1UL;
    3052           0 :   }
    3053             :   /* Same as above, make some progress on a pending drain and poll once
    3054             :      more for what the chunk created. */
    3055           0 :   if( may_drain && lthash_drain_chunk( sched ) ) return task_next_ready( sched, out, 0 );
    3056             : 
    3057             :   /* Absolutely nothing else left to dispatch.  If the block is still
    3058             :      receiving FEC sets and owes additions, we will spend the idle tiles
    3059             :      on speculative additions.  Speculation is the lowest priority work
    3060             :      there is, though.  So if nothing of the block is in flight and
    3061             :      another lane head is ready with must-do work, yield. */
    3062           0 :   int may_speculate = block_may_speculate( sched, block, 1UL );
    3063           0 :   if( FD_LIKELY( may_speculate ) ) {
    3064           0 :     if( FD_UNLIKELY( !block_is_in_flight( block ) && other_lane_has_real_work( sched, block ) ) ) {
    3065           0 :       FD_LOG_DEBUG(( "reset active_bank_idx %lu: speculation yields", sched->active_bank_idx ));
    3066           0 :       sched->last_active_bank_idx = sched->active_bank_idx;
    3067           0 :       sched->active_bank_idx = ULONG_MAX;
    3068           0 :       sched->metrics->lthash_spec_yield_cnt++;
    3069           0 :       try_activate_block( sched );
    3070           0 :       return 0UL;
    3071           0 :     }
    3072           0 :     int spec = lthash_speculate( sched, block, bank_idx, exec_fully_ready_bitset, out );
    3073           0 :     if( FD_UNLIKELY( spec>=0 ) ) {
    3074           0 :       if( spec ) {
    3075           0 :         sched->next_ready_last_tick     = fd_tickcount();
    3076           0 :         sched->next_ready_last_bank_idx = bank_idx;
    3077           0 :       }
    3078           0 :       return (ulong)spec;
    3079           0 :     }
    3080           0 :   }
    3081             : 
    3082             :   /* A complete block may still have additions to hand to the
    3083             :      dispatcher, which we do a chunk at a time.  So there could be
    3084             :      moments when every task we handed out has come back, nothing is
    3085             :      running, and yet the block cannot end, because we still owe
    3086             :      additions.  Another poll will surface them.  Return empty handed,
    3087             :      since we know the block cannot end yet, and since the liveness
    3088             :      check at the end of this function would otherwise mistake a block
    3089             :      with unsurfaced additions for being stuck and abort. */
    3090           0 :   if( FD_UNLIKELY( block->lthash_draining || block->add_created_cnt>block->add_surfaced_cnt ) ) return 0UL;
    3091             : 
    3092           0 :   if( FD_UNLIKELY( block_should_signal_end( block ) ) ) {
    3093           0 :     FD_TEST( block->block_start_signaled );
    3094           0 :     if( FD_UNLIKELY( block->add_owed_cnt ) ) {
    3095           0 :       sched->print_buf_sz = 0UL;
    3096           0 :       print_all( sched, block );
    3097           0 :       FD_LOG_NOTICE(( "%s", sched->print_buf ));
    3098           0 :       FD_LOG_CRIT(( "invariant violation: block %lu:%lu ends owing %u additions", block->slot, bank_idx, block->add_owed_cnt ));
    3099           0 :     }
    3100           0 :     int tick_reason = verify_ticks_final( block );
    3101           0 :     if( FD_UNLIKELY( tick_reason!=FD_SCHED_DEAD_REASON_NONE ) ) {
    3102             :       /* Tick verification can't be done at parse time (except for
    3103             :          TRAILING_ENTRY), because we may not know the expected number of
    3104             :          hashes yet.  It can't be driven by transaction dispatch or
    3105             :          completion, because the block may be empty.  Similarly, it can't
    3106             :          be driven by PoH hashing, because a bad block may simply not
    3107             :          have any microblocks. */
    3108           0 :       handle_bad_block( sched, block, tick_reason );
    3109           0 :       out->task_type = FD_SCHED_TT_MARK_DEAD;
    3110           0 :       out->mark_dead->bank_idx = bank_idx;
    3111           0 :       sched->next_ready_last_tick     = fd_tickcount();
    3112           0 :       sched->next_ready_last_bank_idx = bank_idx;
    3113           0 :       return 1UL;
    3114           0 :     }
    3115           0 :     out->task_type = FD_SCHED_TT_BLOCK_END;
    3116           0 :     out->block_end->bank_idx = bank_idx;
    3117           0 :     block->block_end_signaled = 1;
    3118           0 :     FD_TEST( block->refcnt );
    3119           0 :     block->refcnt = 0;
    3120           0 :     FD_TEST( ref_q_avail( sched->ref_q ) );
    3121           0 :     ref_q_push_tail( sched->ref_q, bank_idx );
    3122           0 :     sched->next_ready_last_tick     = fd_tickcount();
    3123           0 :     sched->next_ready_last_bank_idx = bank_idx;
    3124           0 :     return 1UL;
    3125           0 :   }
    3126             : 
    3127             :   /* Nothing queued for the active block.  If we haven't received all
    3128             :      the FEC sets for it, then return and wait for more FEC sets, while
    3129             :      there are in-flight transactions.  This is a policy decision to
    3130             :      minimize fork churn and allow for executing down the current fork
    3131             :      as much as we can.  If we have received all the FEC sets for it,
    3132             :      then we'd still like to return and wait for the in-flight
    3133             :      transactions to retire, before switching to a different block.
    3134             : 
    3135             :      Either way, there should be in-flight transactions.  We deactivate
    3136             :      the active block the moment we exhausted transactions from it.
    3137             : 
    3138             :      We don't assert block_is_in_flight() here because there might be
    3139             :      in-flight tasks from dying blocks that are preventing us from
    3140             :      dispatching anything momentarily.  So we assert the global exec
    3141             :      tile busy count.  This doesn't lose too much assertion coverage
    3142             :      since dying blocks are few and far between. */
    3143           0 :   ulong total_exec_busy_cnt = sched->exec_cnt-(ulong)fd_ulong_popcnt( exec_fully_ready_bitset );
    3144           0 :   if( FD_UNLIKELY( !total_exec_busy_cnt ) ) {
    3145           0 :     sched->print_buf_sz = 0UL;
    3146           0 :     print_all( sched, block );
    3147           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
    3148           0 :     FD_LOG_CRIT(( "invariant violation: expected in-flight tasks but none found" ));
    3149           0 :   }
    3150             : 
    3151           0 :   return 0UL;
    3152           0 : }
    3153             : 
    3154             : ulong
    3155           0 : fd_sched_task_next_ready( fd_sched_t * sched, fd_sched_task_t * out ) {
    3156           0 :   ulong bank_idx = sched->active_bank_idx;
    3157           0 :   ulong ret      = task_next_ready( sched, out, 1 );
    3158           0 :   if( FD_LIKELY( bank_idx!=ULONG_MAX ) ) slot_state_eval( sched, block_pool_ele( sched, bank_idx ) );
    3159           0 :   return ret;
    3160           0 : }
    3161             : 
    3162             : int
    3163           0 : fd_sched_task_done( fd_sched_t * sched, ulong task_type, ulong txn_idx, ulong exec_idx, void * data ) {
    3164           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3165             : 
    3166           0 :   ulong bank_idx = ULONG_MAX;
    3167           0 :   switch( task_type ) {
    3168           0 :     case FD_SCHED_TT_BLOCK_START:
    3169           0 :     case FD_SCHED_TT_BLOCK_END: {
    3170           0 :       (void)txn_idx;
    3171           0 :       (void)data;
    3172           0 :       bank_idx = sched->active_bank_idx;
    3173           0 :       break;
    3174           0 :     }
    3175           0 :     case FD_SCHED_TT_TXN_EXEC:
    3176           0 :     case FD_SCHED_TT_TXN_SIGVERIFY: {
    3177           0 :       (void)data;
    3178           0 :       FD_TEST( txn_idx < sched->depth );
    3179           0 :       bank_idx = sched->tile_to_bank_idx[ exec_idx ];
    3180           0 :       break;
    3181           0 :     }
    3182           0 :     case FD_SCHED_TT_POH_HASH:
    3183           0 :     case FD_SCHED_TT_CERT_VERIFY:
    3184           0 :     case FD_SCHED_TT_REWARDS: {
    3185           0 :       (void)txn_idx;
    3186           0 :       bank_idx = sched->tile_to_bank_idx[ exec_idx ];
    3187           0 :       break;
    3188           0 :     }
    3189           0 :     case FD_SCHED_TT_LTHASH_SUB:
    3190           0 :     case FD_SCHED_TT_LTHASH_ADD: {
    3191             :       /* Like any other tile task, an LtHash task keeps its block in the
    3192             :          scheduler and the dispatcher until it lands.  block_is_in_flight
    3193             :          counts it, which gates abandon, demotion and pruning, and
    3194             :          BLOCK_END waits on it, so the block checks below hold for a
    3195             :          result of either kind. */
    3196           0 :       FD_TEST( exec_idx<sched->exec_cnt );
    3197           0 :       FD_TEST( data );
    3198           0 :       bank_idx = sched->tile_to_bank_idx[ exec_idx ];
    3199           0 :       break;
    3200           0 :     }
    3201           0 :     default: FD_LOG_CRIT(( "unsupported task_type %lu", task_type ));
    3202           0 :   }
    3203           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3204             : 
    3205           0 :   if( FD_UNLIKELY( !block->in_sched ) ) {
    3206           0 :     FD_LOG_CRIT(( "invariant violation: block->in_sched==0, block %lu:%lu, parent slot %lu",
    3207           0 :                   block->slot, bank_idx, block->parent_slot ));
    3208           0 :   }
    3209           0 :   if( FD_UNLIKELY( !block->staged ) ) {
    3210             :     /* Invariant: only staged blocks can have in-flight transactions. */
    3211           0 :     FD_LOG_CRIT(( "invariant violation: block->staged==0, block %lu:%lu, parent slot %lu",
    3212           0 :                   block->slot, bank_idx, block->parent_slot ));
    3213           0 :   }
    3214           0 :   if( FD_UNLIKELY( !block->in_rdisp ) ) {
    3215             :     /* Invariant: staged blocks must be in the dispatcher. */
    3216           0 :     FD_LOG_CRIT(( "invariant violation: block->in_rdisp==0, block %lu:%lu, parent slot %lu",
    3217           0 :                   block->slot, bank_idx, block->parent_slot ));
    3218           0 :   }
    3219             : 
    3220           0 :   block->txn_pool_max_popcnt   = fd_ulong_max( block->txn_pool_max_popcnt, sched->depth - sched->txn_pool_free_cnt - 1UL );
    3221           0 :   block->mblk_pool_max_popcnt  = fd_ulong_max( block->mblk_pool_max_popcnt, sched->depth - sched->mblk_pool_free_cnt );
    3222           0 :   block->block_pool_max_popcnt = fd_ulong_max( block->block_pool_max_popcnt, sched->block_pool_popcnt );
    3223             : 
    3224           0 :   int exec_tile_idx = (int)exec_idx;
    3225             : 
    3226           0 :   switch( task_type ) {
    3227           0 :     case FD_SCHED_TT_BLOCK_START: {
    3228           0 :       FD_TEST( !block->block_start_done );
    3229           0 :       block->block_start_done = 1;
    3230           0 :       if( FD_UNLIKELY( block->starts_at_update_parent && !block->flh_active ) ) {
    3231             :         /* Without the feature an initial UpdateParent is unexpected, as
    3232             :            agave rules it.  The UpdateParent was parsed at ingest, but
    3233             :            the feature is only known once BLOCK_START cloned the bank. */
    3234           0 :         FD_LOG_INFO(( "bad block: SPURIOUS_UPDATE_PARENT, slot %lu, parent slot %lu, started at its UpdateParent without alpenglow_fast_leader_handover", block->slot, block->parent_slot ));
    3235           0 :         handle_bad_block( sched, block, FD_SCHED_DEAD_REASON_SPURIOUS_UPDATE_PARENT );
    3236           0 :         return FD_SCHED_DEAD_REASON_SPURIOUS_UPDATE_PARENT;
    3237           0 :       }
    3238           0 :       break;
    3239           0 :     }
    3240           0 :     case FD_SCHED_TT_BLOCK_END: {
    3241             :       /* It may seem redundant to be invoking task_done() on these
    3242             :          somewhat fake tasks.  But these are necessary to drive state
    3243             :          transition for empty blocks or slow blocks. */
    3244           0 :       FD_TEST( !block->block_end_done );
    3245           0 :       block->block_end_done = 1;
    3246           0 :       if( FD_LIKELY( block_is_done( block ) ) ) slot_state_close( sched, block ); /* else on the last cert task done */
    3247           0 :       sched->print_buf_sz = 0UL;
    3248           0 :       print_block_metrics( sched, block );
    3249           0 :       FD_LOG_DEBUG(( "block %lu:%lu replayed fully: %s", block->slot, bank_idx, sched->print_buf ));
    3250           0 :       break;
    3251           0 :     }
    3252           0 :     case FD_SCHED_TT_TXN_EXEC: {
    3253           0 :       long now = fd_tickcount();
    3254           0 :       ulong delta = (ulong)(now-sched->txn_in_flight_last_tick);
    3255           0 :       ulong txn_exec_busy_cnt = sched->exec_cnt-(ulong)fd_ulong_popcnt( sched->txn_exec_ready_bitset[ 0 ] );
    3256           0 :       sched->metrics->txn_weighted_in_flight_tickcount += delta;
    3257           0 :       sched->metrics->txn_weighted_in_flight_cnt       += delta*txn_exec_busy_cnt;
    3258           0 :       sched->txn_in_flight_last_tick = now;
    3259             : 
    3260           0 :       sched->txn_info_pool[ txn_idx ].tick_exec_done = now;
    3261             : 
    3262           0 :       block->txn_exec_done_cnt++;
    3263           0 :       block->txn_exec_in_flight_cnt--;
    3264           0 :       FD_TEST( !fd_ulong_extract_bit( sched->txn_exec_ready_bitset[ 0 ], exec_tile_idx ) );
    3265           0 :       sched->txn_exec_ready_bitset[ 0 ] = fd_ulong_set_bit( sched->txn_exec_ready_bitset[ 0 ], exec_tile_idx );
    3266           0 :       sched->metrics->txn_exec_done_cnt++;
    3267           0 :       txn_bitset_insert( sched->exec_done_set, txn_idx );
    3268           0 :       sched->txn_info_pool[ txn_idx ].flags |= FD_SCHED_TXN_EXEC_DONE;
    3269           0 :       int dead_reason = FD_SCHED_DEAD_REASON_NONE;
    3270           0 :       if( FD_UNLIKELY( (sched->txn_info_pool[ txn_idx ].flags&FD_SCHED_TXN_ALT_UNRESOLVED) && data ) ) {
    3271           0 :         fd_sched_txn_alts_t const * alts = fd_type_pun_const( data );
    3272           0 :         if( FD_UNLIKELY( block->lthash_writable_cnt+alts->cnt>sched->lthash_txn_writers_max ) ) {
    3273           0 :           FD_LOG_INFO(( "bad block: TOO_MANY_WRITABLE_ACCOUNTS, slot %lu, parent slot %lu, txn_idx %lu, lthash_writable_cnt %u, late_reg_cnt %lu", block->slot, block->parent_slot, txn_idx, block->lthash_writable_cnt, alts->cnt ));
    3274           0 :           dead_reason = FD_SCHED_DEAD_REASON_TOO_MANY_WRITABLE_ACCOUNTS;
    3275           0 :         } else {
    3276           0 :           block->lthash_writable_cnt += (uint)alts->cnt;
    3277           0 :           if( FD_LIKELY( !block->lthash_inband ) ) {
    3278           0 :             for( ulong i=0UL; i<alts->cnt; i++ ) lthash_register( sched, block, alts->addrs+i );
    3279           0 :             sched->metrics->lthash_late_reg_cnt += (uint)alts->cnt;
    3280           0 :           }
    3281           0 :         }
    3282           0 :       }
    3283           0 :       if( txn_bitset_test( sched->sigverify_done_set, txn_idx ) && txn_bitset_test( sched->poh_mixin_done_set, txn_idx ) ) {
    3284             :         /* Release the txn_idx if all tasks on it are done.  This is
    3285             :            guaranteed to only happen once per transaction because
    3286             :            whichever one completed first would not release. */
    3287           0 :         if( FD_UNLIKELY( block->txn_idx_tail==(uint)txn_idx ) ) block->txn_idx_tail = 0U;
    3288           0 :         fd_rdisp_complete_txn( sched->rdisp, txn_idx, 1 );
    3289           0 :         sched->txn_pool_free_cnt++;
    3290           0 :         block->txn_done_cnt++;
    3291           0 :         sched->metrics->txn_done_cnt++;
    3292           0 :       } else {
    3293           0 :         fd_rdisp_complete_txn( sched->rdisp, txn_idx, 0 );
    3294           0 :       }
    3295           0 :       if( FD_UNLIKELY( dead_reason!=FD_SCHED_DEAD_REASON_NONE && !block->dying ) ) {
    3296           0 :         handle_bad_block( sched, block, dead_reason );
    3297           0 :         return dead_reason;
    3298           0 :       }
    3299           0 :       break;
    3300           0 :     }
    3301           0 :     case FD_SCHED_TT_TXN_SIGVERIFY: {
    3302           0 :       sched->txn_info_pool[ txn_idx ].tick_sigverify_done = fd_tickcount();
    3303           0 :       block->txn_sigverify_done_cnt++;
    3304           0 :       block->txn_sigverify_in_flight_cnt--;
    3305           0 :       FD_TEST( !fd_ulong_extract_bit( sched->sigverify_ready_bitset[ 0 ], exec_tile_idx ) );
    3306           0 :       sched->sigverify_ready_bitset[ 0 ] = fd_ulong_set_bit( sched->sigverify_ready_bitset[ 0 ], exec_tile_idx );
    3307           0 :       sched->metrics->txn_sigverify_done_cnt++;
    3308           0 :       txn_bitset_insert( sched->sigverify_done_set, txn_idx );
    3309           0 :       sched->txn_info_pool[ txn_idx ].flags |= FD_SCHED_TXN_SIGVERIFY_DONE;
    3310           0 :       if( txn_bitset_test( sched->exec_done_set, txn_idx ) && txn_bitset_test( sched->poh_mixin_done_set, txn_idx ) ) {
    3311             :         /* Release the txn_idx if all tasks on it are done.  This is
    3312             :            guaranteed to only happen once per transaction because
    3313             :            whichever one completed first would not release. */
    3314           0 :         if( FD_UNLIKELY( block->txn_idx_tail==(uint)txn_idx ) ) block->txn_idx_tail = 0U;
    3315           0 :         fd_rdisp_complete_txn( sched->rdisp, txn_idx, 1 );
    3316           0 :         sched->txn_pool_free_cnt++;
    3317           0 :         block->txn_done_cnt++;
    3318           0 :         sched->metrics->txn_done_cnt++;
    3319           0 :       }
    3320           0 :       break;
    3321           0 :     }
    3322           0 :     case FD_SCHED_TT_POH_HASH: {
    3323           0 :       block->poh_hashing_in_flight_cnt--;
    3324           0 :       FD_TEST( !fd_ulong_extract_bit( sched->poh_ready_bitset[ 0 ], exec_tile_idx ) );
    3325           0 :       sched->poh_ready_bitset[ 0 ] = fd_ulong_set_bit( sched->poh_ready_bitset[ 0 ], exec_tile_idx );
    3326           0 :       fd_execrp_poh_hash_done_msg_t * msg = fd_type_pun( data );
    3327           0 :       FD_TEST( msg->cnt && msg->cnt<=FD_EXECRP_POH_PARA );
    3328           0 :       fd_sched_poh_hash_t const * task = sched->poh_inflight+exec_tile_idx;
    3329           0 :       FD_TEST( msg->cnt==task->cnt );
    3330             : 
    3331             :       /* Once the block is found to be bad, the remaining elements are
    3332             :          freed without retiring. */
    3333           0 :       int dead_reason = FD_SCHED_DEAD_REASON_NONE;
    3334           0 :       for( ulong i=0UL; i<msg->cnt; i++ ) {
    3335           0 :         block->poh_mblk_in_flight_cnt--;
    3336           0 :         uint mblk_idx = (uint)task->mblk_idx[ i ];
    3337           0 :         fd_sched_mblk_t * mblk = sched->mblk_pool+mblk_idx;
    3338           0 :         memcpy( mblk->curr_hash, msg->hash+i, sizeof(fd_hash_t) );
    3339           0 :         if( FD_UNLIKELY( dead_reason!=FD_SCHED_DEAD_REASON_NONE ) ) {
    3340           0 :           free_mblk( sched, block, mblk_idx );
    3341           0 :           continue;
    3342           0 :         }
    3343           0 :         dead_reason = poh_retire_mblk( sched, block, mblk_idx );
    3344           0 :       }
    3345           0 :       if( FD_LIKELY( dead_reason==FD_SCHED_DEAD_REASON_NONE ) ) dead_reason = verify_ticks_eager( block );
    3346           0 :       if( FD_UNLIKELY( dead_reason!=FD_SCHED_DEAD_REASON_NONE ) ) {
    3347           0 :         handle_bad_block( sched, block, dead_reason );
    3348           0 :         return dead_reason;
    3349           0 :       }
    3350           0 :       break;
    3351           0 :     }
    3352           0 :     case FD_SCHED_TT_CERT_VERIFY:
    3353           0 :     case FD_SCHED_TT_REWARDS: {
    3354           0 :       FD_TEST( block->footer_in_flight_cnt );
    3355           0 :       block->footer_in_flight_cnt--;
    3356           0 :       FD_TEST( !fd_ulong_extract_bit( sched->footer_ready_bitset[ 0 ], exec_tile_idx ) );
    3357           0 :       sched->footer_ready_bitset[ 0 ] = fd_ulong_set_bit( sched->footer_ready_bitset[ 0 ], exec_tile_idx );
    3358           0 :       if( FD_UNLIKELY( block_is_done( block ) ) ) slot_state_close( sched, block );
    3359           0 :       break;
    3360           0 :     }
    3361           0 :     case FD_SCHED_TT_LTHASH_SUB: {
    3362             :       /* A subtraction is never invalidated.  The parent is frozen, so
    3363             :          the value does not depend on when it ran. */
    3364           0 :       fd_sched_lthash_batch_t const * rec = sched->lthash_inflight+exec_tile_idx;
    3365           0 :       FD_TEST( rec->kind==FD_SCHED_TT_LTHASH_SUB && rec->bank_idx==bank_idx && rec->cnt && rec->cnt<=FD_SCHED_LTHASH_BATCH_MAX );
    3366           0 :       for( ulong i=0UL; i<rec->cnt; i++ ) {
    3367           0 :         fd_sched_lthash_entry_t * e = sched->lthash_lane[ rec->lane ].entry_pool+rec->entry_idx[ i ];
    3368           0 :         FD_TEST( e->sub_state==FD_SCHED_LTHASH_SUB_DISPATCHED && e->bank_idx==(uint)bank_idx );
    3369           0 :         e->sub_state = (uchar)FD_SCHED_LTHASH_SUB_DONE;
    3370           0 :       }
    3371           0 :       fd_lthash_sub( block_lthash_delta( sched, block ), fd_type_pun_const( data ) );
    3372           0 :       block->sub_in_flight_cnt -= (uint)rec->cnt;
    3373           0 :       block->sub_applied_cnt   += (uint)rec->cnt;
    3374           0 :       block->lthash_in_flight_cnt--;
    3375           0 :       sched->metrics->lthash_sub_applied_cnt += rec->cnt;
    3376           0 :       FD_TEST( !fd_ulong_extract_bit( sched->lthash_ready_bitset[ 0 ], exec_tile_idx ) );
    3377           0 :       sched->lthash_ready_bitset[ 0 ] = fd_ulong_set_bit( sched->lthash_ready_bitset[ 0 ], exec_tile_idx );
    3378           0 :       break;
    3379           0 :     }
    3380           0 :     case FD_SCHED_TT_LTHASH_ADD: {
    3381           0 :       fd_sched_lthash_batch_t const * rec = sched->lthash_inflight+exec_tile_idx;
    3382           0 :       FD_TEST( rec->kind==FD_SCHED_TT_LTHASH_ADD && rec->bank_idx==bank_idx && rec->cnt && rec->cnt<=FD_SCHED_LTHASH_BATCH_MAX );
    3383             :       /* Entries live until the lane is reset, so the record's entries
    3384             :          are still these accounts'. */
    3385           0 :       ulong stale_cnt = 0UL;
    3386           0 :       for( ulong i=0UL; i<rec->cnt; i++ ) {
    3387           0 :         fd_sched_lthash_entry_t * e = sched->lthash_lane[ rec->lane ].entry_pool+rec->entry_idx[ i ];
    3388           0 :         FD_TEST( e->bank_idx==(uint)bank_idx );
    3389             :         /* A later write to the account was observed while the batch
    3390             :            was on the tile, so its value is not final.  The rewrite
    3391             :            freed its speculation entry, if any. */
    3392           0 :         stale_cnt += (ulong)(rec->ptxn_idx[ i ]!=e->ptxn_idx);
    3393           0 :       }
    3394           0 :       if( FD_UNLIKELY( stale_cnt ) ) {
    3395             :         /* Stale hash.  Drop it. */
    3396           0 :         if( FD_UNLIKELY( rec->cnt>1UL ) ) {
    3397             :           /* We're given one sum for a whole batch, so a stale entry
    3398             :              spoils the batch.  We don't need to handle this, because
    3399             :              only a speculative addition can be stale after it has
    3400             :              surfaced, and a speculative addition goes out alone.  See
    3401             :              the account state machine.  So a stale batch is an
    3402             :              invariant violation.  A stale non-speculative batch of one
    3403             :              account is silently dropped here, though it ought to be an
    3404             :              invariant violation too. */
    3405           0 :           sched->print_buf_sz = 0UL;
    3406           0 :           print_all( sched, block );
    3407           0 :           FD_LOG_NOTICE(( "%s", sched->print_buf ));
    3408           0 :           FD_LOG_CRIT(( "invariant violation: %lu of %lu batched additions of block %lu:%lu were rewritten on the tile", stale_cnt, rec->cnt, block->slot, bank_idx ));
    3409           0 :         }
    3410           0 :         sched->metrics->lthash_add_dispatched_stale_cnt++;
    3411           0 :       } else {
    3412             :         /* Good hash.  Fold it in. */
    3413           0 :         fd_lthash_add( block_lthash_delta( sched, block ), fd_type_pun_const( data ) );
    3414           0 :         for( ulong i=0UL; i<rec->cnt; i++ ) {
    3415           0 :           fd_sched_lthash_entry_t * e = sched->lthash_lane[ rec->lane ].entry_pool+rec->entry_idx[ i ];
    3416           0 :           FD_TEST( e->add_state==FD_SCHED_LTHASH_ADD_DISPATCHED );
    3417           0 :           if( e->spec_idx!=UINT_MAX ) {
    3418             :             /* A speculative addition is always sent individually. */
    3419           0 :             FD_TEST( rec->cnt==1UL );
    3420           0 :             fd_memcpy( sched->lthash_spec_pool+e->spec_idx, data, sizeof(fd_lthash_value_t) );
    3421           0 :             sched->metrics->lthash_spec_applied_cnt++;
    3422           0 :           }
    3423           0 :           e->add_state = (uchar)FD_SCHED_LTHASH_ADD_DONE;
    3424           0 :           e->ptxn_idx  = 0U;
    3425           0 :         }
    3426           0 :         block->add_applied_cnt += (uint)rec->cnt;
    3427           0 :         sched->metrics->lthash_add_applied_cnt += rec->cnt;
    3428           0 :       }
    3429           0 :       block->add_done_cnt += (uint)rec->cnt;
    3430           0 :       for( ulong i=0UL; i<rec->cnt; i++ ) fd_rdisp_complete_txn( sched->rdisp, (ulong)rec->ptxn_idx[ i ], 1 );
    3431           0 :       block->lthash_in_flight_cnt--;
    3432           0 :       FD_TEST( !fd_ulong_extract_bit( sched->lthash_ready_bitset[ 0 ], exec_tile_idx ) );
    3433           0 :       sched->lthash_ready_bitset[ 0 ] = fd_ulong_set_bit( sched->lthash_ready_bitset[ 0 ], exec_tile_idx );
    3434           0 :       break;
    3435           0 :     }
    3436           0 :   }
    3437             : 
    3438           0 :   if( FD_UNLIKELY( block->dying && !block_is_in_flight( block ) ) ) {
    3439           0 :     if( FD_UNLIKELY( sched->active_bank_idx==bank_idx ) ) {
    3440           0 :       FD_LOG_CRIT(( "invariant violation: active block %lu:%lu shouldn't be dying, parent slot %lu",
    3441           0 :                     block->slot, bank_idx, block->parent_slot ));
    3442           0 :     }
    3443           0 :     FD_LOG_DEBUG(( "dying block %lu:%lu drained", block->slot, bank_idx ));
    3444           0 :     subtree_abandon( sched, block );
    3445           0 :     try_activate_block( sched );
    3446           0 :     return 0;
    3447           0 :   }
    3448             : 
    3449           0 :   slot_state_eval( sched, block );
    3450             : 
    3451           0 :   if( FD_UNLIKELY( !block->dying && sched->active_bank_idx!=bank_idx ) ) {
    3452             :     /* Block is not dead.  So we should be actively replaying it. */
    3453           0 :     fd_sched_block_t * active_block = block_pool_ele( sched, sched->active_bank_idx );
    3454           0 :     FD_LOG_CRIT(( "invariant violation: sched->active_bank_idx %lu, slot %lu, parent slot %lu, bank_idx %lu, slot %lu, parent slot %lu",
    3455           0 :                   sched->active_bank_idx, active_block->slot, active_block->parent_slot,
    3456           0 :                   bank_idx, block->slot, block->parent_slot ));
    3457           0 :   }
    3458             : 
    3459           0 :   maybe_switch_block( sched, bank_idx );
    3460             : 
    3461           0 :   return 0;
    3462           0 : }
    3463             : 
    3464             : void
    3465           0 : fd_sched_block_abandon( fd_sched_t * sched, ulong bank_idx, int cause ) {
    3466           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3467           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    3468             : 
    3469           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3470           0 :   if( FD_UNLIKELY( !block->in_sched ) ) {
    3471           0 :     FD_LOG_CRIT(( "invariant violation: block->in_sched==0, block %lu:%lu, parent slot %lu",
    3472           0 :                   block->slot, bank_idx, block->parent_slot ));
    3473           0 :   }
    3474             : 
    3475           0 :   if( FD_LIKELY( cause==FD_SCHED_ABANDON_DISCARDED && !block->dying ) ) block->discarded = 1;
    3476             : 
    3477           0 :   FD_LOG_INFO(( "abandoning block %lu:%lu", block->slot, bank_idx ));
    3478           0 :   sched->print_buf_sz = 0UL;
    3479           0 :   print_all( sched, block );
    3480           0 :   FD_LOG_DEBUG(( "%s", sched->print_buf ));
    3481             : 
    3482           0 :   subtree_abandon( sched, block );
    3483           0 :   try_activate_block( sched );
    3484           0 : }
    3485             : 
    3486             : int
    3487           0 : fd_sched_get_dead_reason( fd_sched_t * sched, ulong bank_idx ) {
    3488           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3489           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    3490           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3491           0 :   if( FD_UNLIKELY( !block->in_sched ) ) return FD_SCHED_DEAD_REASON_NONE;
    3492           0 :   return block->dead_reason;
    3493           0 : }
    3494             : 
    3495             : int
    3496           0 : fd_sched_block_is_discarded( fd_sched_t * sched, ulong bank_idx ) {
    3497           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3498           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    3499           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3500           0 :   if( FD_UNLIKELY( !block->in_sched ) ) return 0;
    3501           0 :   return (int)block->discarded;
    3502           0 : }
    3503             : 
    3504             : void
    3505           0 : fd_sched_cancel( fd_sched_t * sched, ulong bank_idx ) {
    3506           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3507           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    3508             : 
    3509           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3510           0 :   if( FD_UNLIKELY( !block->in_sched ) ) return;
    3511           0 :   FD_LOG_INFO(( "canceling subtree at block %lu:%lu", block->slot, bank_idx ));
    3512           0 :   fd_sched_block_t * parent = block_pool_ele( sched, block->parent_idx );
    3513           0 :   if( FD_LIKELY( parent ) ) {
    3514             :     /* Splice the block out of its parent's children list. */
    3515           0 :     ulong block_idx = block_to_idx( sched, block );
    3516           0 :     ulong * idx_p = &parent->child_idx;
    3517           0 :     while( *idx_p!=block_idx ) {
    3518           0 :       idx_p = &(block_pool_ele( sched, *idx_p )->sibling_idx);
    3519           0 :     }
    3520           0 :     *idx_p = block->sibling_idx;
    3521           0 :   }
    3522           0 :   subtree_prune( sched, bank_idx, ULONG_MAX );
    3523           0 : }
    3524             : 
    3525             : void
    3526        1008 : fd_sched_block_add_done( fd_sched_t * sched, ulong bank_idx, ulong parent_bank_idx, ulong slot ) {
    3527        1008 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3528        1008 :   FD_TEST( bank_idx<sched->block_cnt_max );
    3529             : 
    3530        1008 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3531        1008 :   add_block( sched, bank_idx, parent_bank_idx );
    3532        1008 :   block->slot                   = slot;
    3533        1008 :   block->fec_eos                = 1;
    3534        1008 :   block->block_start_signaled   = 1;
    3535        1008 :   block->block_end_signaled     = 1;
    3536        1008 :   block->block_start_done       = 1;
    3537        1008 :   block->block_end_done         = 1;
    3538        1008 :   block->refcnt                 = 0;
    3539        1008 :   if( FD_LIKELY( parent_bank_idx!=ULONG_MAX ) ) {
    3540           0 :     fd_sched_block_t * parent_block = block_pool_ele( sched, parent_bank_idx );
    3541           0 :     block->parent_slot = parent_block->slot;
    3542           0 :   }
    3543        1008 :   if( FD_UNLIKELY( parent_bank_idx==ULONG_MAX ) ) {
    3544             :     /* Assumes that a NULL parent implies the snapshot slot. */
    3545        1008 :     block->parent_slot = ULONG_MAX;
    3546        1008 :     block->rooted      = 1;
    3547        1008 :     sched->root_idx    = bank_idx;
    3548        1008 :   }
    3549        1008 : }
    3550             : 
    3551             : void
    3552        1008 : fd_sched_advance_root( fd_sched_t * sched, ulong root_idx ) {
    3553        1008 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3554        1008 :   FD_TEST( root_idx<sched->block_cnt_max );
    3555        1008 :   FD_TEST( sched->root_idx<sched->block_cnt_max );
    3556        1008 :   FD_TEST( ref_q_empty( sched->ref_q ) );
    3557             : 
    3558        1008 :   fd_sched_block_t * new_root = block_pool_ele( sched, root_idx );
    3559        1008 :   fd_sched_block_t * old_root = block_pool_ele( sched, sched->root_idx );
    3560        1008 :   if( FD_UNLIKELY( !old_root->rooted ) ) {
    3561           0 :     FD_LOG_CRIT(( "invariant violation: old_root is not rooted, slot %lu, parent slot %lu",
    3562           0 :                   old_root->slot, old_root->parent_slot ));
    3563           0 :   }
    3564             : 
    3565             :   /* Early exit if the new root is the same as the old root. */
    3566        1008 :   if( FD_UNLIKELY( root_idx==sched->root_idx ) ) {
    3567        1008 :     FD_LOG_INFO(( "new root is the same as the old root, slot %lu, parent slot %lu",
    3568        1008 :                   new_root->slot, new_root->parent_slot ));
    3569        1008 :     return;
    3570        1008 :   }
    3571             : 
    3572           0 :   subtree_prune( sched, sched->root_idx, root_idx );
    3573             : 
    3574           0 :   new_root->parent_idx = ULONG_MAX;
    3575           0 :   sched->root_idx = root_idx;
    3576           0 : }
    3577             : 
    3578             : void
    3579        1008 : fd_sched_root_notify( fd_sched_t * sched, ulong root_idx ) {
    3580        1008 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3581        1008 :   FD_TEST( root_idx<sched->block_cnt_max );
    3582        1008 :   FD_TEST( sched->root_idx<sched->block_cnt_max );
    3583        1008 :   FD_TEST( ref_q_empty( sched->ref_q ) );
    3584             : 
    3585        1008 :   fd_sched_block_t * block    = block_pool_ele( sched, root_idx );
    3586        1008 :   fd_sched_block_t * old_root = block_pool_ele( sched, sched->root_idx );
    3587        1008 :   if( FD_UNLIKELY( !old_root->rooted ) ) {
    3588           0 :     FD_LOG_CRIT(( "invariant violation: old_root is not rooted, slot %lu, parent slot %lu",
    3589           0 :                   old_root->slot, old_root->parent_slot ));
    3590           0 :   }
    3591             : 
    3592             :   /* Early exit if the new root is the same as the old root. */
    3593        1008 :   if( FD_UNLIKELY( root_idx==sched->root_idx ) ) {
    3594        1008 :     FD_LOG_INFO(( "new root is the same as the old root, slot %lu, parent slot %lu",
    3595        1008 :                   block->slot, block->parent_slot ));
    3596        1008 :     return;
    3597        1008 :   }
    3598             : 
    3599             :   /* Mark every node from the new root up through its parents to the
    3600             :      old root as being rooted. */
    3601           0 :   fd_sched_block_t * curr = block;
    3602           0 :   fd_sched_block_t * prev = NULL;
    3603           0 :   while( curr ) {
    3604           0 :     if( FD_UNLIKELY( !block_is_done( curr ) ) ) {
    3605           0 :       FD_LOG_CRIT(( "invariant violation: rooting a block that is not done, slot %lu, parent slot %lu",
    3606           0 :                     curr->slot, curr->parent_slot ));
    3607           0 :     }
    3608           0 :     if( FD_UNLIKELY( curr->dying ) ) {
    3609           0 :       FD_LOG_CRIT(( "invariant violation: rooting a block that is dying, slot %lu, parent slot %lu",
    3610           0 :                     curr->slot, curr->parent_slot ));
    3611           0 :     }
    3612           0 :     if( FD_UNLIKELY( curr->staged ) ) {
    3613           0 :       FD_LOG_CRIT(( "invariant violation: rooting a block that is staged, slot %lu, parent slot %lu",
    3614           0 :                     curr->slot, curr->parent_slot ));
    3615           0 :     }
    3616           0 :     if( FD_UNLIKELY( curr->in_rdisp ) ) {
    3617           0 :       FD_LOG_CRIT(( "invariant violation: rooting a block that is in the dispatcher, slot %lu, parent slot %lu",
    3618           0 :                     curr->slot, curr->parent_slot ));
    3619           0 :     }
    3620           0 :     curr->rooted = 1;
    3621           0 :     prev = curr;
    3622           0 :     curr = block_pool_ele( sched, curr->parent_idx );
    3623           0 :   }
    3624             : 
    3625             :   /* If we didn't reach the old root, the new root is not a descendant. */
    3626           0 :   if( FD_UNLIKELY( prev!=old_root ) ) {
    3627           0 :     FD_LOG_CRIT(( "invariant violation: new root is not a descendant of old root, new root slot %lu, parent slot %lu, old root slot %lu, parent slot %lu",
    3628           0 :                   block->slot, block->parent_slot, old_root->slot, old_root->parent_slot ));
    3629           0 :   }
    3630             : 
    3631           0 :   ulong old_active_bank_idx    = sched->active_bank_idx;
    3632           0 :   uint  old_deactivate_abandon = sched->metrics->deactivate_abandoned_cnt;
    3633             : 
    3634             :   /* Now traverse from old root towards new root, and abandon all
    3635             :      minority forks. */
    3636           0 :   curr = old_root;
    3637           0 :   while( curr && curr->rooted && curr!=block ) { /* curr!=block to avoid abandoning good forks. */
    3638           0 :     fd_sched_block_t * rooted_child_block = NULL;
    3639           0 :     ulong              child_idx          = curr->child_idx;
    3640           0 :     while( child_idx!=ULONG_MAX ) {
    3641           0 :       fd_sched_block_t * child = block_pool_ele( sched, child_idx );
    3642           0 :       child_idx = child->sibling_idx;
    3643           0 :       if( child->rooted ) {
    3644           0 :         rooted_child_block = child;
    3645           0 :       } else {
    3646             :         /* This is a minority fork.  Consensus converged elsewhere, so
    3647             :            the fork is discarded rather than invalid.  Mark the subtree
    3648             :            root as discarded so subtree_abandon() propagates the flag
    3649             :            down.
    3650             : 
    3651             :            Gated on the block not already going down.  A block that is
    3652             :            already dying was abandoned for a reason of its own, either
    3653             :            sched's own verdict or runtime's verdict on its validity, and
    3654             :            losing the fork choice race afterwards must not re-label it
    3655             :            discarded. */
    3656           0 :         if( FD_LIKELY( !child->dying ) ) child->discarded = 1;
    3657             : 
    3658           0 :         ulong abandoned_cnt = sched->metrics->block_abandoned_cnt;
    3659           0 :         subtree_abandon( sched, child );
    3660           0 :         abandoned_cnt = sched->metrics->block_abandoned_cnt-abandoned_cnt;
    3661           0 :         if( FD_UNLIKELY( abandoned_cnt ) ) FD_LOG_DEBUG(( "abandoned %lu blocks on minority fork starting at block %lu:%lu", abandoned_cnt, child->slot, block_to_idx( sched, child ) ));
    3662           0 :       }
    3663           0 :     }
    3664           0 :     curr = rooted_child_block;
    3665           0 :   }
    3666             : 
    3667             :   /* If the active block got abandoned, we need to reset it. */
    3668           0 :   if( sched->active_bank_idx==ULONG_MAX ) {
    3669             :     /* Count a minority-fork deactivation once, as MinorityFork only */
    3670           0 :     sched->metrics->deactivate_abandoned_cnt  = old_deactivate_abandon;
    3671           0 :     sched->metrics->deactivate_pruned_cnt    += fd_uint_if( old_active_bank_idx!=ULONG_MAX, 1U, 0U );
    3672           0 :     try_activate_block( sched );
    3673           0 :   }
    3674           0 : }
    3675             : 
    3676             : ulong
    3677           0 : fd_sched_slot_states_drain( fd_sched_t * sched, fd_sched_slot_state_t * out ) {
    3678           0 :   ulong cnt = sched->state_cnt;
    3679           0 :   if( FD_UNLIKELY( sched->state_drop_cnt ) ) {
    3680           0 :     FD_LOG_WARNING(( "dropped %lu slot state intervals", sched->state_drop_cnt ));
    3681           0 :     sched->state_drop_cnt = 0UL;
    3682           0 :   }
    3683           0 :   fd_memcpy( out, sched->state_buf, cnt*sizeof(fd_sched_slot_state_t) );
    3684           0 :   sched->state_cnt = 0UL;
    3685           0 :   return cnt;
    3686           0 : }
    3687             : 
    3688             : ulong
    3689        4557 : fd_sched_pruned_block_next( fd_sched_t * sched ) {
    3690        4557 :   if( !ref_q_empty( sched->ref_q ) ) {
    3691           0 :     ulong bank_idx = ref_q_pop_head( sched->ref_q );
    3692           0 :     return bank_idx;
    3693           0 :   }
    3694        4557 :   return ULONG_MAX;
    3695        4557 : }
    3696             : 
    3697             : void
    3698           0 : fd_sched_set_poh_params( fd_sched_t * sched, ulong bank_idx, ulong tick_height, ulong max_tick_height, fd_hash_t const * start_poh ) {
    3699           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3700           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    3701           0 :   FD_TEST( max_tick_height>tick_height );
    3702           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3703           0 :   block->tick_height = tick_height;
    3704           0 :   block->max_tick_height = max_tick_height;
    3705             :   #if FD_SCHED_SKIP_POH
    3706             :   /* No-op. */
    3707             :   (void)start_poh;
    3708             :   #else
    3709           0 :   if( FD_LIKELY( block->mblk_cnt ) ) {
    3710             :     /* Fix up the first mblk's curr_hash. */
    3711           0 :     FD_TEST( block->mblk_unhashed_cnt );
    3712           0 :     FD_TEST( !mblk_slist_is_empty( block->mblks_unhashed, sched->mblk_pool ) );
    3713           0 :     FD_TEST( !block->mblk_freed_cnt );
    3714           0 :     fd_sched_mblk_t * first_mblk = sched->mblk_pool + mblk_slist_idx_peek_head( block->mblks_unhashed, sched->mblk_pool );
    3715           0 :     memcpy( first_mblk->curr_hash, start_poh, sizeof(fd_hash_t) );
    3716           0 :   } else {
    3717           0 :     memcpy( block->poh_hash, start_poh, sizeof(fd_hash_t) );
    3718           0 :   }
    3719           0 :   #endif
    3720           0 : }
    3721             : 
    3722             : void
    3723           0 : fd_sched_set_flh_active( fd_sched_t * sched, ulong bank_idx, int flh_active ) {
    3724           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3725           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    3726           0 :   block_pool_ele( sched, bank_idx )->flh_active = !!flh_active;
    3727           0 : }
    3728             : 
    3729             : void
    3730        1008 : fd_sched_set_bypass_poh_verify( fd_sched_t * sched, int bypass_poh_verify ) {
    3731        1008 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3732        1008 :   sched->bypass_poh_verify = !!bypass_poh_verify;
    3733        1008 : }
    3734             : 
    3735             : void
    3736        1008 : fd_sched_set_bypass_alut_resolution( fd_sched_t * sched, int bypass_alut_resolution ) {
    3737        1008 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3738        1008 :   sched->bypass_alut_resolution = !!bypass_alut_resolution;
    3739        1008 : }
    3740             : 
    3741             : void
    3742           0 : fd_sched_set_lthash_txn_writers_max( fd_sched_t * sched, ulong lthash_txn_writers_max ) {
    3743           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3744           0 :   FD_TEST( lthash_txn_writers_max<=FD_SCHED_LTHASH_TXN_WRITERS_MAX );
    3745           0 :   sched->lthash_txn_writers_max = lthash_txn_writers_max;
    3746           0 : }
    3747             : 
    3748             : void
    3749           0 : fd_sched_set_lthash_oob_block_max( fd_sched_t * sched, ulong lthash_oob_block_max ) {
    3750           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3751           0 :   FD_TEST( lthash_oob_block_max<=FD_SCHED_LTHASH_OOB_BLOCK_MAX );
    3752           0 :   sched->lthash_oob_block_max = lthash_oob_block_max;
    3753           0 : }
    3754             : 
    3755             : fd_txn_p_t *
    3756           0 : fd_sched_get_txn( fd_sched_t * sched, ulong txn_idx ) {
    3757           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3758           0 :   if( FD_UNLIKELY( txn_idx>=sched->depth ) ) {
    3759           0 :     return NULL;
    3760           0 :   }
    3761           0 :   return sched->txn_pool+txn_idx;
    3762           0 : }
    3763             : 
    3764             : fd_sched_txn_info_t *
    3765           0 : fd_sched_get_txn_info( fd_sched_t * sched, ulong txn_idx ) {
    3766           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3767           0 :   if( FD_UNLIKELY( txn_idx>=sched->depth ) ) {
    3768           0 :     return NULL;
    3769           0 :   }
    3770           0 :   return sched->txn_info_pool+txn_idx;
    3771           0 : }
    3772             : 
    3773             : fd_hash_t *
    3774           0 : fd_sched_get_poh( fd_sched_t * sched, ulong bank_idx ) {
    3775           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3776           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    3777           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3778           0 :   FD_TEST( block->fec_eos );
    3779           0 :   FD_TEST( block->mblk_cnt );
    3780           0 :   return block->poh_hash;
    3781           0 : }
    3782             : 
    3783             : uint
    3784           0 : fd_sched_get_shred_cnt( fd_sched_t * sched, ulong bank_idx ) {
    3785           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3786           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    3787           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3788           0 :   return block->shred_cnt;
    3789           0 : }
    3790             : 
    3791             : fd_block_footer_t const *
    3792           0 : fd_sched_get_footer( fd_sched_t * sched, ulong bank_idx ) {
    3793           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3794           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    3795           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3796           0 :   return block->footer_seen ? &block->footer : NULL;
    3797           0 : }
    3798             : 
    3799             : fd_lthash_value_t const *
    3800           0 : fd_sched_lthash_delta( fd_sched_t * sched, ulong bank_idx ) {
    3801           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3802           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    3803           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3804             :   /* The delta accumulates from the block's first LtHash task on and is
    3805             :      final once BLOCK_END is signaled.  Completing BLOCK_END zeroes it. */
    3806           0 :   FD_TEST( block->in_sched && !block->block_end_done );
    3807           0 :   return block_lthash_delta( sched, block );
    3808           0 : }
    3809             : 
    3810             : void
    3811           0 : fd_sched_block_start_writes( fd_sched_t * sched, ulong bank_idx, fd_acct_addr_t const * addrs, ulong cnt ) {
    3812           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    3813           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    3814           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3815           0 :   FD_TEST( block->in_sched && block->staged && block->in_rdisp );
    3816           0 :   FD_TEST( !block->lthash_inband );
    3817             :   /* We expect this to run during BLOCK_START processing, before any
    3818             :      real transaction of the block is dispatched. */
    3819           0 :   FD_TEST( block->block_start_signaled && !block->block_start_done );
    3820           0 :   FD_TEST( !block->txn_exec_in_flight_cnt && !block->txn_exec_done_cnt );
    3821             :   /* Exceeding the start-of-block writable account budget is a bug, not
    3822             :      an invalid block. */
    3823           0 :   if( FD_UNLIKELY( cnt>FD_SCHED_LTHASH_START_WRITES_MAX ) ) FD_LOG_CRIT(( "start-of-block processing wrote %lu accounts, over the budget of %lu, block %lu:%lu", cnt, FD_SCHED_LTHASH_START_WRITES_MAX, block->slot, bank_idx ));
    3824           0 :   for( ulong i=0UL; i<cnt; i++ ) lthash_register( sched, block, addrs+i );
    3825           0 :   sched->metrics->lthash_start_reg_cnt += (uint)cnt;
    3826           0 : }
    3827             : 
    3828             : void
    3829             : fd_sched_metrics_write( fd_sched_t *            sched,
    3830           0 :                         fd_clock_tile_t const * clock ) {
    3831           0 :   FD_MGAUGE_SET( REPLAY, SCHED_ACTIVE_BANK_INDEX, sched->active_bank_idx );
    3832           0 :   FD_MGAUGE_SET( REPLAY, SCHED_LAST_DISPATCH_BANK_INDEX, sched->next_ready_last_bank_idx );
    3833           0 :   FD_MGAUGE_SET( REPLAY, SCHED_LAST_DISPATCH_TIME, sched->next_ready_last_tick==LONG_MAX ? 0UL : (ulong)fd_clock_tile_tickcount_to_wallclock( clock, sched->next_ready_last_tick ) );
    3834           0 :   FD_MGAUGE_SET( REPLAY, SCHED_STAGING_LANE_USED, (ulong)fd_ulong_popcnt( sched->staged_bitset ) );
    3835           0 :   FD_MGAUGE_SET( REPLAY, SCHED_STAGING_LANE_PEAK, sched->staged_popcnt_wmk );
    3836           0 :   ulong staging_lane_head_bank_idx[ FD_METRICS_ENUM_STAGING_LANE_CNT ];
    3837           0 :   for( ulong lane=0UL; lane<FD_METRICS_ENUM_STAGING_LANE_CNT; lane++ ) {
    3838           0 :     staging_lane_head_bank_idx[ lane ] = fd_ulong_if( fd_ulong_extract_bit( sched->staged_bitset, (int)lane ), sched->staged_head_bank_idx[ lane ], ULONG_MAX );
    3839           0 :   }
    3840           0 :   FD_MGAUGE_ENUM_COPY( REPLAY, SCHED_STAGING_LANE_HEAD_BANK_INDEX, staging_lane_head_bank_idx );
    3841           0 :   FD_MGAUGE_SET( REPLAY, SCHED_TXN_POOL_USED, sched->depth-sched->txn_pool_free_cnt-1UL );
    3842           0 :   FD_MGAUGE_SET( REPLAY, SCHED_TXN_POOL_CAPACITY, sched->depth-1UL );
    3843           0 :   FD_MGAUGE_SET( REPLAY, SCHED_MICROBLOCK_POOL_USED, sched->depth-sched->mblk_pool_free_cnt );
    3844           0 :   FD_MGAUGE_SET( REPLAY, SCHED_MICROBLOCK_POOL_CAPACITY, sched->depth );
    3845           0 :   FD_MGAUGE_SET( REPLAY, SCHED_BLOCK_POOL_USED, sched->block_pool_popcnt );
    3846           0 :   FD_MGAUGE_SET( REPLAY, SCHED_BLOCK_POOL_CAPACITY, sched->block_cnt_max );
    3847             : 
    3848           0 :   fd_sched_metrics_t const * m = sched->metrics;
    3849             : 
    3850           0 :   ulong sched_block_added[ FD_METRICS_ENUM_SCHED_BLOCK_STAGING_STATE_CNT ];
    3851           0 :   sched_block_added[ FD_METRICS_ENUM_SCHED_BLOCK_STAGING_STATE_V_STAGED_IDX   ] = m->block_added_staged_cnt;
    3852           0 :   sched_block_added[ FD_METRICS_ENUM_SCHED_BLOCK_STAGING_STATE_V_UNSTAGED_IDX ] = m->block_added_unstaged_cnt;
    3853           0 :   sched_block_added[ FD_METRICS_ENUM_SCHED_BLOCK_STAGING_STATE_V_DEAD_IDX     ] = m->block_added_dead_ood_cnt;
    3854           0 :   FD_MCNT_ENUM_COPY( REPLAY, SCHED_BLOCK_ADDED, sched_block_added );
    3855           0 :   FD_MCNT_SET( REPLAY, SCHED_BLOCK_REPLAYED, m->block_removed_cnt );
    3856           0 :   FD_MCNT_SET( REPLAY, SCHED_BLOCK_ABANDONED, m->block_abandoned_cnt );
    3857           0 :   FD_MCNT_SET( REPLAY, SCHED_BLOCK_REJECTED, m->block_bad_cnt );
    3858           0 :   FD_MCNT_SET( REPLAY, SCHED_BLOCK_PROMOTED, m->block_promoted_cnt );
    3859           0 :   FD_MCNT_SET( REPLAY, SCHED_BLOCK_DEMOTED, m->block_demoted_cnt );
    3860           0 :   ulong sched_deactivate[ FD_METRICS_ENUM_SCHED_DEACTIVATE_REASON_CNT ];
    3861           0 :   sched_deactivate[ FD_METRICS_ENUM_SCHED_DEACTIVATE_REASON_V_NO_CHILD_IDX       ] = m->deactivate_no_child_cnt;
    3862           0 :   sched_deactivate[ FD_METRICS_ENUM_SCHED_DEACTIVATE_REASON_V_NO_WORK_IDX        ] = m->deactivate_no_txn_cnt;
    3863           0 :   sched_deactivate[ FD_METRICS_ENUM_SCHED_DEACTIVATE_REASON_V_ABANDONED_IDX      ] = m->deactivate_abandoned_cnt;
    3864           0 :   sched_deactivate[ FD_METRICS_ENUM_SCHED_DEACTIVATE_REASON_V_MINORITY_FORK_IDX  ] = m->deactivate_pruned_cnt;
    3865           0 :   sched_deactivate[ FD_METRICS_ENUM_SCHED_DEACTIVATE_REASON_V_SPEC_YIELD_IDX     ] = m->lthash_spec_yield_cnt;
    3866           0 :   sched_deactivate[ FD_METRICS_ENUM_SCHED_DEACTIVATE_REASON_V_SPEC_POOL_FULL_IDX ] = m->lthash_pool_full_cnt;
    3867           0 :   FD_MCNT_ENUM_COPY( REPLAY, SCHED_BLOCK_DEACTIVATED, sched_deactivate );
    3868           0 :   FD_MCNT_SET( REPLAY, SCHED_LANE_SWITCHED, m->lane_switch_cnt );
    3869           0 :   FD_MCNT_SET( REPLAY, SCHED_LANE_PROMOTED, m->lane_promoted_cnt );
    3870           0 :   FD_MCNT_SET( REPLAY, SCHED_LANE_DEMOTED, m->lane_demoted_cnt );
    3871           0 :   FD_MCNT_SET( REPLAY, SCHED_BLOCK_INBAND_PICKED, m->lthash_inband_block_cnt );
    3872           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_PROMOTED, m->txn_promoted_cnt );
    3873           0 :   FD_MCNT_SET( REPLAY, SCHED_FORK_OBSERVED, m->fork_observed_cnt );
    3874           0 :   ulong sched_alut[ FD_METRICS_ENUM_SCHED_LUT_RESOLVE_RESULT_CNT ];
    3875           0 :   sched_alut[ FD_METRICS_ENUM_SCHED_LUT_RESOLVE_RESULT_V_SUCCESS_IDX ] = m->alut_success_cnt;
    3876           0 :   sched_alut[ FD_METRICS_ENUM_SCHED_LUT_RESOLVE_RESULT_V_FAILED_IDX  ] = m->alut_serializing_cnt;
    3877           0 :   FD_MCNT_ENUM_COPY( REPLAY, SCHED_TXN_LUT_RESOLVE_ATTEMPTED, sched_alut );
    3878           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_PARSED_ABANDONED, m->txn_abandoned_parsed_cnt );
    3879           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_EXECUTED_ABANDONED, m->txn_abandoned_exec_done_cnt );
    3880           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_DONE_ABANDONED, m->txn_abandoned_done_cnt );
    3881             :   /* Fold in the open interval, which the next dispatch or completion credits */
    3882           0 :   ulong open_ticks = (ulong)( fd_tickcount()-sched->txn_in_flight_last_tick );
    3883           0 :   ulong busy_cnt   = sched->exec_cnt-(ulong)fd_ulong_popcnt( sched->txn_exec_ready_bitset[ 0 ] );
    3884           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_IN_FLIGHT_INTEGRAL_NANOS, m->txn_weighted_in_flight_cnt+open_ticks*busy_cnt );
    3885           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_BUSY_DURATION_NANOS, m->txn_weighted_in_flight_tickcount+fd_ulong_if( !!busy_cnt, open_ticks, 0UL ) );
    3886           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_IDLE_DURATION_NANOS, m->txn_none_in_flight_tickcount+fd_ulong_if( !busy_cnt, open_ticks, 0UL ) );
    3887           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_PARSED, m->txn_parsed_cnt );
    3888           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_EXECUTED, m->txn_exec_done_cnt );
    3889           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_SIGNATURE_VERIFIED, m->txn_sigverify_done_cnt );
    3890           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_POH_MIXED, m->txn_mixin_done_cnt );
    3891           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_DONE, m->txn_done_cnt );
    3892           0 :   FD_MCNT_SET( REPLAY, SCHED_MICROBLOCK_PARSED, m->mblk_parsed_cnt );
    3893           0 :   FD_MCNT_SET( REPLAY, SCHED_MICROBLOCK_HASHED, m->mblk_poh_hashed_cnt );
    3894           0 :   FD_MCNT_SET( REPLAY, SCHED_MICROBLOCK_DONE, m->mblk_poh_done_cnt );
    3895           0 :   FD_MCNT_SET( REPLAY, SCHED_INGESTED_BYTES, m->bytes_ingested_cnt );
    3896           0 :   FD_MCNT_SET( REPLAY, SCHED_TRAILING_SKIPPED_BYTES, m->bytes_ingested_unparsed_cnt );
    3897           0 :   FD_MCNT_SET( REPLAY, SCHED_DROPPED_BYTES, m->bytes_dropped_cnt );
    3898           0 :   FD_MCNT_SET( REPLAY, SCHED_FEC_INGESTED, m->fec_cnt );
    3899             : 
    3900           0 :   ulong lthash_write[ FD_METRICS_ENUM_SCHED_LTHASH_WRITE_TYPE_CNT ];
    3901           0 :   lthash_write[ FD_METRICS_ENUM_SCHED_LTHASH_WRITE_TYPE_V_PARSED_IDX        ] = m->lthash_parsed_writable_cnt;
    3902           0 :   lthash_write[ FD_METRICS_ENUM_SCHED_LTHASH_WRITE_TYPE_V_BLOCK_START_IDX   ] = m->lthash_start_reg_cnt;
    3903           0 :   lthash_write[ FD_METRICS_ENUM_SCHED_LTHASH_WRITE_TYPE_V_LATE_LUT_IDX      ] = m->lthash_late_reg_cnt;
    3904           0 :   lthash_write[ FD_METRICS_ENUM_SCHED_LTHASH_WRITE_TYPE_V_DEMOTED_IDX       ] = m->lthash_demote_reg_cnt;
    3905           0 :   lthash_write[ FD_METRICS_ENUM_SCHED_LTHASH_WRITE_TYPE_V_SPEC_GIVEN_UP_IDX ] = m->lthash_spec_given_up_cnt;
    3906           0 :   FD_MCNT_ENUM_COPY( REPLAY, SCHED_LTHASH_WRITE_OBSERVED_BY_TYPE, lthash_write );
    3907           0 :   ulong lthash_handled[ FD_METRICS_ENUM_SCHED_LTHASH_WRITE_RESULT_CNT ];
    3908           0 :   lthash_handled[ FD_METRICS_ENUM_SCHED_LTHASH_WRITE_RESULT_V_FIRST_WRITE_IDX ] = m->lthash_first_write_cnt;
    3909           0 :   lthash_handled[ FD_METRICS_ENUM_SCHED_LTHASH_WRITE_RESULT_V_COALESCED_IDX   ] = m->lthash_coalesced_cnt;
    3910           0 :   FD_MCNT_ENUM_COPY( REPLAY, SCHED_LTHASH_WRITE_OBSERVED, lthash_handled );
    3911           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_ADD_CREATED,         m->lthash_add_created_cnt );
    3912           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_ADD_SURFACED,        m->lthash_add_surfaced_cnt );
    3913           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_ADD_STALE_SURFACED,  m->lthash_add_surfaced_stale_cnt );
    3914           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_ADD_DISPATCHED,      m->lthash_add_dispatched_cnt );
    3915           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_ADD_STALE_DROPPED,   m->lthash_add_dispatched_stale_cnt );
    3916           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_ADD_APPLIED,         m->lthash_add_applied_cnt );
    3917           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_ADD_UNDONE,          m->lthash_add_undone_cnt );
    3918           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_SPEC_CREATED,        m->lthash_spec_cnt );
    3919           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_SPEC_APPLIED,        m->lthash_spec_applied_cnt );
    3920           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_SPEC_INVALIDATED,    m->lthash_spec_stale_cnt );
    3921           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_SUB_OWED,            m->lthash_entry_cnt );
    3922           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_SUB_DISPATCHED,      m->lthash_sub_dispatched_cnt );
    3923           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_SUB_APPLIED,         m->lthash_sub_applied_cnt );
    3924           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_ACCOUNT_FROZEN,      m->lthash_frozen_cnt );
    3925           0 :   ulong lthash_dropped[ FD_METRICS_ENUM_SCHED_LTHASH_WORK_TYPE_CNT ];
    3926           0 :   lthash_dropped[ FD_METRICS_ENUM_SCHED_LTHASH_WORK_TYPE_V_ADD_OWED_IDX    ] = m->lthash_discarded_add_owed_cnt;
    3927           0 :   lthash_dropped[ FD_METRICS_ENUM_SCHED_LTHASH_WORK_TYPE_V_ADD_PENDING_IDX ] = m->lthash_discarded_add_pending_cnt;
    3928           0 :   lthash_dropped[ FD_METRICS_ENUM_SCHED_LTHASH_WORK_TYPE_V_ADD_APPLIED_IDX ] = m->lthash_discarded_add_applied_cnt;
    3929           0 :   lthash_dropped[ FD_METRICS_ENUM_SCHED_LTHASH_WORK_TYPE_V_SUB_QUEUED_IDX  ] = m->lthash_discarded_sub_queued_cnt;
    3930           0 :   lthash_dropped[ FD_METRICS_ENUM_SCHED_LTHASH_WORK_TYPE_V_SUB_APPLIED_IDX ] = m->lthash_discarded_sub_applied_cnt;
    3931           0 :   lthash_dropped[ FD_METRICS_ENUM_SCHED_LTHASH_WORK_TYPE_V_ADD_HELD_IDX    ] = m->lthash_discarded_add_held_cnt;
    3932           0 :   lthash_dropped[ FD_METRICS_ENUM_SCHED_LTHASH_WORK_TYPE_V_INBAND_ADD_OWED_IDX   ] = m->lthash_inband_add_owed_cnt;
    3933           0 :   lthash_dropped[ FD_METRICS_ENUM_SCHED_LTHASH_WORK_TYPE_V_INBAND_SUB_QUEUED_IDX ] = m->lthash_inband_sub_queued_cnt;
    3934           0 :   FD_MCNT_ENUM_COPY( REPLAY, SCHED_LTHASH_WORK_DROPPED, lthash_dropped );
    3935           0 :   ulong lthash_task[ FD_METRICS_ENUM_SCHED_LTHASH_TASK_TYPE_CNT ];
    3936           0 :   lthash_task[ FD_METRICS_ENUM_SCHED_LTHASH_TASK_TYPE_V_SUB_IDX ] = m->lthash_sub_task_cnt;
    3937           0 :   lthash_task[ FD_METRICS_ENUM_SCHED_LTHASH_TASK_TYPE_V_ADD_IDX ] = m->lthash_add_task_cnt;
    3938           0 :   FD_MCNT_ENUM_COPY( REPLAY, SCHED_LTHASH_TASK_DISPATCHED, lthash_task );
    3939           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_BATCH_HELD,        m->lthash_add_batch_held_cnt );
    3940           0 :   FD_MCNT_SET( REPLAY, SCHED_LTHASH_BATCH_IDLE_WAITED, m->lthash_add_batch_wait_idle_cnt );
    3941           0 : }
    3942             : 
    3943             : char *
    3944           0 : fd_sched_get_state_cstr( fd_sched_t * sched ) {
    3945           0 :   lthash_metrics_check( sched );
    3946           0 :   sched->print_buf_sz = 0UL;
    3947           0 :   print_metrics( sched );
    3948           0 :   print_sched( sched );
    3949           0 :   return sched->print_buf;
    3950           0 : }
    3951             : 
    3952           0 : void * fd_sched_leave ( fd_sched_t * sched ) { return sched; }
    3953           0 : void * fd_sched_delete( void * mem         ) { return   mem; }
    3954             : 
    3955             : 
    3956             : /* Internal helpers. */
    3957             : 
    3958             : static void
    3959             : add_block( fd_sched_t * sched,
    3960             :            ulong        bank_idx,
    3961        2009 :            ulong        parent_bank_idx ) {
    3962        2009 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3963        2009 :   FD_TEST( !block->in_sched );
    3964        2009 :   sched->block_pool_popcnt++;
    3965             : 
    3966        2009 :   block->txn_parsed_cnt              = 0U;
    3967        2009 :   block->txn_exec_in_flight_cnt      = 0U;
    3968        2009 :   block->txn_exec_done_cnt           = 0U;
    3969        2009 :   block->txn_sigverify_in_flight_cnt = 0U;
    3970        2009 :   block->txn_sigverify_done_cnt      = 0U;
    3971        2009 :   block->poh_hashing_in_flight_cnt   = 0U;
    3972        2009 :   block->poh_mblk_in_flight_cnt      = 0U;
    3973        2009 :   block->poh_hashing_done_cnt        = 0U;
    3974        2009 :   block->poh_hash_cmp_done_cnt       = 0U;
    3975        2009 :   block->footer_in_flight_cnt          = 0U;
    3976        2009 :   block->txn_done_cnt                = 0U;
    3977        2009 :   block->shred_cnt                   = 0U;
    3978        2009 :   block->shred_scan_idx              = 0U;
    3979        2009 :   block->shred_scan_off              = 0U;
    3980        2009 :   block->shred_sz_overflow           = 0;
    3981        2009 :   block->mblk_cnt                    = 0U;
    3982        2009 :   block->mblk_freed_cnt              = 0U;
    3983        2009 :   block->mblk_tick_cnt               = 0U;
    3984        2009 :   block->mblk_unhashed_cnt           = 0U;
    3985        2009 :   block->dead_reason                 = FD_SCHED_DEAD_REASON_NONE;
    3986        2009 :   block->txn_pool_max_popcnt         = sched->depth - sched->txn_pool_free_cnt - 1UL;
    3987        2009 :   block->mblk_pool_max_popcnt        = sched->depth - sched->mblk_pool_free_cnt;
    3988        2009 :   block->block_pool_max_popcnt       = sched->block_pool_popcnt;
    3989        2009 :   block->txn_idx_tail                = 0U;
    3990        2009 :   block->txn_sigverify_next_idx      = 0U;
    3991        2009 :   block->parse_mblk_idx              = UINT_MAX;
    3992             : 
    3993        2009 :   mblk_slist_remove_all( block->mblks_unhashed, sched->mblk_pool );
    3994        2009 :   mblk_slist_remove_all( block->mblks_mixin_in_progress, sched->mblk_pool );
    3995        2009 :   block->last_mblk_is_tick            = 0;
    3996        2009 :   block->tick_height                  = ULONG_MAX;
    3997        2009 :   block->max_tick_height              = ULONG_MAX;
    3998             : 
    3999        2009 :   block->header_seen        = 0;
    4000        2009 :   block->genesis_cert_seen  = 0;
    4001        2009 :   block->update_parent_seen = 0;
    4002        2009 :   block->footer_seen        = 0;
    4003        2009 :   block->cert_cnt           = 0U;
    4004        2009 :   block->cert_next          = 0U;
    4005        2009 :   block->alpentick_seen     = 0;
    4006        2009 :   block->starts_at_update_parent = 0;
    4007        2009 :   block->flh_active              = 0;
    4008             : 
    4009        2009 :   block->mblks_rem        = 0UL;
    4010        2009 :   block->txns_rem         = 0UL;
    4011        2009 :   block->residual_sz      = 0U;
    4012        2009 :   block->fec_buf_boff     = 0U;
    4013        2009 :   block->fec_eob          = 0;
    4014        2009 :   block->fec_sob          = 1;
    4015             : 
    4016        2009 :   block->fec_eos              = 0;
    4017        2009 :   block->rooted               = 0;
    4018        2009 :   block->dying                = 0;
    4019        2009 :   block->discarded            = 0;
    4020        2009 :   block->fec_completed_ns     = 0L;
    4021        2009 :   block->refcnt               = 1;
    4022        2009 :   block->in_sched             = 1;
    4023        2009 :   block->in_rdisp             = 0;
    4024        2009 :   block->block_start_signaled = 0;
    4025        2009 :   block->block_end_signaled   = 0;
    4026        2009 :   block->block_start_done     = 0;
    4027        2009 :   block->block_end_done       = 0;
    4028        2009 :   block->staged               = 0;
    4029        2009 :   block->state_open           = 0;
    4030        2009 :   block->lthash_mode_picked   = 0;
    4031        2009 :   block->lthash_inband        = 0;
    4032        2009 :   FD_TEST( !block->add_owed_cnt && !block->add_created_cnt && !block->sub_queued_cnt && !block->sub_in_flight_cnt && !block->sub_applied_cnt && !block->lthash_in_flight_cnt && !block->lthash_drained && !block->lthash_draining );
    4033             : 
    4034        2009 :   block->luf_depth = 0UL;
    4035             : 
    4036             :   /* New leaf node, no child, no sibling. */
    4037        2009 :   block->child_idx   = ULONG_MAX;
    4038        2009 :   block->sibling_idx = ULONG_MAX;
    4039        2009 :   block->parent_idx  = ULONG_MAX;
    4040             : 
    4041        2009 :   if( FD_UNLIKELY( parent_bank_idx==ULONG_MAX ) ) {
    4042        1008 :     return;
    4043        1008 :   }
    4044             : 
    4045             :   /* node->parent link */
    4046        1001 :   fd_sched_block_t * parent_block = block_pool_ele( sched, parent_bank_idx );
    4047        1001 :   block->parent_idx = parent_bank_idx;
    4048             : 
    4049             :   /* parent->node and sibling->node links */
    4050        1001 :   ulong child_idx = bank_idx;
    4051        1001 :   if( FD_LIKELY( parent_block->child_idx==ULONG_MAX ) ) { /* Optimize for no forking. */
    4052        1001 :     parent_block->child_idx = child_idx;
    4053        1001 :   } else {
    4054           0 :     fd_sched_block_t * curr_block = block_pool_ele( sched, parent_block->child_idx );
    4055           0 :     while( curr_block->sibling_idx!=ULONG_MAX ) {
    4056           0 :       curr_block = block_pool_ele( sched, curr_block->sibling_idx );
    4057           0 :     }
    4058           0 :     curr_block->sibling_idx = child_idx;
    4059           0 :     sched->metrics->fork_observed_cnt++;
    4060           0 :   }
    4061             : 
    4062        1001 :   if( FD_UNLIKELY( parent_block->dying ) ) {
    4063           0 :     record_lineage_death( block, parent_block );
    4064           0 :     block->dying = 1;
    4065           0 :   }
    4066        1001 : }
    4067             : 
    4068             : /* Alpenglow block structure.  agave's BlockComponentProcessor rules
    4069             :    an Alpenglow block invalid unless its components are laid out as
    4070             : 
    4071             :      header | [genesis cert] | entries* | [update parent] | entries* | footer | alpentick
    4072             : 
    4073             :    with exactly one header and one footer, and the alpentick as the
    4074             :    final component.  The helpers below drive the same state machine off
    4075             :    the batches sched parses. */
    4076             : 
    4077             : static int
    4078             : on_marker( fd_sched_t *              sched,
    4079             :            fd_sched_block_t *        block,
    4080        1993 :            fd_block_marker_t const * marker ) {
    4081        1993 :   (void)sched;
    4082        1993 :   switch( marker->kind ) {
    4083             : 
    4084        1001 :   case FD_BLOCK_MARKER_KIND_HEADER:
    4085        1001 :     if( FD_UNLIKELY( block->header_seen ) ) {
    4086           0 :       FD_LOG_INFO(( "bad block: MULTIPLE_BLOCK_HEADERS, slot %lu, parent slot %lu", block->slot, block->parent_slot ));
    4087           0 :       return FD_SCHED_DEAD_REASON_MULTIPLE_BLOCK_HEADERS;
    4088           0 :     }
    4089             :     /* Checking for shred header and parent_slot mismatch occurs at the
    4090             :        rotor level, so it does not need to be checked in sched.  We do
    4091             :        this because currently replay tile never sees shred headers. */
    4092        1001 :     block->header_seen = 1;
    4093        1001 :     return FD_SCHED_DEAD_REASON_NONE;
    4094             : 
    4095           0 :   case FD_BLOCK_MARKER_KIND_GENESIS_CERT:
    4096           0 :     if( FD_UNLIKELY( !block->header_seen ) ) {
    4097           0 :       FD_LOG_INFO(( "bad block: MISSING_PARENT_MARKER, slot %lu, parent slot %lu, genesis cert before header", block->slot, block->parent_slot ));
    4098           0 :       return FD_SCHED_DEAD_REASON_MISSING_PARENT_MARKER;
    4099           0 :     }
    4100           0 :     if( FD_UNLIKELY( block->mblk_cnt || block->genesis_cert_seen || block->footer_seen ) ) {
    4101           0 :       FD_LOG_INFO(( "bad block: GENESIS_CERT_OUT_OF_ORDER, slot %lu, parent slot %lu", block->slot, block->parent_slot ));
    4102           0 :       return FD_SCHED_DEAD_REASON_GENESIS_CERT_OUT_OF_ORDER;
    4103           0 :     }
    4104           0 :     block->genesis_cert_seen = 1;
    4105           0 :     return FD_SCHED_DEAD_REASON_NONE;
    4106             : 
    4107         992 :   case FD_BLOCK_MARKER_KIND_FOOTER:
    4108         992 :     if( FD_UNLIKELY( !block->header_seen ) ) {
    4109           0 :       FD_LOG_INFO(( "bad block: MISSING_PARENT_MARKER, slot %lu, parent slot %lu, footer before header", block->slot, block->parent_slot ));
    4110           0 :       return FD_SCHED_DEAD_REASON_MISSING_PARENT_MARKER;
    4111           0 :     }
    4112         992 :     if( FD_UNLIKELY( block->footer_seen ) ) {
    4113           0 :       FD_LOG_INFO(( "bad block: MULTIPLE_BLOCK_FOOTERS, slot %lu, parent slot %lu", block->slot, block->parent_slot ));
    4114           0 :       return FD_SCHED_DEAD_REASON_MULTIPLE_BLOCK_FOOTERS;
    4115           0 :     }
    4116         992 :     block->footer      = marker->footer;
    4117         992 :     block->footer_seen = 1;
    4118         992 :     fd_block_footer_cert_t const * cert  [ FD_ALPENGLOW_FOOTER_CERT_MAX ];
    4119         992 :     uint                           tag   [ FD_ALPENGLOW_FOOTER_CERT_MAX ];
    4120         992 :     ulong                          quorum[ FD_ALPENGLOW_FOOTER_CERT_MAX ];
    4121         992 :     ulong cert_cnt = fd_alpenglow_footer_certs( &block->footer, cert, tag, quorum );
    4122         992 :     FD_TEST( cert_cnt<=FD_ALPENGLOW_FOOTER_CERT_MAX );
    4123         992 :     block->cert_cnt = (uint)cert_cnt & 7U; /* The test is the bound.  The mask only quiets the conversion warning. */
    4124         992 :     return FD_SCHED_DEAD_REASON_NONE;
    4125             : 
    4126           0 :   case FD_BLOCK_MARKER_KIND_UPDATE_PARENT:
    4127           0 :     if( FD_UNLIKELY( block->update_parent_seen ) ) {
    4128           0 :       FD_LOG_INFO(( "bad block: MULTIPLE_UPDATE_PARENTS, slot %lu, parent slot %lu", block->slot, block->parent_slot ));
    4129           0 :       return FD_SCHED_DEAD_REASON_MULTIPLE_UPDATE_PARENTS;
    4130           0 :     }
    4131           0 :     if( FD_LIKELY( !block->header_seen ) ) {
    4132             :       /* Replay started the block here because rotor saw the
    4133             :          UpdateParent, so it is the parent marker in place of the
    4134             :          header and must name the parent replay built the block on.
    4135             :          Anything before it already died as MISSING_PARENT_MARKER. */
    4136           0 :       if( FD_UNLIKELY( !block->starts_at_update_parent ) ) {
    4137           0 :         FD_LOG_INFO(( "bad block: SPURIOUS_UPDATE_PARENT, slot %lu, parent slot %lu", block->slot, block->parent_slot ));
    4138           0 :         return FD_SCHED_DEAD_REASON_SPURIOUS_UPDATE_PARENT;
    4139           0 :       }
    4140           0 :       if( FD_UNLIKELY( marker->update_parent.new_parent_slot!=block->parent_slot ) ) {
    4141           0 :         FD_LOG_INFO(( "bad block: UPDATE_PARENT_MISMATCH, slot %lu, parent slot %lu, update parent names %lu", block->slot, block->parent_slot, marker->update_parent.new_parent_slot ));
    4142           0 :         return FD_SCHED_DEAD_REASON_UPDATE_PARENT_MISMATCH;
    4143           0 :       }
    4144           0 :       block->header_seen        = 1; /* the parent marker the remaining rules gate on */
    4145           0 :       block->update_parent_seen = 1;
    4146           0 :       return FD_SCHED_DEAD_REASON_NONE;
    4147           0 :     }
    4148             :     /* After a header or footer.  If after a header, replay should have
    4149             :        skipped the prefix instead, so reaching here means it was handed
    4150             :        a prefix it did not know to skip. If after a footer, its a
    4151             :        misformed block. */
    4152           0 :     FD_LOG_INFO(( "bad block: SPURIOUS_UPDATE_PARENT, slot %lu, parent slot %lu, update parent after %s", block->slot, block->parent_slot, block->footer_seen ? "footer" : "header" ));
    4153           0 :     return FD_SCHED_DEAD_REASON_SPURIOUS_UPDATE_PARENT;
    4154             : 
    4155           0 :   default:
    4156           0 :     FD_LOG_INFO(( "bad block: slot %lu, parent slot %lu, unknown block marker kind %u", block->slot, block->parent_slot, marker->kind ));
    4157           0 :     return FD_SCHED_DEAD_REASON_BAD_BLOCK_MARKER;
    4158        1993 :   }
    4159        1993 : }
    4160             : 
    4161             : /* Called when a batch header declares one or more microblocks. */
    4162             : 
    4163             : static int
    4164        2562 : on_entry_batch( fd_sched_block_t * block ) {
    4165        2562 :   if( FD_UNLIKELY( !block->header_seen ) ) {
    4166           0 :     FD_LOG_INFO(( "bad block: MISSING_PARENT_MARKER, slot %lu, parent slot %lu, entries before header", block->slot, block->parent_slot ));
    4167           0 :     return FD_SCHED_DEAD_REASON_MISSING_PARENT_MARKER;
    4168           0 :   }
    4169        2562 :   if( FD_UNLIKELY( block->alpentick_seen ) ) {
    4170           0 :     FD_LOG_INFO(( "bad block: ENTRY_AFTER_BLOCK_FOOTER, slot %lu, parent slot %lu, batch after alpentick", block->slot, block->parent_slot ));
    4171           0 :     return FD_SCHED_DEAD_REASON_ENTRY_AFTER_BLOCK_FOOTER;
    4172           0 :   }
    4173             :   /* Only the alpentick may follow the footer: a batch of exactly one
    4174             :      microblock which must turn out to be a tick (checked when its
    4175             :      header parses). */
    4176        2562 :   if( FD_UNLIKELY( block->footer_seen && block->mblks_rem!=1UL ) ) {
    4177           0 :     FD_LOG_INFO(( "bad block: ENTRY_AFTER_BLOCK_FOOTER, slot %lu, parent slot %lu, batch of %lu microblocks after footer", block->slot, block->parent_slot, block->mblks_rem ));
    4178           0 :     return FD_SCHED_DEAD_REASON_ENTRY_AFTER_BLOCK_FOOTER;
    4179           0 :   }
    4180        2562 :   return FD_SCHED_DEAD_REASON_NONE;
    4181        2562 : }
    4182             : 
    4183             : static int
    4184         991 : on_final( fd_sched_block_t * block ) {
    4185         991 :   if( FD_UNLIKELY( !block->footer_seen ) ) {
    4186           0 :     FD_LOG_INFO(( "bad block: MISSING_BLOCK_FOOTER, slot %lu, parent slot %lu", block->slot, block->parent_slot ));
    4187           0 :     return FD_SCHED_DEAD_REASON_MISSING_BLOCK_FOOTER;
    4188           0 :   }
    4189         991 :   if( FD_UNLIKELY( !block->alpentick_seen ) ) {
    4190           0 :     FD_LOG_INFO(( "bad block: INVALID_ALPENTICK_POSITION, slot %lu, parent slot %lu, block ended after footer without alpentick", block->slot, block->parent_slot ));
    4191           0 :     return FD_SCHED_DEAD_REASON_INVALID_ALPENTICK_POSITION;
    4192           0 :   }
    4193         991 :   return FD_SCHED_DEAD_REASON_NONE;
    4194         991 : }
    4195             : 
    4196             : /* Bounds the tick count as soon as the tick window is known.  Returns
    4197             :    FD_SCHED_DEAD_REASON_NONE (0) on success, else the
    4198             :    FD_SCHED_DEAD_REASON_* the block should be ruled invalid for.  Does
    4199             :    not modify the block. */
    4200             : static int
    4201        4555 : verify_ticks_eager( fd_sched_block_t * block ) {
    4202        4555 :   FD_TEST( block->tick_height!=ULONG_MAX ); /* PoH params initialized. */
    4203             : 
    4204        4555 :   if( FD_UNLIKELY( block->mblk_tick_cnt+block->tick_height>block->max_tick_height ) ) {
    4205        1486 :     FD_LOG_INFO(( "bad block: TOO_MANY_TICKS, slot %lu, parent slot %lu, tick_cnt %u, tick_height %lu, max_tick_height %lu", block->slot, block->parent_slot, block->mblk_tick_cnt, block->tick_height, block->max_tick_height ));
    4206        1486 :     return FD_SCHED_DEAD_REASON_TOO_MANY_TICKS;
    4207        1486 :   }
    4208             : 
    4209        3069 :   return FD_SCHED_DEAD_REASON_NONE;
    4210        4555 : }
    4211             : 
    4212             : /* https://github.com/anza-xyz/agave/blob/v3.0.6/ledger/src/blockstore_processor.rs#L1057
    4213             : 
    4214             :    The only check we don't do here is TRAILING_ENTRY, which can be done
    4215             :    independently when we parse the final FEC set of a block.
    4216             : 
    4217             :    Returns FD_SCHED_DEAD_REASON_NONE (0) on success, else the
    4218             :    FD_SCHED_DEAD_REASON_* the block should be ruled invalid for.  Does
    4219             :    not modify the block. */
    4220             : static int
    4221         992 : verify_ticks_final( fd_sched_block_t * block ) {
    4222         992 :   FD_TEST( block->fec_eos );
    4223             : 
    4224         992 :   if( FD_UNLIKELY( block->mblk_tick_cnt+block->tick_height<block->max_tick_height ) ) {
    4225           0 :     FD_LOG_INFO(( "bad block: TOO_FEW_TICKS, slot %lu, parent slot %lu, tick_cnt %u, tick_height %lu, max_tick_height %lu", block->slot, block->parent_slot, block->mblk_tick_cnt, block->tick_height, block->max_tick_height ));
    4226           0 :     return FD_SCHED_DEAD_REASON_TOO_FEW_TICKS;
    4227           0 :   }
    4228             : 
    4229         992 :   return verify_ticks_eager( block );
    4230         992 : }
    4231             : 
    4232             : int
    4233             : fd_sched_block_verify_ticks( fd_sched_t * sched,
    4234             :                              ulong        bank_idx,
    4235             :                              ulong        tick_height,
    4236        4555 :                              ulong        max_tick_height ) {
    4237        4555 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    4238        4555 :   FD_TEST( bank_idx<sched->block_cnt_max );
    4239        4555 :   FD_TEST( max_tick_height>tick_height );
    4240        4555 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    4241             :   /* Set the tick window directly; skip fd_sched_set_poh_params
    4242             :      PoH fixup, unneeded here and unsafe to repeat per FEC set. */
    4243        4555 :   block->tick_height     = tick_height;
    4244        4555 :   block->max_tick_height = max_tick_height;
    4245             :   /* fec_eos => slot fully ingested, so the TOO_FEW check is meaningful
    4246             :      too. */
    4247        4555 :   return block->fec_eos ? verify_ticks_final( block ) : verify_ticks_eager( block );
    4248        4555 : }
    4249             : 
    4250       28259 : #define CHECK( cond )  do {           \
    4251       28259 :   if( FD_UNLIKELY( !(cond) ) ) {      \
    4252           2 :     return FD_SCHED_DEAD_REASON_NONE; \
    4253           2 :   }                                   \
    4254       28259 : } while( 0 )
    4255             : 
    4256             : /* CHECK that it is safe to read at least n more bytes. */
    4257       28259 : #define CHECK_LEFT( n ) CHECK( (n)<=(sched->fec_buf_sz-sched->fec_buf_soff) )
    4258             : 
    4259             : /* Consume as much as possible from the buffer.  By the end of this
    4260             :    function, there could be residual data left over in the buffer.  That
    4261             :    residual has to be either a partially received microblock header or a
    4262             :    transaction that straddles a FEC set boundary.  These bytes are kept
    4263             :    and will be concatenated with the next FEC set.  This residual is
    4264             :    bounded.
    4265             : 
    4266             :    Trailing bytes within a batch are dropped immediately.  These
    4267             :    trailing bytes can span arbitrarily many FEC sets. */
    4268             : FD_WARN_UNUSED static int
    4269        4557 : fd_sched_parse( fd_sched_t * sched, fd_sched_block_t * block, fd_sched_alut_ctx_t * alut_ctx ) {
    4270       32815 :   while( 1 ) {
    4271       34354 :     while( block->txns_rem>0UL ) {
    4272        1541 :       int err = fd_sched_parse_txn( sched, block, alut_ctx );
    4273        1541 :       if( FD_UNLIKELY( -1==err ) ) return FD_SCHED_DEAD_REASON_NONE;
    4274        1541 :       else if( FD_UNLIKELY( err ) ) return err;
    4275        1541 :     }
    4276             : 
    4277       32813 :     if( block->txns_rem==0UL && block->mblks_rem>0UL ) {
    4278       23702 :       if( FD_UNLIKELY( block->mblk_cnt>=sched->max_mblk_per_slot ) ) {
    4279             :         /* Microblock count is enforced as invariant
    4280             :            mblk_cnt+mblks_rem<=max_mblk_per_slot
    4281             :            when we read the declared count in a batch header.  So we
    4282             :            shouldn't get here. */
    4283           0 :         sched->print_buf_sz = 0UL;
    4284           0 :         print_all( sched, block );
    4285           0 :         FD_LOG_NOTICE(( "%s", sched->print_buf ));
    4286           0 :         FD_LOG_CRIT(( "invariant violation: slot %lu, parent slot %lu, mblks_rem %lu, mblk_cnt %u (%u ticks) >= %lu", block->slot, block->parent_slot, block->mblks_rem, block->mblk_cnt, block->mblk_tick_cnt, sched->max_mblk_per_slot ));
    4287           0 :       }
    4288             : 
    4289       23702 :       CHECK_LEFT( sizeof(fd_microblock_hdr_t) );
    4290       23702 :       fd_microblock_hdr_t * hdr = (fd_microblock_hdr_t *)fd_type_pun( sched->fec_buf+sched->fec_buf_soff );
    4291       23702 :       sched->fec_buf_soff      += (uint)sizeof(fd_microblock_hdr_t);
    4292             : 
    4293       23702 :       if( FD_UNLIKELY( hdr->txn_cnt>fd_ulong_sat_sub( sched->max_txn_per_slot, block->txn_parsed_cnt ) ) ) {
    4294           0 :         FD_LOG_INFO(( "bad block: TOO_MANY_TXNS, microblock header declared too many transactions, slot %lu, parent slot %lu, txn_parsed_cnt %u, hdr->txn_cnt %lu", block->slot, block->parent_slot, block->txn_parsed_cnt, hdr->txn_cnt ));
    4295           0 :         return FD_SCHED_DEAD_REASON_TOO_MANY_TXNS;
    4296           0 :       }
    4297       23702 :       if( FD_UNLIKELY( hdr->hash_cnt!=1UL ) ) {
    4298           0 :         FD_LOG_INFO(( "bad block: ALPENGLOW_HASH_CNT, slot %lu, parent slot %lu, mblk idx %u declared hash_cnt %lu", block->slot, block->parent_slot, block->mblk_cnt, hdr->hash_cnt ));
    4299           0 :         return FD_SCHED_DEAD_REASON_ALPENGLOW_HASH_CNT;
    4300           0 :       }
    4301       23702 :       if( FD_UNLIKELY( block->footer_seen ) ) {
    4302         992 :         if( FD_UNLIKELY( hdr->txn_cnt ) ) {
    4303           0 :           FD_LOG_INFO(( "bad block: ENTRY_AFTER_BLOCK_FOOTER, slot %lu, parent slot %lu, transaction microblock after footer", block->slot, block->parent_slot ));
    4304           0 :           return FD_SCHED_DEAD_REASON_ENTRY_AFTER_BLOCK_FOOTER;
    4305           0 :         }
    4306         992 :         block->alpentick_seen = 1;
    4307         992 :       }
    4308             : 
    4309       23702 :       block->mblks_rem--;
    4310       23702 :       block->txns_rem = hdr->txn_cnt;
    4311             : 
    4312       23702 :       sched->metrics->mblk_parsed_cnt++;
    4313       23702 :       if( FD_UNLIKELY( !hdr->txn_cnt ) ) {
    4314             :         /* This is a tick microblock. */
    4315       22304 :         block->mblk_tick_cnt++;
    4316       22304 :       }
    4317             : 
    4318       23702 :       FD_TEST( sched->mblk_pool_free_cnt ); /* can_ingest should have guaranteed sufficient free capacity. */
    4319       23702 :       uint mblk_idx = sched->mblk_pool_free_head;
    4320       23702 :       sched->mblk_pool_free_head = sched->mblk_pool[ mblk_idx ].next;
    4321       23702 :       sched->mblk_pool_free_cnt--;
    4322             : 
    4323       23702 :       fd_sched_mblk_t * mblk = sched->mblk_pool+mblk_idx;
    4324       23702 :       mblk->start_txn_idx = block->txn_parsed_cnt;
    4325       23702 :       mblk->end_txn_idx   = mblk->start_txn_idx+hdr->txn_cnt;
    4326       23702 :       mblk->curr_txn_idx  = mblk->start_txn_idx;
    4327       23702 :       mblk->curr_sig_cnt  = 0U;
    4328       23702 :       mblk->mixin_txn_idx = 0U;
    4329       23702 :       mblk->is_tick       = !hdr->txn_cnt;
    4330       23702 :       memcpy( mblk->end_hash, hdr->hash, sizeof(fd_hash_t) );
    4331       23702 :       memcpy( mblk->curr_hash, block->poh_hash, sizeof(fd_hash_t) );
    4332       23702 :       block->parse_mblk_idx = mblk_idx;
    4333             : 
    4334             :       /* Update block tracking. */
    4335       23702 :       memcpy( block->poh_hash, hdr->hash, sizeof(fd_hash_t) );
    4336       23702 :       block->last_mblk_is_tick = mblk->is_tick;
    4337       23702 :       block->mblk_cnt++;
    4338             : 
    4339             :       #if FD_SCHED_SKIP_POH
    4340             :       block->poh_hashing_done_cnt++;
    4341             :       block->poh_hash_cmp_done_cnt++;
    4342             :       free_mblk( sched, block, mblk_idx );
    4343             :       #else
    4344       23702 :       mblk_slist_idx_push_tail( block->mblks_unhashed, mblk_idx, sched->mblk_pool );
    4345       23702 :       block->mblk_unhashed_cnt++;
    4346       23702 :       #endif
    4347       23702 :       continue;
    4348       23702 :     }
    4349        9111 :     if( block->txns_rem==0UL && block->mblks_rem==0UL && block->fec_sob ) {
    4350        4557 :       CHECK_LEFT( sizeof(ulong) );
    4351        4555 :       FD_TEST( sched->fec_buf_soff==0U );
    4352        4555 :       if( FD_UNLIKELY( !FD_LOAD( ulong, sched->fec_buf ) && !block->fec_eob ) ) {
    4353             :         /* Block marker split across FEC sets */
    4354           0 :         fd_block_marker_t peek[1];
    4355           0 :         if( FD_UNLIKELY( FD_BLOCK_MARKER_DE_ERR_SZ==fd_block_marker_de( peek, sched->fec_buf, (ulong)sched->fec_buf_sz ) &&
    4356           0 :                          sched->fec_buf_sz<FD_BLOCK_MARKER_SER_MAX ) ) return FD_SCHED_DEAD_REASON_NONE;
    4357           0 :       }
    4358        4555 :       block->mblks_rem     = FD_LOAD( ulong, sched->fec_buf );
    4359        4555 :       sched->fec_buf_soff += (uint)sizeof(ulong);
    4360             : 
    4361        4555 :       if( FD_UNLIKELY( block->mblks_rem>fd_ulong_sat_sub( sched->max_mblk_per_slot, block->mblk_cnt ) ) ) {
    4362           0 :         FD_LOG_INFO(( "bad block: TOO_MANY_MICROBLOCKS, slot %lu, parent slot %lu, mblk_cnt %u (%u ticks) + hdr->mblk_cnt %lu >= %lu", block->slot, block->parent_slot, block->mblk_cnt, block->mblk_tick_cnt, block->mblks_rem, sched->max_mblk_per_slot ));
    4363           0 :         return FD_SCHED_DEAD_REASON_TOO_MANY_MICROBLOCKS;
    4364           0 :       }
    4365             : 
    4366        4555 :       block->fec_sob = 0;
    4367             : 
    4368        4555 :       if( FD_UNLIKELY( !block->mblks_rem ) ) {
    4369             :         /* A zero-microblock batch is a block marker */
    4370        1993 :         fd_block_marker_t marker[1];
    4371        1993 :         int err = fd_block_marker_de( marker, sched->fec_buf, (ulong)sched->fec_buf_sz );
    4372        1993 :         if( FD_UNLIKELY( err ) ) {
    4373           0 :           FD_LOG_INFO(( "bad block: slot %lu, unable to parse block marker (err %d)", block->slot, err ));
    4374           0 :           return FD_SCHED_DEAD_REASON_BAD_BLOCK_MARKER;
    4375           0 :         }
    4376        1993 :         int dead_reason = on_marker( sched, block, marker );
    4377        1993 :         if( FD_UNLIKELY( dead_reason!=FD_SCHED_DEAD_REASON_NONE ) ) return dead_reason;
    4378        1993 :         sched->fec_buf_soff = (uint)( FD_BLOCK_MARKER_PREAMBLE_SZ+FD_LOAD( ushort, sched->fec_buf+FD_BLOCK_MARKER_PREAMBLE_SZ-sizeof(ushort) ) ); /* the marker is not trailing bytes */
    4379        2562 :       } else if( FD_UNLIKELY( block->mblks_rem ) ) {
    4380        2562 :         int dead_reason = on_entry_batch( block );
    4381        2562 :         if( FD_UNLIKELY( dead_reason!=FD_SCHED_DEAD_REASON_NONE ) ) return dead_reason;
    4382        2562 :       }
    4383        4555 :       continue;
    4384        4555 :     }
    4385        4554 :     if( block->txns_rem==0UL && block->mblks_rem==0UL ) {
    4386        4553 :       break;
    4387        4553 :     }
    4388        4554 :   }
    4389        4555 :   if( !block->fec_sob && block->txns_rem==0UL && block->mblks_rem==0UL ) {
    4390             :     /* All microblocks announced by the current batch header have been
    4391             :        parsed out.  Anything still in the buffer must be trailing bytes.
    4392             :        Drop them now because trailing bytes can span many FEC sets and
    4393             :        accumulating them would overflow the parse buffer.  Any followup
    4394             :        FEC sets within the same batch will simply be discarded
    4395             :        wholesale. */
    4396        4553 :     sched->metrics->bytes_ingested_unparsed_cnt += sched->fec_buf_sz-sched->fec_buf_soff;
    4397        4553 :     block->fec_buf_boff += sched->fec_buf_sz;
    4398        4553 :     sched->fec_buf_soff  = 0U;
    4399        4553 :     sched->fec_buf_sz    = 0U;
    4400        4553 :   }
    4401        4555 :   if( block->fec_eob ) {
    4402        4552 :     block->fec_sob = 1;
    4403        4552 :     block->fec_eob = 0;
    4404        4552 :   }
    4405        4555 :   return FD_SCHED_DEAD_REASON_NONE;
    4406        4557 : }
    4407             : 
    4408             : static inline fd_acct_addr_t const *
    4409        2889 : get_acct( fd_txn_t const * txn, fd_acct_addr_t const * imms, fd_acct_addr_t const * alts, ushort idx ) {
    4410        2889 :   if( FD_LIKELY( idx<txn->acct_addr_cnt ) ) return imms+idx;
    4411           0 :   if( FD_UNLIKELY( !alts )                ) return NULL;
    4412           0 :   return alts+(idx-txn->acct_addr_cnt);
    4413           0 : }
    4414             : 
    4415             : FD_WARN_UNUSED static int
    4416        1541 : fd_sched_parse_txn( fd_sched_t * sched, fd_sched_block_t * block, fd_sched_alut_ctx_t * alut_ctx ) {
    4417        1541 :   fd_txn_t * txn = fd_type_pun( sched->txn );
    4418             : 
    4419        1541 :   uchar * payload   = sched->fec_buf+sched->fec_buf_soff;
    4420        1541 :   ulong   remaining = sched->fec_buf_sz-sched->fec_buf_soff;
    4421        1541 :   ulong   pay_sz    = 0UL;
    4422        1541 :   ulong   txn_sz    = fd_txn_parse_core( payload,
    4423        1541 :                                          remaining,
    4424        1541 :                                          txn,
    4425        1541 :                                          NULL,
    4426        1541 :                                          &pay_sz );
    4427             : 
    4428             :   /* Can't parse out a full transaction yet, EAGAIN. */
    4429        1541 :   if( FD_UNLIKELY( !pay_sz || !txn_sz ) ) return -1;
    4430             : 
    4431             :   /* The pool slots this transaction will land in are cold: warm the
    4432             :      lines written below (the start of the payload copy, the metadata,
    4433             :      the start of the parsed txn and the whole info record) while the
    4434             :      ALUT resolution and the dispatcher insert run.  A hint only. */
    4435        1541 :   ulong next_idx = fd_rdisp_peek_free_txn( sched->rdisp );
    4436        1542 :   if( FD_LIKELY( next_idx && next_idx<sched->depth ) ) {
    4437        1542 :     fd_txn_p_t * next_p = sched->txn_pool+next_idx;
    4438        1542 :     __builtin_prefetch( next_p->payload,      1, 3 );
    4439        1542 :     __builtin_prefetch( next_p->payload+64UL, 1, 3 );
    4440        1542 :     __builtin_prefetch( &next_p->payload_sz,  1, 3 );
    4441        1542 :     __builtin_prefetch( TXN( next_p ),        1, 3 );
    4442        1542 :     ulong info_lo = fd_ulong_align_dn( (ulong)(sched->txn_info_pool+next_idx     ), 64UL );
    4443        1542 :     ulong info_hi = fd_ulong_align_up( (ulong)(sched->txn_info_pool+next_idx+1UL ), 64UL );
    4444        7682 :     for( ulong line=info_lo; line<info_hi; line+=64UL ) __builtin_prefetch( (void const *)line, 1, 3 );
    4445        1542 :   }
    4446             : 
    4447        1541 :   if( FD_UNLIKELY( block->txn_parsed_cnt>=sched->max_txn_per_slot ) ) {
    4448             :     /* Transaction count is enforced as invariant
    4449             :        txn_parsed_cnt+txns_rem<=max_txn_per_slot
    4450             :        when we read the declared count in a microblock header.  So we
    4451             :        shouldn't get here. */
    4452           0 :     sched->print_buf_sz = 0UL;
    4453           0 :     print_all( sched, block );
    4454           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
    4455           0 :     FD_LOG_CRIT(( "invariant violation: slot %lu, parent slot %lu, txn_parsed_cnt %u", block->slot, block->parent_slot, block->txn_parsed_cnt ));
    4456           0 :   }
    4457             : 
    4458        1541 :   ulong imm_cnt = fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_IMM );
    4459        1541 :   ulong alt_cnt = fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_ALT );
    4460             : 
    4461             :   /* Try to expand ALUTs. */
    4462        1541 :   int serializing = 0;
    4463        1541 :   if( alt_cnt>0UL ) {
    4464           0 :     if( FD_UNLIKELY( sched->bypass_alut_resolution ) ) {
    4465             :       /* test/fuzz: no accdb to query, so treat ALUT txns as serializing. */
    4466           0 :       serializing = 1;
    4467           0 :     } else {
    4468             :       /* Copy the slot hashes sysvar out and release the read BEFORE
    4469             :          resolving the ALTs.  fd_runtime_load_txn_address_lookup_tables
    4470             :          issues its own fd_accdb_read_one per lookup table, and the accdb
    4471             :          acquire state is a single non-nestable flag — holding this read
    4472             :          open across that call would trip the IDLE assertion in
    4473             :          fd_accdb_acquire.  The slot_hashes view aliases the record data,
    4474             :          so it must view the copy, not the released record. */
    4475           0 :       static uchar slot_hashes_buf[ FD_SYSVAR_SLOT_HASHES_BINCODE_SZ ];
    4476           0 :       ulong        slot_hashes_sz = 0UL;
    4477           0 :       int          have_slot_hashes = 0;
    4478           0 :       fd_acc_t ro = fd_accdb_read_one( alut_ctx->accdb, alut_ctx->fork_id, fd_sysvar_slot_hashes_id.uc );
    4479           0 :       if( FD_LIKELY( ro.lamports && ro.data_len<=sizeof(slot_hashes_buf) ) ) {
    4480           0 :         fd_memcpy( slot_hashes_buf, ro.data, ro.data_len );
    4481           0 :         slot_hashes_sz   = ro.data_len;
    4482           0 :         have_slot_hashes = 1;
    4483           0 :       }
    4484           0 :       fd_accdb_unread_one( alut_ctx->accdb, &ro );
    4485             : 
    4486           0 :       fd_slot_hashes_t slot_hashes_view[1];
    4487           0 :       if( FD_LIKELY( have_slot_hashes &&
    4488           0 :                      fd_sysvar_slot_hashes_view( slot_hashes_view, slot_hashes_buf, slot_hashes_sz ) ) ) {
    4489           0 :         serializing = !!fd_runtime_load_txn_address_lookup_tables( txn, payload, alut_ctx->accdb, alut_ctx->fork_id, alut_ctx->els, slot_hashes_view, sched->aluts );
    4490           0 :         sched->metrics->alut_success_cnt += (uint)!serializing;
    4491           0 :       } else {
    4492           0 :         serializing = 1;
    4493           0 :       }
    4494           0 :     }
    4495           0 :   }
    4496             : 
    4497             :   /* Transactions should not have duplicate accounts.
    4498             :      https://github.com/anza-xyz/agave/blob/v3.1.11/ledger/src/blockstore_processor.rs#L778-L790 */
    4499        1541 :   fd_acct_addr_t const * imms = fd_txn_get_acct_addrs( txn, payload );
    4500 >1844*10^16 :   fd_acct_addr_t * alts = (!alt_cnt||serializing) ? NULL : sched->aluts;
    4501        1541 :   alt_cnt = alts ? alt_cnt : 0UL;
    4502        1541 :   if( FD_UNLIKELY( fd_chkdup_check( sched->chkdup, imms, imm_cnt, alts, alt_cnt ) ) ) {
    4503           0 :     FD_LOG_INFO(( "bad block: DUPLICATE_ACCOUNT, slot %lu, parent slot %lu, txn_parsed_cnt %u", block->slot, block->parent_slot, block->txn_parsed_cnt ));
    4504           0 :     return FD_SCHED_DEAD_REASON_DUPLICATE_ACCOUNT;
    4505           0 :   }
    4506             : 
    4507             :   /* The ALT accounts that could not be resolved right now are accounted
    4508             :      for when the transaction completes and late registers them. */
    4509        1541 :   ulong writable_cnt = fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE ) - fd_ulong_if( !alts, fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_ALT ), 0UL );
    4510        1541 :   if( FD_UNLIKELY( block->lthash_writable_cnt+writable_cnt>sched->lthash_txn_writers_max ) ) {
    4511           0 :     FD_LOG_INFO(( "bad block: TOO_MANY_WRITABLE_ACCOUNTS, slot %lu, parent slot %lu, txn_parsed_cnt %u, lthash_writable_cnt %u, writable_cnt %lu", block->slot, block->parent_slot, block->txn_parsed_cnt, block->lthash_writable_cnt, writable_cnt ));
    4512           0 :     return FD_SCHED_DEAD_REASON_TOO_MANY_WRITABLE_ACCOUNTS;
    4513           0 :   }
    4514             : 
    4515        1541 :   ulong bank_idx = (ulong)(block-sched->block_pool);
    4516        1541 :   fd_rdisp_neww_t neww[ fd_rdisp_neww_word_cnt ];
    4517        1541 :   ulong txn_idx  = fd_rdisp_add_txn( sched->rdisp, bank_idx, txn, payload, alts, serializing, block->lthash_inband ? NULL : neww );
    4518        1541 :   FD_TEST( txn_idx && txn_idx<sched->depth );
    4519        1541 :   block->lthash_writable_cnt += (uint)writable_cnt;
    4520        1542 :   if( FD_LIKELY( !block->lthash_inband ) ) {
    4521        1542 :     ulong first_write_cnt = fd_rdisp_neww_cnt( neww );
    4522        1542 :     sched->metrics->lthash_parsed_writable_cnt += writable_cnt;
    4523        1542 :     sched->metrics->lthash_first_write_cnt     += first_write_cnt;
    4524        1542 :     sched->metrics->lthash_coalesced_cnt       += writable_cnt-first_write_cnt;
    4525        1542 :     block->add_owed_cnt                        += (uint)first_write_cnt;
    4526        1542 :   }
    4527        1542 :   if( FD_LIKELY( block_owns_lane_map( sched, block ) ) ) {
    4528        4431 :     for( ulong iter = fd_rdisp_neww_iter_init( neww ); !fd_rdisp_neww_iter_done( iter ); iter=fd_rdisp_neww_iter_next( neww, iter ) ) {
    4529        2889 :       fd_acct_addr_t const * acct = get_acct( txn, imms, alts, (ushort)iter );
    4530        2889 :       FD_TEST( acct );
    4531        2889 :       lthash_entry_new_write_observed( sched, block, acct );
    4532        2889 :     }
    4533        1542 :   }
    4534             : 
    4535             :   /* This transaction either needs to be consumed by sigverify or PoH.
    4536             :      If either of the two are caught up, mark it for consumption.
    4537             :      Otherwise, link it to the tail of the chain. */
    4538             : 
    4539        1541 :   ulong sigverify_dispatched_cnt = (ulong)block->txn_sigverify_done_cnt + (ulong)block->txn_sigverify_in_flight_cnt;
    4540        1541 :   int   sigverify_caught_up      = sigverify_dispatched_cnt==block->txn_parsed_cnt;
    4541        1541 : #if !FD_SCHED_SKIP_POH
    4542        1541 :   fd_sched_mblk_t * parse_mblk = sched->mblk_pool + block->parse_mblk_idx;
    4543        1541 :   int poh_caught_up = parse_mblk->curr_txn_idx==block->txn_parsed_cnt;
    4544        1541 : #endif
    4545             : 
    4546        1541 :   sched->txn_info_pool[ txn_idx ].next_idx = 0U;
    4547             :   /* Link to the previous transaction. */
    4548        1541 :   if( FD_LIKELY( block->txn_idx_tail ) ) {
    4549         572 :     FD_TEST( block->txn_idx_tail<sched->depth );
    4550         572 :     sched->txn_info_pool[ block->txn_idx_tail ].next_idx = (uint)txn_idx;
    4551         572 :   }
    4552             :   /* If either consumer is caught up, update next pointer to the new
    4553             :      transaction. */
    4554        1541 :   if( FD_LIKELY( sigverify_caught_up ) ) block->txn_sigverify_next_idx = (uint)txn_idx;
    4555        1541 : #if !FD_SCHED_SKIP_POH
    4556        1541 :   if( FD_LIKELY( poh_caught_up ) ) parse_mblk->mixin_txn_idx = (uint)txn_idx;
    4557        1541 : #endif
    4558        1541 :   block->txn_idx_tail = (uint)txn_idx;
    4559             : 
    4560        1541 :   sched->metrics->txn_parsed_cnt++;
    4561        1541 :   sched->metrics->alut_serializing_cnt += (uint)serializing;
    4562        1541 :   sched->txn_pool_free_cnt--;
    4563        1541 :   fd_txn_p_t * txn_p = sched->txn_pool + txn_idx;
    4564        1541 :   txn_p->payload_sz  = (ushort)pay_sz;
    4565             : 
    4566        1541 :   txn_p->start_shred_idx = (ushort)fd_uint_min( shred_split( block, block->fec_buf_boff+sched->fec_buf_soff ), USHORT_MAX ); /* monitoring-only ushorts; saturate under bench shred counts */
    4567        1541 :   txn_p->start_shred_idx = fd_ushort_if( txn_p->start_shred_idx>0U, (ushort)(txn_p->start_shred_idx-1U), txn_p->start_shred_idx );
    4568        1541 :   txn_p->end_shred_idx = (ushort)fd_uint_min( shred_split( block, block->fec_buf_boff+sched->fec_buf_soff+(uint)pay_sz ), USHORT_MAX );
    4569             : 
    4570        1541 :   fd_memcpy( txn_p->payload, payload, pay_sz );
    4571        1541 :   fd_memcpy( TXN(txn_p),     txn,     txn_sz );
    4572        1541 :   txn_bitset_remove( sched->exec_done_set, txn_idx );
    4573        1541 :   txn_bitset_remove( sched->sigverify_done_set, txn_idx );
    4574        1541 :   txn_bitset_remove( sched->poh_mixin_done_set, txn_idx );
    4575        1541 :   sched->txn_info_pool[ txn_idx ].flags = fd_ulong_if( serializing, FD_SCHED_TXN_ALT_UNRESOLVED, 0UL );
    4576        1541 :   sched->txn_info_pool[ txn_idx ].received_ns = block->fec_completed_ns;
    4577        1541 :   sched->txn_info_pool[ txn_idx ].txn_err = 0;
    4578             : 
    4579        1541 :   sched->txn_info_pool[ txn_idx ].tick_parsed = fd_tickcount();
    4580        1541 :   sched->txn_info_pool[ txn_idx ].tick_sigverify_disp = LONG_MAX;
    4581        1541 :   sched->txn_info_pool[ txn_idx ].tick_sigverify_done = LONG_MAX;
    4582        1541 :   sched->txn_info_pool[ txn_idx ].tick_exec_disp = LONG_MAX;
    4583        1541 :   sched->txn_info_pool[ txn_idx ].tick_exec_done = LONG_MAX;
    4584        1541 :   sched->txn_info_pool[ txn_idx ].tick_load_start = LONG_MAX;
    4585        1541 :   sched->txn_info_pool[ txn_idx ].tick_check_start = LONG_MAX;
    4586        1541 :   sched->txn_info_pool[ txn_idx ].tick_exec_start = LONG_MAX;
    4587        1541 :   sched->txn_info_pool[ txn_idx ].tick_commit_start = LONG_MAX;
    4588        1541 :   sched->txn_info_pool[ txn_idx ].tick_commit_end = LONG_MAX;
    4589             : 
    4590        1541 :   sched->txn_info_pool[ txn_idx ].slot          = block->slot;
    4591        1541 :   sched->txn_info_pool[ txn_idx ].bank_seq      = ULONG_MAX;
    4592        1541 :   sched->txn_info_pool[ txn_idx ].index_in_slot  = block->txn_parsed_cnt;
    4593        1541 :   sched->txn_info_pool[ txn_idx ].exec_tile_idx           = ULONG_MAX;
    4594        1541 :   sched->txn_info_pool[ txn_idx ].sigverify_exec_tile_idx = ULONG_MAX;
    4595        1541 :   sched->txn_info_pool[ txn_idx ].compute_units_consumed = 0U;
    4596        1541 :   sched->txn_info_pool[ txn_idx ].max_compute_units = ULONG_MAX;
    4597        1541 :   sched->txn_info_pool[ txn_idx ].transaction_fee = 0UL;
    4598        1541 :   sched->txn_info_pool[ txn_idx ].priority_fee = 0UL;
    4599        1541 :   sched->txn_info_pool[ txn_idx ].tips = 0UL;
    4600        1541 :   sched->fec_buf_soff += (uint)pay_sz;
    4601        1541 :   block->txn_parsed_cnt++;
    4602             : #if FD_SCHED_SKIP_SIGVERIFY
    4603             :   txn_bitset_insert( sched->sigverify_done_set, txn_idx );
    4604             :   block->txn_sigverify_done_cnt++;
    4605             : #endif
    4606             : #if FD_SCHED_SKIP_POH
    4607             :   txn_bitset_insert( sched->poh_mixin_done_set, txn_idx );
    4608             : #endif
    4609        1541 :   block->txns_rem--;
    4610        1541 :   return FD_SCHED_DEAD_REASON_NONE;
    4611        1541 : }
    4612             : 
    4613             : #undef CHECK
    4614             : #undef CHECK_LEFT
    4615             : 
    4616             : static void
    4617           0 : dispatch_sigverify( fd_sched_t * sched, fd_sched_block_t * block, ulong bank_idx, int exec_tile_idx, fd_sched_task_t * out ) {
    4618             :   /* Dispatch transactions for sigverify in parse order. */
    4619           0 :   uint txn_idx = block->txn_sigverify_next_idx;
    4620           0 :   FD_TEST( txn_idx && txn_idx<sched->depth );
    4621           0 :   out->task_type = FD_SCHED_TT_TXN_SIGVERIFY;
    4622           0 :   out->txn_sigverify->bank_idx = bank_idx;
    4623           0 :   out->txn_sigverify->txn_idx  = txn_idx;
    4624           0 :   out->txn_sigverify->exec_idx = (ulong)exec_tile_idx;
    4625           0 :   sched->txn_info_pool[ out->txn_sigverify->txn_idx ].sigverify_exec_tile_idx = (ulong)exec_tile_idx;
    4626           0 :   sched->sigverify_ready_bitset[ 0 ] = fd_ulong_clear_bit( sched->sigverify_ready_bitset[ 0 ], exec_tile_idx );
    4627           0 :   sched->tile_to_bank_idx[ exec_tile_idx ] = bank_idx;
    4628           0 :   block->txn_sigverify_in_flight_cnt++;
    4629           0 :   block->txn_sigverify_next_idx = sched->txn_info_pool[ txn_idx ].next_idx;
    4630           0 :   exec_ready_bitsets_check( sched );
    4631           0 : }
    4632             : 
    4633             : /* Retires a microblock whose PoH hashing has completed, and eagerly
    4634             :    does as much mixin as possible.  Returns FD_SCHED_DEAD_REASON_NONE on
    4635             :    success, else the dead reason. */
    4636             : static int
    4637           0 : poh_retire_mblk( fd_sched_t * sched, fd_sched_block_t * block, uint mblk_idx ) {
    4638           0 :   fd_sched_mblk_t * mblk = sched->mblk_pool+mblk_idx;
    4639           0 :   block->poh_hashing_done_cnt++;
    4640           0 :   sched->metrics->mblk_poh_hashed_cnt++;
    4641           0 :   if( FD_LIKELY( !mblk->is_tick ) ) {
    4642           0 :     mblk_slist_idx_push_tail( block->mblks_mixin_in_progress, mblk_idx, sched->mblk_pool );
    4643           0 :   } else {
    4644           0 :     block->poh_hash_cmp_done_cnt++;
    4645           0 :     sched->metrics->mblk_poh_done_cnt++;
    4646           0 :     free_mblk( sched, block, mblk_idx );
    4647           0 :     if( FD_UNLIKELY( memcmp( mblk->curr_hash, mblk->end_hash, sizeof(fd_hash_t) ) ) ) {
    4648           0 :       FD_BASE58_ENCODE_32_BYTES( mblk->curr_hash->hash, our_str );
    4649           0 :       FD_BASE58_ENCODE_32_BYTES( mblk->end_hash->hash, ref_str );
    4650           0 :       FD_LOG_INFO(( "bad block: TICK_HASH_MISMATCH, mblk %u, ours %s, claimed %s, slot %lu, parent slot %lu", mblk_idx, our_str, ref_str, block->slot, block->parent_slot ));
    4651           0 :       return FD_SCHED_DEAD_REASON_TICK_HASH_MISMATCH;
    4652           0 :     }
    4653           0 :   }
    4654           0 :   int mixin_res;
    4655           0 :   while( (mixin_res=maybe_mixin( sched, block )) ) {
    4656           0 :     if( FD_UNLIKELY( mixin_res==-1 ) ) return FD_SCHED_DEAD_REASON_ENTRY_HASH_MISMATCH;
    4657           0 :     FD_TEST( mixin_res==1||mixin_res==2 );
    4658           0 :   }
    4659           0 :   return FD_SCHED_DEAD_REASON_NONE;
    4660           0 : }
    4661             : 
    4662             : /* Assembles a PoH task for the given exec tile.  Assumes there is a
    4663             :    PoH task available for dispatching.  A tick needs one hash and a
    4664             :    transaction microblock none (its hash is the mixin), so a batch holds
    4665             :    only one kind.  A transaction microblock still dispatches, as a
    4666             :    degenerate zero-hashcnt task, so that it retires through
    4667             :    fd_sched_task_done like any other PoH task. */
    4668             : static void
    4669           0 : dispatch_poh( fd_sched_t * sched, fd_sched_block_t * block, ulong bank_idx, int exec_tile_idx, fd_sched_task_t * out ) {
    4670           0 :   fd_sched_poh_hash_t * poh = out->poh_hash;
    4671           0 :   ulong cnt     = 0UL;
    4672           0 :   ulong hashcnt = 0UL;
    4673           0 :   while( cnt<FD_SCHED_POH_PARA && block->mblk_unhashed_cnt ) {
    4674           0 :     uint mblk_idx = (uint)mblk_slist_idx_peek_head( block->mblks_unhashed, sched->mblk_pool );
    4675           0 :     fd_sched_mblk_t * mblk = sched->mblk_pool+mblk_idx;
    4676           0 :     ulong mblk_hashcnt = (ulong)mblk->is_tick;
    4677           0 :     if( FD_UNLIKELY( cnt && mblk_hashcnt!=hashcnt ) ) break;
    4678           0 :     mblk_slist_idx_pop_head( block->mblks_unhashed, sched->mblk_pool );
    4679           0 :     block->mblk_unhashed_cnt--;
    4680           0 :     hashcnt = mblk_hashcnt;
    4681           0 :     poh->mblk_idx[ cnt ] = mblk_idx;
    4682           0 :     memcpy( poh->hash+cnt, mblk->curr_hash, sizeof(fd_hash_t) );
    4683           0 :     cnt++;
    4684           0 :     block->poh_mblk_in_flight_cnt++;
    4685           0 :   }
    4686           0 :   FD_TEST( cnt ); /* poh_hashing_queued_cnt>0 implies at least one queued microblock. */
    4687             : 
    4688           0 :   out->task_type = FD_SCHED_TT_POH_HASH;
    4689           0 :   poh->bank_idx  = bank_idx;
    4690           0 :   poh->exec_idx  = (ulong)exec_tile_idx;
    4691           0 :   poh->cnt       = cnt;
    4692           0 :   poh->hashcnt   = hashcnt;
    4693           0 :   sched->poh_inflight[ exec_tile_idx ] = *poh;
    4694             : 
    4695           0 :   sched->poh_ready_bitset[ 0 ] = fd_ulong_clear_bit( sched->poh_ready_bitset[ 0 ], exec_tile_idx );
    4696           0 :   sched->tile_to_bank_idx[ exec_tile_idx ] = bank_idx;
    4697           0 :   block->poh_hashing_in_flight_cnt++;
    4698           0 :   exec_ready_bitsets_check( sched );
    4699           0 : }
    4700             : 
    4701             : /* Does up to one transaction mixin.  Returns 1 if one mixin was done, 2
    4702             :    if that mixin also completed a microblock, 0 if no transaction mixin
    4703             :    was available, -1 if there is a PoH verify error. */
    4704             : FD_WARN_UNUSED static int
    4705        4555 : maybe_mixin( fd_sched_t * sched, fd_sched_block_t * block ) {
    4706        4555 :   if( FD_UNLIKELY( mblk_slist_is_empty( block->mblks_mixin_in_progress, sched->mblk_pool ) ) ) return 0;
    4707           1 :   FD_TEST( block->poh_hashing_done_cnt-block->poh_hash_cmp_done_cnt>0 );
    4708             : 
    4709             :   /* The microblock we would like to do mixin on is at the head of the
    4710             :      queue.  It may have had some mixin, it may have never had any
    4711             :      mixin.  In the case of the former, we should continue to mixin the
    4712             :      same head microblock until it's done, lest the per-block bmtree
    4713             :      gets clobbered when we start a new one. */
    4714           1 :   ulong mblk_idx = mblk_slist_idx_pop_head( block->mblks_mixin_in_progress, sched->mblk_pool );
    4715           1 :   fd_sched_mblk_t * mblk = sched->mblk_pool+mblk_idx;
    4716             : 
    4717           1 :   if( FD_UNLIKELY( mblk->end_txn_idx>block->txn_parsed_cnt ) ) {
    4718             :     /* A partially parsed microblock is by definition at the end of the
    4719             :        FEC stream.  If such a microblock is in progress, there should be
    4720             :        no other microblock in this block so far that hasn't been
    4721             :        dispatched, because microblocks are dispatched in parse order. */
    4722           0 :     if( FD_UNLIKELY( block->mblk_unhashed_cnt ) ) {
    4723           0 :       sched->print_buf_sz = 0UL;
    4724           0 :       print_all( sched, block );
    4725           0 :       FD_LOG_CRIT(( "invariant violation end_txn_idx %lu: %s", mblk->end_txn_idx, sched->print_buf ));
    4726           0 :     }
    4727             : 
    4728             :     /* If we've decided to start mixin on a partially parsed microblock,
    4729             :        there better be nothing else in-progress.  Otherwise, they might
    4730             :        clobber the per-block bmtree for mixin. */
    4731           0 :     if( FD_UNLIKELY( mblk->curr_txn_idx!=mblk->start_txn_idx && (block->poh_mblk_in_flight_cnt||!mblk_slist_is_empty( block->mblks_mixin_in_progress, sched->mblk_pool )) ) ) {
    4732           0 :       sched->print_buf_sz = 0UL;
    4733           0 :       print_all( sched, block );
    4734           0 :       FD_LOG_CRIT(( "invariant violation end_txn_idx %lu start_txn_idx %lu curr_txn_idx %lu: %s", mblk->end_txn_idx, mblk->start_txn_idx, mblk->curr_txn_idx, sched->print_buf ));
    4735           0 :     }
    4736           0 :   }
    4737             : 
    4738             :   /* Very rarely, we've finished hashing, but not all transactions in
    4739             :      the microblock have been parsed out.  This can happen if we haven't
    4740             :      received all the FEC sets for this microblock.  We can't yet fully
    4741             :      mixin the microblock.  So we'll stick it back into the end of the
    4742             :      queue, and try to see if there's a fully parsed microblock.
    4743             :      Unless, there's truly nothing else to mixin.  Then we would start
    4744             :      mixin with the partially parsed microblock.  We do this because the
    4745             :      txn pool is meant to be an OOO scheduling window not tied to
    4746             :      max_live_slots sizing requirements, so there shouldn't be a way for
    4747             :      external input to tie up txn pool entries for longer than
    4748             :      necessary. */
    4749           1 :   if( FD_UNLIKELY( mblk->curr_txn_idx>=block->txn_parsed_cnt || /* Nothing more to mixin for this microblock. */
    4750           1 :                    (mblk->end_txn_idx>block->txn_parsed_cnt &&  /* There is something to mixin, but the microblock isn't fully parsed yet ... */
    4751           1 :                     mblk->curr_txn_idx==mblk->start_txn_idx &&  /* ... and we haven't started mixin on it yet ... */
    4752           1 :                     (block->poh_mblk_in_flight_cnt ||           /* ... and another microblock is in-progress and might preempt this microblock and clobber the bmtree, so we shouldn't start the partial microblock just yet.  Counting microblocks rather than tasks matters: a batched PoH task is retired one microblock at a time, and the ones not yet retired are off every list. */
    4753           1 :                      !mblk_slist_is_empty( block->mblks_mixin_in_progress, sched->mblk_pool ))) ) ) {
    4754           0 :     mblk_slist_idx_push_tail( block->mblks_mixin_in_progress, mblk_idx, sched->mblk_pool );
    4755             : 
    4756             :     /* No other microblock in the mixin queue. */
    4757           0 :     if( FD_UNLIKELY( block->poh_hashing_done_cnt-block->poh_hash_cmp_done_cnt==1 ) ) return 0;
    4758             : 
    4759             :     /* At this point, there's at least one more microblock in the mixin
    4760             :        queue we could try.  It's a predecessor (in parse order) that
    4761             :        finished hashing later than the partially parsed microblock at
    4762             :        the head of the mixin queue. */
    4763             : 
    4764             :     /* It should never clobber the bmtree for a microblock that has had some mixin done on it. */
    4765           0 :     if( FD_UNLIKELY( mblk->curr_txn_idx!=mblk->start_txn_idx ) ) {
    4766           0 :       sched->print_buf_sz = 0UL;
    4767           0 :       print_all( sched, block );
    4768           0 :       FD_LOG_CRIT(( "invariant violation curr_txn_idx %lu start_txn_idx %lu: %s", mblk->curr_txn_idx, mblk->start_txn_idx, sched->print_buf ));
    4769           0 :     }
    4770             : 
    4771           0 :     mblk_idx = mblk_slist_idx_pop_head( block->mblks_mixin_in_progress, sched->mblk_pool );
    4772           0 :     mblk = sched->mblk_pool+mblk_idx;
    4773             : 
    4774             :     /* It should be a fresh microblock for mixin. */
    4775           0 :     FD_TEST( mblk->curr_txn_idx==mblk->start_txn_idx );
    4776             :     /* Invariant: at any given point in time, there can be at most one
    4777             :        microblock that hasn't been fully parsed yet, due to the nature
    4778             :        of sequential parsing.  So this microblock has to be fully
    4779             :        parsed. */
    4780           0 :     FD_TEST( mblk->end_txn_idx<=block->txn_parsed_cnt );
    4781           0 :   }
    4782             : 
    4783           1 :   FD_TEST( mblk->curr_txn_idx<mblk->end_txn_idx );
    4784             : 
    4785             :   /* Now mixin. */
    4786           1 :   if( FD_LIKELY( mblk->curr_txn_idx==mblk->start_txn_idx ) ) block->bmtree = fd_bmtree_commit_init( block->bmtree_mem, 32UL, 1UL, 0UL ); /* Optimize for single-transaction microblocks, which are the majority. */
    4787             : 
    4788           1 :   ulong txn_gidx = mblk->mixin_txn_idx;
    4789           1 :   uint next_txn_pool_idx = sched->txn_info_pool[ txn_gidx ].next_idx;
    4790           1 :   fd_txn_p_t * _txn = sched->txn_pool+txn_gidx;
    4791           1 :   fd_txn_t * txn = TXN(_txn);
    4792           1 :   for( ulong j=0; j<txn->signature_cnt; j++ ) {
    4793           0 :     fd_bmtree_node_t node[ 1 ];
    4794           0 :     fd_bmtree_hash_leaf( node, _txn->payload+txn->signature_off+FD_TXN_SIGNATURE_SZ*j, 64UL, 1UL );
    4795           0 :     fd_bmtree_commit_append( block->bmtree, node, 1UL );
    4796           0 :     mblk->curr_sig_cnt++;
    4797           0 :   }
    4798             : 
    4799             :   /* Release the txn_idx. */
    4800           1 :   txn_bitset_insert( sched->poh_mixin_done_set, txn_gidx );
    4801           1 :   sched->metrics->txn_mixin_done_cnt++;
    4802           1 :   if( txn_bitset_test( sched->exec_done_set, txn_gidx ) && txn_bitset_test( sched->sigverify_done_set, txn_gidx ) ) {
    4803           0 :     if( FD_UNLIKELY( block->txn_idx_tail==(uint)txn_gidx ) ) block->txn_idx_tail = 0U;
    4804           0 :     fd_rdisp_complete_txn( sched->rdisp, txn_gidx, 1 );
    4805           0 :     sched->txn_pool_free_cnt++;
    4806           0 :     block->txn_done_cnt++;
    4807           0 :     sched->metrics->txn_done_cnt++;
    4808           0 :   }
    4809             : 
    4810           1 :   mblk->curr_txn_idx++;
    4811           1 :   mblk->mixin_txn_idx = next_txn_pool_idx;
    4812           1 :   int rv = 2;
    4813           1 :   if( FD_LIKELY( mblk->curr_txn_idx==mblk->end_txn_idx ) ) {
    4814             :     /* Ready to compute the final hash for this microblock. */
    4815           0 :     block->poh_hash_cmp_done_cnt++;
    4816           0 :     sched->metrics->mblk_poh_done_cnt++;
    4817           0 :     uchar * root = fd_bmtree_commit_fini( block->bmtree );
    4818           0 :     uchar mixin_buf[ 64 ];
    4819           0 :     fd_memcpy( mixin_buf, mblk->curr_hash, 32UL );
    4820           0 :     fd_memcpy( mixin_buf+32UL, root, 32UL );
    4821           0 :     fd_sha256_hash( mixin_buf, 64UL, mblk->curr_hash );
    4822           0 :     free_mblk( sched, block, (uint)mblk_idx );
    4823             :     /* Bypass PoH verification for fuzzing/testing throughput. */
    4824           0 :     if( FD_UNLIKELY( !sched->bypass_poh_verify && memcmp( mblk->curr_hash, mblk->end_hash, sizeof(fd_hash_t) ) ) ) {
    4825           0 :       FD_BASE58_ENCODE_32_BYTES( mblk->curr_hash->hash, our_str );
    4826           0 :       FD_BASE58_ENCODE_32_BYTES( mblk->end_hash->hash, ref_str );
    4827           0 :       FD_LOG_INFO(( "bad block: poh hash mismatch on mblk %lu, ours %s, claimed %s, txns [%lu,%lu), %u sigs, slot %lu, parent slot %lu", mblk_idx, our_str, ref_str, mblk->start_txn_idx, mblk->end_txn_idx, mblk->curr_sig_cnt, block->slot, block->parent_slot ));
    4828           0 :       return -1;
    4829           0 :     }
    4830           1 :   } else {
    4831             :     /* There are more transactions to mixin in this microblock. */
    4832           1 :     mblk_slist_idx_push_head( block->mblks_mixin_in_progress, mblk_idx, sched->mblk_pool );
    4833           1 :     rv = 1;
    4834           1 :   }
    4835             : 
    4836           1 :   return rv;
    4837           1 : }
    4838             : 
    4839             : /* Does activating bank_idx move execution to another fork?  A block
    4840             :    that descends from the last active block continues its fork, even
    4841             :    across blocks that were added done and never activated, such as a
    4842             :    leader block. */
    4843             : static int
    4844        1001 : is_fork_switch( fd_sched_t * sched, ulong bank_idx ) {
    4845        1001 :   if( FD_UNLIKELY( sched->last_active_bank_idx==ULONG_MAX ) ) return 0;
    4846           0 :   for( ulong idx=bank_idx; idx!=ULONG_MAX; idx=block_pool_ele( sched, idx )->parent_idx ) {
    4847           0 :     if( idx==sched->last_active_bank_idx ) return 0;
    4848           0 :   }
    4849           0 :   return 1;
    4850           0 : }
    4851             : 
    4852             : static void
    4853        1003 : try_activate_block( fd_sched_t * sched ) {
    4854             :   /* Early return if there's already an active block. */
    4855        1003 :   if( FD_LIKELY( sched->active_bank_idx!=ULONG_MAX ) ) return;
    4856             : 
    4857             :   /* See if there are any allocated staging lanes that we can activate
    4858             :      for scheduling ... */
    4859        1003 :   ulong staged_bitset = sched->staged_bitset;
    4860        1003 :   while( staged_bitset ) {
    4861        1001 :     int lane_idx  = fd_ulong_find_lsb( staged_bitset );
    4862        1001 :     staged_bitset = fd_ulong_pop_lsb( staged_bitset );
    4863             : 
    4864        1001 :     ulong              head_idx     = sched->staged_head_bank_idx[ lane_idx ];
    4865        1001 :     fd_sched_block_t * head_block   = block_pool_ele( sched, head_idx );
    4866        1001 :     fd_sched_block_t * parent_block = block_pool_ele( sched, head_block->parent_idx );
    4867             :     //FIXME: restore these invariant checks when we have immediate demotion of dying blocks
    4868             :     //Today, dying blocks can remain staged if they have in-flight transactions.
    4869             :     // if( FD_UNLIKELY( parent_block->dying ) ) {
    4870             :     //   /* Invariant: no child of a dying block should be staged. */
    4871             :     //   FD_LOG_CRIT(( "invariant violation: staged_head_bank_idx %lu, slot %lu, parent slot %lu on lane %d has parent_block->dying set, slot %lu, parent slot %lu",
    4872             :     //                 head_idx, head_block->slot, head_block->parent_slot, lane_idx, parent_block->slot, parent_block->parent_slot ));
    4873             :     // }
    4874             :     // if( FD_UNLIKELY( head_block->dying ) ) {
    4875             :     //   /* Invariant: no dying block should be staged. */
    4876             :     //   FD_LOG_CRIT(( "invariant violation: staged_head_bank_idx %lu, slot %lu, prime %lu on lane %u has head_block->dying set",
    4877             :     //                 head_idx, (ulong)head_block->block_id.slot, (ulong)head_block->block_id.prime, lane_idx ));
    4878             :     // }
    4879        1001 :     if( block_is_done( parent_block ) && block_is_activatable( head_block ) ) {
    4880             :       /* ... Yes, on this staging lane the parent block is done.  So we
    4881             :          can activate the staged child. */
    4882        1001 :       if( FD_UNLIKELY( is_fork_switch( sched, head_idx ) ) ) { /* Forking is rare. */
    4883           0 :         FD_LOG_DEBUG(( "activating block %lu:%lu: fork switch to lane %d", head_block->slot, head_idx, lane_idx ));
    4884           0 :         sched->metrics->lane_switch_cnt++;
    4885        1001 :       } else {
    4886        1001 :         FD_LOG_DEBUG(( "activating block %lu:%lu on lane %d", head_block->slot, head_idx, lane_idx ));
    4887        1001 :       }
    4888        1001 :       active_bank_set( sched, head_idx );
    4889        1001 :       return;
    4890        1001 :     }
    4891        1001 :   }
    4892             : 
    4893             :   /* ... No lane head with a done parent has real work.  Wake one that
    4894             :      can speculate on the additions it owes, if the speculation pool has
    4895             :      room enough to make the switch worthwhile. */
    4896           2 :   staged_bitset = sched->staged_bitset;
    4897           2 :   while( staged_bitset ) {
    4898           0 :     int lane_idx  = fd_ulong_find_lsb( staged_bitset );
    4899           0 :     staged_bitset = fd_ulong_pop_lsb( staged_bitset );
    4900             : 
    4901           0 :     ulong              head_idx     = sched->staged_head_bank_idx[ lane_idx ];
    4902           0 :     fd_sched_block_t * head_block   = block_pool_ele( sched, head_idx );
    4903           0 :     fd_sched_block_t * parent_block = block_pool_ele( sched, head_block->parent_idx );
    4904           0 :     if( block_is_done( parent_block ) && block_is_stageable( head_block ) && block_may_speculate( sched, head_block, FD_SCHED_LTHASH_SPEC_THRESHOLD_PER_TILE*sched->exec_cnt ) ) {
    4905           0 :       FD_LOG_DEBUG(( "activating block %lu:%lu: speculation on lane %d", head_block->slot, head_idx, lane_idx ));
    4906           0 :       sched->metrics->lane_switch_cnt += (uint)is_fork_switch( sched, head_idx );
    4907           0 :       active_bank_set( sched, head_idx );
    4908           0 :       return;
    4909           0 :     }
    4910           0 :   }
    4911             : 
    4912             :   /* ... No, promote unstaged blocks. */
    4913           2 :   ulong root_idx = sched->root_idx;
    4914           2 :   if( FD_UNLIKELY( root_idx==ULONG_MAX ) ) {
    4915           0 :     FD_LOG_CRIT(( "invariant violation: root_idx==ULONG_MAX indicating fd_sched is uninitialized" ));
    4916           0 :   }
    4917             :   /* Find and stage the longest stageable unstaged fork.  This is a
    4918             :      policy decision. */
    4919           2 :   ulong depth = compute_longest_unstaged_fork( sched, root_idx );
    4920           2 :   if( FD_LIKELY( depth>0UL ) ) {
    4921           0 :     if( FD_UNLIKELY( sched->staged_bitset==fd_ulong_mask_lsb( FD_SCHED_MAX_STAGING_LANES ) ) ) {
    4922             :       /* No more staging lanes available.  All of them are occupied by
    4923             :          slow squatters.  Only empty blocks can be demoted, and so
    4924             :          blocks with in-flight transactions, including dying in-flight
    4925             :          blocks, shouldn't be demoted.  We demote all demotable lanes.
    4926             :          Demotion isn't all that expensive, since demotable blocks have
    4927             :          no transactions in them.  If a demoted block proves to be
    4928             :          active still, it'll naturally promote back into a staging lane.
    4929             : 
    4930             :          In fact, all lanes should be demotable at this point.  None of
    4931             :          the lanes have anything dispatchable, otherwise we would have
    4932             :          simply activated one of the dispatchable lanes.  None of the
    4933             :          lanes have anything in-flight either, as we allow for a grace
    4934             :          period while something is in-flight, before we deactivate any
    4935             :          block.  In principle, we could get rid of the grace period and
    4936             :          deactivate right away.  In that case, it's okay if nothing is
    4937             :          demotable at the moment, as that simply implies that all lanes
    4938             :          have in-flight tasks.  We would get another chance to try to
    4939             :          demote when the last in-flight task on any lane completes.
    4940             : 
    4941             :          A note on why this whole thing does not deadlock:
    4942             : 
    4943             :          One might reasonably wonder what happens if all the lanes are
    4944             :          non-empty, non-dead, but for some reason couldn't be activated
    4945             :          for dispatching.  We would deadlock in this case, as no lane
    4946             :          dispatches to the point of being demotable, and no unstaged
    4947             :          block can be promoted.  Such is not in fact possible.  The only
    4948             :          way a dispatchable lane can be ineligible for activation is if
    4949             :          it has a parent block that isn't done yet.  So a deadlock
    4950             :          happens when this parent block, or any of its dispatchable
    4951             :          ancestors, is unstaged.  An important invariant we maintain is
    4952             :          that a staged block can't have an unstaged stageable parent.
    4953             :          This invariant, by induction, gives us the guarantee that at
    4954             :          least one of the lanes can be activated. */
    4955           0 :       for( int l=0; l<(int)FD_SCHED_MAX_STAGING_LANES; l++ ) {
    4956             :         /* We would be able to assert that all lanes are demotable,
    4957             :            except that abandoned blocks are given no grace period for
    4958             :            deactivation.  So there could be lanes transiently occupied
    4959             :            by dying blocks that are neither demotable (due to in-flight
    4960             :            tasks) nor activatable (due to being dying).  If
    4961             :            rdisp_demote() supported non-empty blocks, then we could
    4962             :            probably restore the assertion. */
    4963           0 :         if( FD_UNLIKELY( !lane_is_demotable( sched, l ) ) ) continue;
    4964           0 :         demote_lane( sched, l );
    4965           0 :         sched->metrics->lane_demoted_cnt++;
    4966           0 :       }
    4967             :       /* We weren't able to successfully demote anything.  This is
    4968             :          likely because all lanes are occupied by dying blocks with
    4969             :          in-flight tasks.  We would get another chance to try to demote
    4970             :          when the last in-flight task on any lane completes. */
    4971           0 :       if( FD_UNLIKELY( sched->staged_bitset==fd_ulong_mask_lsb( FD_SCHED_MAX_STAGING_LANES ) ) ) return;
    4972           0 :     }
    4973           0 :     FD_TEST( sched->staged_bitset!=fd_ulong_mask_lsb( FD_SCHED_MAX_STAGING_LANES ) );
    4974           0 :     int   lane_idx      = pick_free_lane( sched );
    4975           0 :     ulong head_bank_idx = promote_from_longest_unstaged_fork( sched, root_idx, lane_idx );
    4976           0 :     if( FD_UNLIKELY( head_bank_idx==ULONG_MAX ) ) {
    4977             :       /* We found a promotable fork depth>0.  This should not happen. */
    4978           0 :       FD_LOG_CRIT(( "invariant violation: head_bank_idx==ULONG_MAX" ));
    4979           0 :     }
    4980             :     /* We don't bother with promotion unless the block is immediately
    4981             :        dispatchable.  So it's okay to set the active block here.  This
    4982             :        doesn't cause out-of-order block replay because the parent of the
    4983             :        promoted block must be done.  We can prove this by contradiction
    4984             :        as follows.  The promoted block is the shallowest promotable
    4985             :        block of its fork, so its parent is not promotable.  The parent
    4986             :        is complete, because FEC sets arrive in order along a fork and a
    4987             :        child of it has arrived.  Suppose the parent were not done.  It
    4988             :        is not dying, or the promoted block would be dying too and not
    4989             :        promotable.  It is not unstaged, or it would be promotable, being
    4990             :        complete, and so dispatchable until it ends.  So it would be
    4991             :        staged and dispatchable, with nothing in flight, since a block
    4992             :        with tasks in flight is the active block or dying, and no block
    4993             :        is active here.  That makes it activatable, and only a parent of
    4994             :        its own that is not done kept the first loop above from
    4995             :        activating it.  That parent is staged as well, because a staged
    4996             :        block never has an unstaged parent that still has to run, and the
    4997             :        same argument applies to it, and so on up the fork chain until a
    4998             :        staged block with a done parent is reached, which the first loop
    4999             :        would have activated. */
    5000           0 :     fd_sched_block_t * head_block = block_pool_ele( sched, head_bank_idx );
    5001           0 :     FD_LOG_DEBUG(( "activating block %lu:%lu: unstaged promotion to lane %d", head_block->slot, head_bank_idx, lane_idx ));
    5002           0 :     sched->metrics->lane_switch_cnt += (uint)is_fork_switch( sched, head_bank_idx );
    5003           0 :     active_bank_set( sched, head_bank_idx );
    5004           0 :     return;
    5005           0 :   }
    5006             :   /* No unstaged blocks to promote.  So we're done.  Yay. */
    5007           2 : }
    5008             : 
    5009             : static void
    5010        4555 : check_or_set_active_block( fd_sched_t * sched ) {
    5011        4555 :   if( FD_UNLIKELY( sched->active_bank_idx==ULONG_MAX ) ) {
    5012        1002 :     try_activate_block( sched );
    5013        3553 :   } else {
    5014        3553 :     fd_sched_block_t * active_block = block_pool_ele( sched, sched->active_bank_idx );
    5015        3553 :     if( FD_UNLIKELY( block_should_deactivate( sched, active_block ) ) ) {
    5016           0 :       sched->print_buf_sz = 0UL;
    5017           0 :       print_all( sched, active_block );
    5018           0 :       FD_LOG_NOTICE(( "%s", sched->print_buf ));
    5019           0 :       FD_LOG_CRIT(( "invariant violation: should have been deactivated" ));
    5020           0 :     }
    5021        3553 :   }
    5022        4555 : }
    5023             : 
    5024             : /* This function has two main jobs:
    5025             :    - Mark everything on the fork tree dying.
    5026             :    - Take blocks out of rdisp if possible. */
    5027             : static void
    5028           2 : subtree_mark_and_maybe_prune_rdisp( fd_sched_t * sched, fd_sched_block_t * block ) {
    5029           2 :   if( FD_UNLIKELY( block->rooted ) ) {
    5030           0 :     FD_LOG_CRIT(( "invariant violation: rooted block should not be abandoned, slot %lu, parent slot %lu",
    5031           0 :                   block->slot, block->parent_slot ));
    5032           0 :   }
    5033             :   /* All minority fork nodes pass through this function eventually.  So
    5034             :      this is a good point to check per-node invariants for minority
    5035             :      forks. */
    5036           2 :   if( FD_UNLIKELY( block->staged && !block->in_rdisp ) ) {
    5037           0 :     FD_LOG_CRIT(( "invariant violation: staged block is not in the dispatcher, slot %lu, parent slot %lu",
    5038           0 :                   block->slot, block->parent_slot ));
    5039           0 :   }
    5040             : 
    5041           2 :   fd_sched_block_t * parent = block_pool_ele( sched, block->parent_idx );
    5042           2 :   if( FD_UNLIKELY( !parent || !parent->in_sched ) ) {
    5043             :     /* Only the root has no parent.  Abandon should never be called on
    5044             :        the root.  So any block we are trying to abandon should have a
    5045             :        parent. */
    5046           0 :     FD_LOG_CRIT(( "invariant violation: parent not found slot %lu, parent slot %lu",
    5047           0 :                   block->slot, block->parent_slot ));
    5048           0 :   }
    5049             : 
    5050             :   /* Gated on the parent going down and on this block not already being
    5051             :      on its way down.
    5052             : 
    5053             :      The parent test is because this function also runs on the block the
    5054             :      public abandon API was called on, whose parent is very much alive.
    5055             :      Inheriting there would stamp DEAD_ANCESTOR on a block that went
    5056             :      down on its own, and overwrite the discarded bit the caller just
    5057             :      set with the live parent's 0.
    5058             : 
    5059             :      The block test is because the walk also revisits blocks that
    5060             :      already went down, and an ancestor abandoned later must not
    5061             :      re-label them.  This cannot be decided based on dead_reason alone
    5062             :      because a verdict the replay tile made leaves it NONE.
    5063             : 
    5064             :      Descendants work fine either way, because dying is set below before
    5065             :      we recurse, so every child rightfully sees a dying parent. */
    5066           2 :   if( FD_UNLIKELY( parent->dying && !block->dying ) ) record_lineage_death( block, parent );
    5067             : 
    5068             :   /* Setting the flag is non-optional and can happen more than once. */
    5069           2 :   block->dying = 1;
    5070           2 :   slot_state_close( sched, block );
    5071           2 :   lthash_release_held_batch( sched, block );
    5072             : 
    5073             :   /* Removal from dispatcher should only happen once. */
    5074           2 :   if( block->in_rdisp ) {
    5075             : 
    5076             :     /* The dispatcher expects blocks to be abandoned in the same order
    5077             :        that they were added on each lane, which holds trivially with
    5078             :        every staged block having its own lane. */
    5079           2 :     if( FD_UNLIKELY( block->staged && sched->active_bank_idx==sched->staged_head_bank_idx[ block->staging_lane ] && sched->active_bank_idx!=ULONG_MAX ) ) {
    5080           2 :       FD_TEST( block_pool_ele( sched, sched->active_bank_idx )==block );
    5081           2 :       FD_LOG_DEBUG(( "reset active_bank_idx %lu: abandon", sched->active_bank_idx ));
    5082           2 :       sched->last_active_bank_idx = sched->active_bank_idx;
    5083           2 :       active_bank_set( sched, ULONG_MAX );
    5084           2 :       sched->metrics->deactivate_abandoned_cnt++;
    5085           2 :     }
    5086             : 
    5087             :     /* We inform the dispatcher of an abandon only when there are no
    5088             :        more in-flight transactions.  Otherwise, if the dispatcher
    5089             :        recycles the same txn_id that was just abandoned, and we receive
    5090             :        completion of an in-flight transaction whose txn_id was just
    5091             :        recycled. */
    5092             :     // FIXME The recycling might be fine now that we no longer use
    5093             :     // txn_id to index into anything.  We might be able to just drop
    5094             :     // txn_id on abandoned blocks.  Though would this leak transaction
    5095             :     // content if the txn_id is recycled?
    5096             :     // Note that subtree pruning from sched isn't dependent on the
    5097             :     // in-flight check being present here, as is_prunable already checks
    5098             :     // for in-flight==0.
    5099           2 :     int abandon = !block_is_in_flight( block );
    5100             : 
    5101           2 :     if( abandon ) {
    5102           2 :       block->in_rdisp = 0;
    5103           2 :       sched->metrics->lthash_discarded_add_owed_cnt    += block->add_owed_cnt;
    5104           2 :       sched->metrics->lthash_discarded_add_pending_cnt += block->add_created_cnt-block->add_surfaced_cnt;
    5105           2 :       sched->metrics->lthash_discarded_sub_queued_cnt  += block->sub_queued_cnt;
    5106           2 :       sched->metrics->lthash_discarded_sub_applied_cnt += block->sub_applied_cnt;
    5107           2 :       sched->metrics->lthash_discarded_add_applied_cnt += block->add_applied_cnt-block->add_undone_cnt;
    5108           2 :       lthash_block_reset( sched, block );
    5109           2 :       lthash_release_reservation( sched, block );
    5110           2 :       fd_rdisp_abandon_block( sched->rdisp, (ulong)(block-sched->block_pool) );
    5111           2 :       block->txn_idx_tail = 0U;
    5112           2 :       sched->txn_pool_free_cnt += block->txn_parsed_cnt-block->txn_done_cnt; /* in_flight_cnt==0 */
    5113             : 
    5114           2 :       sched->metrics->block_abandoned_cnt++;
    5115           2 :       sched->metrics->txn_abandoned_parsed_cnt    += block->txn_parsed_cnt;
    5116           2 :       sched->metrics->txn_abandoned_exec_done_cnt += block->txn_exec_done_cnt;
    5117           2 :       sched->metrics->txn_abandoned_done_cnt      += block->txn_done_cnt;
    5118             : 
    5119             :       //FIXME when demote supports non-empty blocks, we should demote
    5120             :       //the block from the lane unconditionally and immediately,
    5121             :       //regardless of whether it's safe to abandon or not.  So a block
    5122             :       //would go immediately from staged to unstaged and eventually to
    5123             :       //abandoned.
    5124           2 :       if( FD_LIKELY( block->staged ) ) {
    5125           2 :         FD_LOG_DEBUG(( "block %lu:%lu exited lane %lu: abandon", block->slot, block_to_idx( sched, block ), block->staging_lane ));
    5126           2 :         block->staged = 0;
    5127             :         /* Release the staging lane, which the block had to itself. */
    5128           2 :         sched->staged_bitset = fd_ulong_clear_bit( sched->staged_bitset, (int)block->staging_lane );
    5129           2 :         sched->staged_head_bank_idx[ block->staging_lane ] = ULONG_MAX;
    5130           2 :       }
    5131           2 :     }
    5132           2 :   }
    5133             : 
    5134             :   /* Abandon the entire fork chaining off of this block. */
    5135           2 :   ulong child_idx = block->child_idx;
    5136           2 :   while( child_idx!=ULONG_MAX ) {
    5137           0 :     fd_sched_block_t * child = block_pool_ele( sched, child_idx );
    5138           0 :     subtree_mark_and_maybe_prune_rdisp( sched, child );
    5139           0 :     child_idx = child->sibling_idx;
    5140           0 :   }
    5141           2 : }
    5142             : 
    5143             : /* This function tells sched that the subtree is no longer worth
    5144             :    replaying.  It can happen as a result of one of the following.
    5145             :      - Bad block at head of lane.
    5146             :      - Bad block at tail of lane.
    5147             :      - Root notify.
    5148             : 
    5149             :    It's safe to call this function more than once on the same block. */
    5150             : static void
    5151           2 : subtree_abandon( fd_sched_t * sched, fd_sched_block_t * block ) {
    5152           2 :   subtree_mark_and_maybe_prune_rdisp( sched, block );
    5153             :   /* We do not gate refcnt releases on the entire subtree being
    5154             :      prunable.  In the root notify case there can be in-flight tasks in
    5155             :      the middle of a subtree, and gating on whole-subtree prunability
    5156             :      would defer the release of an otherwise prunable block, e.g. an
    5157             :      inactive sibling of an in-flight block, to a later abandon such as
    5158             :      a subsequent root notify.  That could stall bank reclamation.
    5159             : 
    5160             :      The sched fork tree still stays in sync with the banks tree.  This
    5161             :      is to prevent attacks targeting lifetime discrepancy.  So while we
    5162             :      know that the subtree can be pruned from sched at this point, we
    5163             :      only release refcnts here and stop short of actually pruning, which
    5164             :      remains solely initiated by banks. */
    5165           2 :   subtree_release_refcnt( sched, block );
    5166           2 : }
    5167             : 
    5168             : /* Release the bank refcnt for every block in the subtree that sched is
    5169             :    done with.  This is evaluated per block.  A block's refcnt is
    5170             :    released as soon as that block individually qualifies, independent of
    5171             :    whether its siblings or descendants still have in-flight tasks.  This
    5172             :    is safe because the actual pruning is initiated separately by banks,
    5173             :    and gated on the whole subtree's refcnts reaching zero.  Releasing
    5174             :    refcnts eagerly per block get us there sooner.  Blocks that are still
    5175             :    in-flight will be released when their last task drains.  This
    5176             :    function is idempotent. */
    5177             : static void
    5178           2 : subtree_release_refcnt( fd_sched_t * sched, fd_sched_block_t * block ) {
    5179           2 :   FD_TEST( block->in_sched );
    5180           2 :   if( block->refcnt && block_is_prunable( block ) ) {
    5181           2 :     FD_TEST( block->dying ); /* The happy path releases the refcnt on full replay.  Only bad blocks end up here. */
    5182           2 :     FD_TEST( !block->block_end_done ); /* Implied by an outstanding refcnt.  The happy path releases the refcnt on full replay. */
    5183           2 :     block->refcnt = 0;
    5184           2 :     FD_TEST( ref_q_avail( sched->ref_q ) );
    5185           2 :     ref_q_push_tail( sched->ref_q, block_to_idx( sched, block ) );
    5186           2 :     if( FD_LIKELY( block->block_start_done ) ) FD_LOG_DEBUG(( "block %lu:%lu replayed partially, releasing refcnt without full replay", block->slot, block_to_idx( sched, block ) ));
    5187           2 :     else FD_LOG_DEBUG(( "block %lu:%lu replayed nothing, releasing refcnt without any replay", block->slot, block_to_idx( sched, block ) ));
    5188           2 :   }
    5189             : 
    5190           2 :   ulong child_idx = block->child_idx;
    5191           2 :   while( child_idx!=ULONG_MAX ) {
    5192           0 :     fd_sched_block_t * child_block = block_pool_ele( sched, child_idx );
    5193           0 :     subtree_release_refcnt( sched, child_block );
    5194           0 :     child_idx = child_block->sibling_idx;
    5195           0 :   }
    5196           2 : }
    5197             : 
    5198             : static void
    5199           0 : subtree_prune( fd_sched_t * sched, ulong bank_idx, ulong except_idx ) {
    5200           0 :   fd_sched_block_t * head = block_pool_ele( sched, bank_idx );
    5201           0 :   head->parent_idx        = ULONG_MAX;
    5202           0 :   fd_sched_block_t * tail = head;
    5203             : 
    5204           0 :   while( head ) {
    5205           0 :     FD_TEST( !head->refcnt );
    5206           0 :     FD_TEST( head->in_sched );
    5207           0 :     head->in_sched = 0;
    5208           0 :     slot_state_close( sched, head );
    5209           0 :     if( FD_UNLIKELY( block_to_idx( sched, head )==sched->last_active_bank_idx ) ) sched->last_active_bank_idx = ULONG_MAX; /* Don't count a lane switch if the last active block has been pruned. */
    5210             : 
    5211           0 :     ulong child_idx = head->child_idx;
    5212           0 :     while( child_idx!=ULONG_MAX ) {
    5213           0 :       fd_sched_block_t * child = block_pool_ele( sched, child_idx );
    5214             :       /* Add children to be visited.  We abuse the parent_idx field to
    5215             :          link up the next block to visit. */
    5216           0 :       if( child_idx!=except_idx ) {
    5217           0 :         tail->parent_idx = child_idx;
    5218           0 :         tail             = child;
    5219           0 :         tail->parent_idx = ULONG_MAX;
    5220           0 :       }
    5221           0 :       child_idx = child->sibling_idx;
    5222           0 :     }
    5223             : 
    5224             :     /* Prune the current block.  We will never publish halfway into a
    5225             :        staging lane, because anything that we are publishing away should
    5226             :        be out of the dispatcher at this point, much less staged.
    5227             :          - Anything on the rooted fork should have finished replaying
    5228             :            gracefully and be out of the dispatcher.
    5229             :          - Anything on minority forks should have been marked dying and
    5230             :            be taken out of the dispatcher.
    5231             : 
    5232             :        There should be no more in-flight tasks either.  Prunes are
    5233             :        initiated by banks, and the refcnt on the bank won't drop to zero
    5234             :        unless all in-flight tasks have been drained.  So the fact that
    5235             :        we're pruning implies that the bank thinks there's nothing more
    5236             :        in-flight. */
    5237           0 :     if( FD_UNLIKELY( block_is_in_flight( head ) ) ) {
    5238           0 :       FD_LOG_CRIT(( "invariant violation: block has tasks in flight (%u exec %u sigverify %u poh %u cert %u lthash sub, %u/%u lthash add surfaced/done, %u lthash tasks), slot %lu, parent slot %lu",
    5239           0 :                     head->txn_exec_in_flight_cnt, head->txn_sigverify_in_flight_cnt, head->poh_hashing_in_flight_cnt, head->footer_in_flight_cnt, head->sub_in_flight_cnt, head->add_surfaced_cnt, head->add_done_cnt, head->lthash_in_flight_cnt, head->slot, head->parent_slot ));
    5240           0 :     }
    5241           0 :     if( FD_UNLIKELY( head->in_rdisp ) ) {
    5242             :       /* We should have removed it from the dispatcher when we were
    5243             :          notified of the new root, or when in-flight transactions were
    5244             :          drained. */
    5245           0 :       FD_LOG_CRIT(( "invariant violation: block is in the dispatcher, slot %lu, parent slot %lu", head->slot, head->parent_slot ));
    5246           0 :     }
    5247             : 
    5248             :     /* Return remaining mblk descriptors to the shared pool. */
    5249           0 :     free_mblk_slist( sched, head, head->mblks_unhashed );
    5250           0 :     free_mblk_slist( sched, head, head->mblks_mixin_in_progress );
    5251             : 
    5252           0 :     if( FD_UNLIKELY( !head->block_end_done ) ) {
    5253           0 :       sched->print_buf_sz = 0UL;
    5254           0 :       print_block_metrics( sched, head );
    5255           0 :       if( FD_LIKELY( head->block_start_done ) ) FD_LOG_DEBUG(( "block %lu:%lu replayed partially, pruning without full replay: %s", head->slot, block_to_idx( sched, head ), sched->print_buf ));
    5256           0 :       else FD_LOG_DEBUG(( "block %lu:%lu replayed nothing, pruning without any replay: %s", head->slot, block_to_idx( sched, head ), sched->print_buf ));
    5257           0 :     }
    5258             : 
    5259           0 :     sched->block_pool_popcnt--;
    5260             : 
    5261           0 :     fd_sched_block_t * next = block_pool_ele( sched, head->parent_idx );
    5262             : 
    5263             :     /* We don't have to clear the indices here since no one should be
    5264             :        accessing them.  Defensive programming. */
    5265           0 :     head->parent_idx  = ULONG_MAX;
    5266           0 :     head->child_idx   = ULONG_MAX;
    5267           0 :     head->sibling_idx = ULONG_MAX;
    5268             : 
    5269           0 :     head = next;
    5270           0 :   }
    5271           0 : }
    5272             : 
    5273             : static void
    5274           0 : maybe_switch_block( fd_sched_t * sched, ulong bank_idx ) {
    5275             :   /* This only happens rarely when there are dying in-flight blocks.
    5276             :      Early exit and don't let dying blocks affect replay. */
    5277           0 :   if( FD_UNLIKELY( bank_idx!=sched->active_bank_idx ) ) return;
    5278             : 
    5279           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    5280           0 :   if( FD_UNLIKELY( block_is_done( block ) ) ) {
    5281             :     /* A successfully replayed block has exactly one subtraction and one
    5282             :        addition per writable account, net of undone speculations.  This
    5283             :        is the culmination of all the out-of-band LtHash activity in the
    5284             :        block, and a cheap way for us to check count invariants. */
    5285           0 :     FD_TEST( block->sub_applied_cnt==block->add_applied_cnt-block->add_undone_cnt );
    5286           0 :     sched->metrics->lthash_frozen_cnt += block->sub_applied_cnt;
    5287           0 :     lthash_block_reset( sched, block );
    5288           0 :     lthash_release_reservation( sched, block );
    5289           0 :     fd_rdisp_remove_block( sched->rdisp, bank_idx );
    5290           0 :     FD_LOG_DEBUG(( "block %lu:%lu exited lane %lu: remove", block->slot, bank_idx, block->staging_lane ));
    5291           0 :     block->in_rdisp = 0;
    5292           0 :     block->staged   = 0;
    5293           0 :     sched->metrics->block_removed_cnt++;
    5294           0 :     FD_LOG_DEBUG(( "reset active_bank_idx %lu: remove", sched->active_bank_idx ));
    5295           0 :     sched->last_active_bank_idx = sched->active_bank_idx;
    5296           0 :     active_bank_set( sched, ULONG_MAX );
    5297             : 
    5298             :     /* Every staged block has a lane to itself, including this block.
    5299             :        Release the lane and look for the next block to run, which is
    5300             :        typically a child on another lane. */
    5301           0 :     sched->staged_bitset = fd_ulong_clear_bit( sched->staged_bitset, (int)block->staging_lane );
    5302           0 :     sched->staged_head_bank_idx[ block->staging_lane ] = ULONG_MAX;
    5303           0 :     try_activate_block( sched );
    5304             :     /* The common case activates a child of the block that just
    5305             :        finished.  Count the uncommon cases. */
    5306           0 :     int child_activated = sched->active_bank_idx!=ULONG_MAX && block_pool_ele( sched, sched->active_bank_idx )->parent_idx==bank_idx;
    5307           0 :     sched->metrics->deactivate_no_child_cnt += (uint)!child_activated;
    5308           0 :   } else if( block_should_deactivate( sched, block ) ) {
    5309             :     /* We exhausted the active block, but it's not fully done yet.  We
    5310             :        are just not getting FEC sets for it fast enough.  This could
    5311             :        happen when the network path is congested, or when the leader
    5312             :        simply went down.  Reset the active block. */
    5313           0 :     sched->last_active_bank_idx = sched->active_bank_idx;
    5314           0 :     active_bank_set( sched, ULONG_MAX );
    5315             :     /* Nothing of the block is in flight, so no owed account is waiting
    5316             :        on a writer.  A block that still owes additions could not
    5317             :        speculate on them because the speculation pool is out of entries,
    5318             :        or because every owed account is over the hotness threshold and
    5319             :        the block has nothing it may run.  The remaining reason not to
    5320             :        speculate, fec_eos, never reaches this branch.  A block with
    5321             :        fec_eos is dispatched to finish, and then it leaves through the
    5322             :        branch above. */
    5323           0 :     if( block->add_owed_cnt && !sched->lthash_spec_free_cnt ) sched->metrics->lthash_pool_full_cnt++;
    5324           0 :     else                                                      sched->metrics->deactivate_no_txn_cnt++;
    5325           0 :     try_activate_block( sched );
    5326           0 :   }
    5327           0 : }
    5328             : 
    5329             : /* Returns length of the longest stageable unstaged fork, if there is
    5330             :    one, and 0 otherwise. */
    5331             : static ulong
    5332           4 : compute_longest_unstaged_fork( fd_sched_t * sched, ulong bank_idx ) {
    5333           4 :   if( FD_UNLIKELY( bank_idx==ULONG_MAX ) ) {
    5334           0 :     FD_LOG_CRIT(( "invariant violation: bank_idx==ULONG_MAX" ));
    5335           0 :   }
    5336             : 
    5337           4 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    5338             : 
    5339           4 :   ulong max_child_depth = 0UL;
    5340           4 :   ulong child_idx       = block->child_idx;
    5341           6 :   while( child_idx!=ULONG_MAX ) {
    5342           2 :     ulong child_depth = compute_longest_unstaged_fork( sched, child_idx );
    5343           2 :     if( child_depth > max_child_depth ) {
    5344           0 :       max_child_depth = child_depth;
    5345           0 :     }
    5346           2 :     fd_sched_block_t * child = block_pool_ele( sched, child_idx );
    5347           2 :     child_idx = child->sibling_idx;
    5348           2 :   }
    5349             : 
    5350           4 :   block->luf_depth = max_child_depth + fd_ulong_if( block_is_promotable( block ), 1UL, 0UL );
    5351           4 :   return block->luf_depth;
    5352           4 : }
    5353             : 
    5354             : /* Promotes one block onto lane_idx, the shallowest promotable block of
    5355             :    the longest stageable unstaged fork under bank_idx, and returns its
    5356             :    index, or ULONG_MAX if there is no such block.  Assumes
    5357             :    compute_longest_unstaged_fork() ran on the fork tree since it last
    5358             :    changed. */
    5359             : static ulong
    5360           0 : promote_from_longest_unstaged_fork( fd_sched_t * sched, ulong bank_idx, int lane_idx ) {
    5361           0 :   while( bank_idx!=ULONG_MAX ) {
    5362           0 :     fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    5363           0 :     if( FD_LIKELY( block_is_promotable( block ) ) ) {
    5364           0 :       block->staged       = 1;
    5365           0 :       block->staging_lane = (ulong)lane_idx;
    5366           0 :       fd_rdisp_promote_block( sched->rdisp, bank_idx, block->staging_lane );
    5367           0 :       sched->staged_bitset = fd_ulong_set_bit( sched->staged_bitset, lane_idx );
    5368             :       /* No need to update staged_popcnt_wmk because the fact that there
    5369             :          are unstaged blocks implies we already maxed out lanes at one
    5370             :          point. */
    5371           0 :       sched->staged_head_bank_idx[ lane_idx ] = bank_idx;
    5372           0 :       sched->metrics->block_promoted_cnt++;
    5373           0 :       sched->metrics->lane_promoted_cnt++;
    5374             :       /* We never dispatch from an unstaged block, so every transaction
    5375             :          it ingested since it was last staged got promoted. */
    5376           0 :       sched->metrics->txn_promoted_cnt += block->txn_parsed_cnt-block->txn_done_cnt;
    5377           0 :       FD_LOG_DEBUG(( "block %lu:%lu entered lane %lu: promote", block->slot, bank_idx, block->staging_lane ));
    5378             :       /* The lane map is claimed first so that the pseudo transactions
    5379             :          the drain creates land in it.  A block that hit fec_eos while
    5380             :          unstaged has not drained yet, so the polls to come drain it. */
    5381           0 :       if( FD_LIKELY( !block->lthash_inband ) ) lthash_claim_lane_map( sched, block );
    5382           0 :       lthash_drain_later( sched, block );
    5383           0 :       return bank_idx;
    5384           0 :     }
    5385             : 
    5386             :     /* Descend into the longest stageable unstaged fork. */
    5387           0 :     ulong max_depth      = 0UL;
    5388           0 :     ulong best_child_idx = ULONG_MAX;
    5389           0 :     for( ulong child_idx=block->child_idx; child_idx!=ULONG_MAX; ) {
    5390           0 :       fd_sched_block_t * child = block_pool_ele( sched, child_idx );
    5391           0 :       if( child->luf_depth>max_depth ) {
    5392           0 :         max_depth      = child->luf_depth;
    5393           0 :         best_child_idx = child_idx;
    5394           0 :       }
    5395           0 :       child_idx = child->sibling_idx;
    5396           0 :     }
    5397           0 :     bank_idx = best_child_idx;
    5398           0 :   }
    5399           0 :   return ULONG_MAX;
    5400           0 : }
    5401             : 
    5402             : static int
    5403           0 : lane_is_demotable( fd_sched_t * sched, int lane_idx ) {
    5404           0 :   fd_sched_block_t * block = block_pool_ele( sched, sched->staged_head_bank_idx[ lane_idx ] );
    5405           0 :   FD_TEST( block->staged );
    5406           0 :   FD_TEST( block->staging_lane==(ulong)lane_idx );
    5407           0 :   return block_is_demotable( block );
    5408           0 : }
    5409             : 
    5410             : static void
    5411           0 : demote_lane( fd_sched_t * sched, int lane_idx ) {
    5412           0 :   ulong              bank_idx = sched->staged_head_bank_idx[ lane_idx ];
    5413           0 :   fd_sched_block_t * block    = block_pool_ele( sched, bank_idx );
    5414           0 :   FD_TEST( block->staged );
    5415           0 :   FD_TEST( block->staging_lane==(ulong)lane_idx );
    5416             : 
    5417           0 :   lthash_demote( sched, block );
    5418           0 :   int ret = fd_rdisp_demote_block( sched->rdisp, bank_idx );
    5419           0 :   if( FD_UNLIKELY( ret!=0 ) ) {
    5420           0 :     FD_LOG_CRIT(( "fd_rdisp_demote_block failed for block %lu:%lu, lane %d", block->slot, bank_idx, lane_idx ));
    5421           0 :   }
    5422           0 :   FD_LOG_DEBUG(( "block %lu:%lu exited lane %lu: demote", block->slot, bank_idx, block->staging_lane ));
    5423           0 :   block->staged = 0;
    5424             : 
    5425           0 :   sched->staged_bitset = fd_ulong_clear_bit( sched->staged_bitset, lane_idx );
    5426           0 :   sched->staged_head_bank_idx[ lane_idx ] = ULONG_MAX;
    5427           0 :   sched->metrics->block_demoted_cnt++;
    5428           0 : }

Generated by: LCOV version 1.14