LCOV - code coverage report
Current view: top level - discof/replay - fd_sched.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 896 1880 47.7 %
Date: 2026-07-25 05:22:36 Functions: 54 74 73.0 %

          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 "fd_execrp.h" /* for poh hash value */
       6             : #include "../../util/math/fd_stat.h" /* for sorted search */
       7             : #include "../../disco/fd_disco_base.h" /* for FD_MAX_TXN_PER_SLOT */
       8             : #include "../../disco/fd_txn_p.h"
       9             : #include "../../disco/metrics/fd_metrics.h" /* for fd_metrics_convert_seconds_to_ticks and etc. */
      10             : #include "../../disco/pack/fd_chkdup.h"
      11             : #include "../../disco/shred/fd_shredder.h" /* FD_SHREDDER_CHAINED_FEC_SET_PAYLOAD_SZ */
      12             : #include "../../discof/poh/fd_poh.h" /* for MAX_SKIPPED_TICKS */
      13             : #include "../../flamenco/runtime/fd_runtime.h" /* for fd_runtime_load_txn_address_lookup_tables */
      14             : #include "../../flamenco/runtime/fd_system_ids.h"
      15             : #include "../../flamenco/runtime/sysvar/fd_sysvar_slot_hashes.h" /* for ALUTs */
      16             : 
      17         105 : #define FD_SCHED_MAX_STAGING_LANES_LOG     (2)
      18         105 : #define FD_SCHED_MAX_STAGING_LANES         (1UL<<FD_SCHED_MAX_STAGING_LANES_LOG)
      19             : #define FD_SCHED_MAX_EXEC_TILE_CNT         (64UL)
      20         186 : #define FD_SCHED_MAX_PRINT_BUF_SZ          (2UL<<20)
      21             : 
      22             : #define FD_SCHED_MAX_MBLK_PER_SLOT             (MAX_SKIPPED_TICKS)
      23           9 : #define FD_SCHED_MAX_POH_HASHES_PER_TASK       (4096UL) /* This seems to be the sweet spot. */
      24             : 
      25             : /* 64 ticks per slot, and a single gigantic microblock containing min
      26             :    size transactions. */
      27             : 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 );
      28             : 
      29             : /* We size the buffer to be able to hold residual data from the previous
      30             :    FEC set that only becomes parseable after the next FEC set is
      31             :    ingested, as well as the incoming FEC set.  The largest minimally
      32             :    parseable unit of data is a transaction.  So that much data may
      33             :    straddle FEC set boundaries.  Other minimally parseable units of data
      34             :    include the microblock header and the microblock count within a
      35             :    batch. */
      36           6 : #define FD_SCHED_MAX_PAYLOAD_PER_FEC       (63985UL)
      37             : 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. */
      38             : #define FD_SCHED_MAX_FEC_BUF_SZ            (FD_SCHED_MAX_PAYLOAD_PER_FEC+FD_TXN_MTU)
      39             : FD_STATIC_ASSERT( FD_TXN_MTU>=sizeof(fd_microblock_hdr_t), resize buffer for residual data );
      40             : FD_STATIC_ASSERT( FD_TXN_MTU>=sizeof(ulong),               resize buffer for residual data );
      41             : 
      42           3 : #define FD_SCHED_MAX_TXN_PER_FEC           ((FD_SCHED_MAX_PAYLOAD_PER_FEC-1UL)/FD_TXN_MIN_SERIALIZED_SZ+1UL) /* 478 */
      43           3 : #define FD_SCHED_MAX_MBLK_PER_FEC          ((FD_SCHED_MAX_PAYLOAD_PER_FEC-1UL)/sizeof(fd_microblock_hdr_t)+1UL) /* 1334 */
      44             : 
      45             : FD_STATIC_ASSERT( FD_SCHED_MIN_DEPTH>=FD_SCHED_MAX_TXN_PER_FEC, limits );
      46             : FD_STATIC_ASSERT( FD_SCHED_MAX_DEPTH<=FD_RDISP_MAX_DEPTH,       limits );
      47             : 
      48          12 : #define FD_SCHED_MAGIC (0xace8a79c181f89b6UL) /* echo -n "fd_sched_v0" | sha512sum | head -c 16 */
      49             : 
      50           9 : #define FD_SCHED_OK          (0)
      51          15 : #define FD_SCHED_AGAIN_LATER (1)
      52           0 : #define FD_SCHED_BAD_BLOCK   (2)
      53             : 
      54             : 
      55             : /* Structs. */
      56             : 
      57             : struct fd_sched_mblk {
      58             :   ulong start_txn_idx; /* inclusive parse idx */
      59             :   ulong end_txn_idx;   /* non-inclusive parse idx */
      60             :   ulong curr_txn_idx;  /* next txn to mixin, parse idx */
      61             :   ulong hashcnt;       /* number of pure hashes, excluding final mixin */
      62             :   ulong curr_hashcnt;
      63             :   fd_hash_t end_hash[ 1 ];
      64             :   fd_hash_t curr_hash[ 1 ];
      65             :   uint curr_sig_cnt;
      66             :   uint next;
      67             :   int is_tick;
      68             : };
      69             : typedef struct fd_sched_mblk fd_sched_mblk_t;
      70             : 
      71             : #define SLIST_NAME  mblk_slist
      72             : #define SLIST_ELE_T fd_sched_mblk_t
      73           9 : #define SLIST_IDX_T uint
      74          24 : #define SLIST_NEXT  next
      75             : #include "../../util/tmpl/fd_slist.c"
      76             : 
      77             : #define SET_NAME txn_bitset
      78             : #define SET_MAX  FD_SCHED_MAX_DEPTH
      79             : #include "../../util/tmpl/fd_set.c"
      80             : 
      81             : struct fd_sched_block {
      82             :   ulong               slot;
      83             :   ulong               parent_slot;
      84             :   ulong               parent_idx;  /* Index of the parent in the pool. */
      85             :   ulong               child_idx;   /* Index of the left-child in the pool. */
      86             :   ulong               sibling_idx; /* Index of the right-sibling in the pool. */
      87             : 
      88             :   /* Counters. */
      89             :   uint                txn_parsed_cnt;
      90             :   /*                  txn_queued_cnt = txn_parsed_cnt-txn_in_flight_cnt-txn_done_cnt */
      91             :   uint                txn_exec_in_flight_cnt;
      92             :   uint                txn_exec_done_cnt;
      93             :   uint                txn_sigverify_in_flight_cnt;
      94             :   uint                txn_sigverify_done_cnt;
      95             :   uint                poh_hashing_in_flight_cnt;
      96             :   uint                poh_hashing_done_cnt;
      97             :   uint                poh_hash_cmp_done_cnt; /* poh_hashing_done_cnt==poh_hash_cmp_done_cnt+len(mixin_in_progress) */
      98             :   uint                txn_done_cnt; /* A transaction is considered done when all types of tasks associated with it are done. */
      99             :   uint                shred_cnt;
     100             :   uint                mblk_cnt;          /* Total number of microblocks, including ticks and non ticks.
     101             :                                             mblk_cnt==len(unhashed)+len(hashing_in_progress)+hashing_in_flight_cnt+len(mixin_in_progress)+hash_cmp_done_cnt */
     102             :   uint                mblk_tick_cnt;     /* Total number of tick microblocks. */
     103             :   uint                mblk_freed_cnt;    /* This is ==hash_cmp_done_cnt in most cases, except for aborted
     104             :                                             blocks, where the freed cnt will catch up to mblk_cnt and surpass
     105             :                                             hash_cmp_done_cnt when the block is reaped. */
     106             :   uint                mblk_unhashed_cnt; /* ==len(unhashed) */
     107             :   ulong               hashcnt; /* How many hashes this block wants replay to do.  A mixin/record counts as one hash. */
     108             :   ulong               txn_pool_max_popcnt;   /* Peak transaction pool occupancy during the time this block was replaying. */
     109             :   ulong               mblk_pool_max_popcnt;  /* Peak mblk pool occupancy. */
     110             :   ulong               block_pool_max_popcnt; /* Peak block pool occupancy. */
     111             :   ulong               txn_idx[ FD_MAX_TXN_PER_SLOT ]; /* Indexed by parse order. */
     112             : 
     113             :   /* PoH verify. */
     114             :   fd_hash_t    poh_hash[ 1 ]; /* running end_hash of last parsed mblk */
     115             :   int          last_mblk_is_tick;
     116             :   mblk_slist_t mblks_unhashed[ 1 ]; /* A microblock, once parsed out, is in one of these queues.  It
     117             :                                        generally progresses from unhashed to hashing to mixin.  When a
     118             :                                        microblock is being hashed/in-flight, it'll be transiently out of
     119             :                                        any of the queues.  Once a microblock progresses through all stages
     120             :                                        of work, it'll be immediately freed. */
     121             :   mblk_slist_t mblks_hashing_in_progress[ 1 ];
     122             :   mblk_slist_t mblks_mixin_in_progress[ 1 ];
     123             :   uchar bmtree_mem[ FD_BMTREE_COMMIT_FOOTPRINT(0) ] __attribute__((aligned(FD_BMTREE_COMMIT_ALIGN)));
     124             :   fd_bmtree_commit_t * bmtree;
     125             :   ulong tick_hashcnt_wmk;  /* All ticks in a valid block must accumulate the same number of
     126             :                               hashes since the previous tick, or since block start, and this hash
     127             :                               count must match hashes_per_tick for the block. */
     128             :   ulong curr_tick_hashcnt; /* Starts at 0, accumulates hashcnt, resets to 0 on the next tick. */
     129             :   ulong tick_height;       /* Block is built off of a parent block with this many ticks. */
     130             :   ulong max_tick_height;   /* Block should end with precisely this many ticks. */
     131             :   ulong hashes_per_tick;   /* Fixed per block, feature gated, known after bank clone. */
     132             :   int inconsistent_hashes_per_tick;
     133             :   int zero_hash_tick;
     134             : 
     135             :   /* Parser state. */
     136             :   uchar               txn[ FD_TXN_MAX_SZ ] __attribute__((aligned(alignof(fd_txn_t))));
     137             :   ulong               mblks_rem;    /* Number of microblocks remaining in the current batch. */
     138             :   ulong               txns_rem;     /* Number of transactions remaining in the current microblock. */
     139             :   uint                fec_buf_sz;   /* Size of the fec_buf in bytes. */
     140             :   uint                fec_buf_soff; /* Starting offset into fec_buf for unparsed transactions. */
     141             :   uint                fec_buf_boff; /* Byte offset into raw block data of the first byte currently in fec_buf */
     142             :   uint                fec_eob:1;    /* FEC end-of-batch: set if the last FEC set in the batch is being
     143             :                                        ingested. */
     144             :   uint                fec_sob:1;    /* FEC start-of-batch: set if the parser expects to be parsing out a
     145             :                                        batch header. */
     146             : 
     147             :   /* Block state. */
     148             :   uint                fec_eos:1;                          /* FEC end-of-stream: set if the last FEC set in the block has been
     149             :                                                              ingested. */
     150             :   uint                rooted:1;                           /* Set if the block is rooted. */
     151             :   uint                dying:1;                            /* Set if the block has been abandoned and no transactions should be
     152             :                                                              scheduled from it. */
     153             :   uint                refcnt:1;                           /* Starts at 1 when the block is added, set to 0 if caller has been
     154             :                                                              informed to decrement refcnt for sched. */
     155             :   uint                in_sched:1;                         /* Set if the block is being tracked by the scheduler. */
     156             :   uint                in_rdisp:1;                         /* Set if the block is being tracked by the dispatcher, either as staged
     157             :                                                              or unstaged. */
     158             :   uint                block_start_signaled:1;             /* Set if the start-of-block sentinel has been dispatched. */
     159             :   uint                block_end_signaled:1;               /* Set if the end-of-block sentinel has been dispatched. */
     160             :   uint                block_start_done:1;                 /* Set if the start-of-block processing has been completed. */
     161             :   uint                block_end_done:1;                   /* Set if the end-of-block processing has been completed. */
     162             :   uint                staged:1;                           /* Set if the block is in a dispatcher staging lane; a staged block is
     163             :                                                              tracked by the dispatcher. */
     164             :   ulong               staging_lane;                       /* Ignored if staged==0. */
     165             :   ulong               luf_depth;                          /* Depth of longest unstaged fork starting from this node; only
     166             :                                                              stageable unstaged descendants are counted. */
     167             :   uchar               fec_buf[ FD_SCHED_MAX_FEC_BUF_SZ ]; /* The previous FEC set could have some residual data that only becomes
     168             :                                                              parseable after the next FEC set is ingested. */
     169             :   uint                shred_blk_offs[ FD_SHRED_BLK_MAX ]; /* The byte offsets into block data of ingested shreds */
     170             : };
     171             : typedef struct fd_sched_block fd_sched_block_t;
     172             : 
     173             : FD_STATIC_ASSERT( sizeof(fd_hash_t)==sizeof(((fd_microblock_hdr_t *)0)->hash), unexpected poh hash size );
     174             : 
     175             : 
     176             : struct fd_sched_metrics {
     177             :   uint  block_added_cnt;
     178             :   uint  block_added_staged_cnt;
     179             :   uint  block_added_unstaged_cnt;
     180             :   uint  block_added_dead_ood_cnt;
     181             :   uint  block_removed_cnt;
     182             :   uint  block_abandoned_cnt;
     183             :   uint  block_bad_cnt;
     184             :   uint  block_promoted_cnt;
     185             :   uint  block_demoted_cnt;
     186             :   uint  deactivate_no_child_cnt;
     187             :   uint  deactivate_no_txn_cnt;
     188             :   uint  deactivate_pruned_cnt;
     189             :   uint  deactivate_abandoned_cnt;
     190             :   uint  lane_switch_cnt;
     191             :   uint  lane_promoted_cnt;
     192             :   uint  lane_demoted_cnt;
     193             :   uint  fork_observed_cnt;
     194             :   uint  alut_success_cnt;
     195             :   uint  alut_serializing_cnt;
     196             :   uint  txn_abandoned_parsed_cnt;
     197             :   uint  txn_abandoned_exec_done_cnt;
     198             :   uint  txn_abandoned_done_cnt;
     199             :   uint  txn_max_in_flight_cnt;
     200             :   ulong txn_weighted_in_flight_cnt;
     201             :   ulong txn_weighted_in_flight_tickcount;
     202             :   ulong txn_none_in_flight_tickcount;
     203             :   ulong txn_parsed_cnt;
     204             :   ulong txn_exec_done_cnt;
     205             :   ulong txn_sigverify_done_cnt;
     206             :   ulong txn_mixin_done_cnt;
     207             :   ulong txn_done_cnt;
     208             :   ulong mblk_parsed_cnt;
     209             :   ulong mblk_poh_hashed_cnt;
     210             :   ulong mblk_poh_done_cnt;
     211             :   ulong bytes_ingested_cnt;
     212             :   ulong bytes_ingested_unparsed_cnt;
     213             :   ulong bytes_dropped_cnt;
     214             :   ulong fec_cnt;
     215             : };
     216             : typedef struct fd_sched_metrics fd_sched_metrics_t;
     217             : 
     218             : #define DEQUE_NAME ref_q
     219           9 : #define DEQUE_T    ulong
     220             : #include "../../util/tmpl/fd_deque_dynamic.c"
     221             : 
     222             : struct fd_sched {
     223             :   fd_acct_addr_t        aluts[ 256 ]; /* Resolve ALUT accounts into this buffer for more parallelism. */
     224             :   char                  print_buf[ FD_SCHED_MAX_PRINT_BUF_SZ ];
     225             :   ulong                 print_buf_sz;
     226             :   fd_chkdup_t           chkdup[ 1 ];
     227             :   fd_sched_metrics_t    metrics[ 1 ];
     228             :   ulong                 canary; /* == FD_SCHED_MAGIC */
     229             :   ulong                 depth;         /* Immutable. */
     230             :   ulong                 block_cnt_max; /* Immutable. */
     231             :   ulong                 exec_cnt;      /* Immutable. */
     232             :   int                   bypass_poh_verify; /* Test/fuzz: skip the PoH end_hash compare in maybe_mixin. */
     233             :   int                   bypass_alut_resolution; /* Test/fuzz: skip ALUT resolution (no accdb). */
     234             :   long                  txn_in_flight_last_tick;
     235             :   long                  next_ready_last_tick;
     236             :   ulong                 next_ready_last_bank_idx;
     237             :   ulong                 root_idx;
     238             :   fd_rdisp_t *          rdisp;
     239             :   ulong                 txn_exec_ready_bitset[ 1 ];
     240             :   ulong                 sigverify_ready_bitset[ 1 ];
     241             :   ulong                 poh_ready_bitset[ 1 ];
     242             :   ulong                 active_bank_idx; /* Index of the actively replayed block, or ULONG_MAX if no block is
     243             :                                             actively replayed; has to have a transaction to dispatch; staged
     244             :                                             blocks that have no transactions to dispatch are not eligible for
     245             :                                             being active. */
     246             :   ulong                 last_active_bank_idx;
     247             :   ulong                 staged_bitset;    /* Bit i set if staging lane i is occupied. */
     248             :   ulong                 staged_head_bank_idx[ FD_SCHED_MAX_STAGING_LANES ]; /* Head of the linear chain in each staging lane, ignored if bit i is
     249             :                                                                                not set in the bitset. */
     250             :   ulong                 staged_popcnt_wmk;
     251             :   ulong                 txn_pool_free_cnt;
     252             :   fd_txn_p_t *          txn_pool;      /* Just a flat array. */
     253             :   fd_sched_txn_info_t * txn_info_pool; /* Just a flat array. */
     254             :   fd_sched_mblk_t *     mblk_pool;     /* Just a flat array. */
     255             :   ulong                 mblk_pool_free_cnt;
     256             :   uint                  mblk_pool_free_head;
     257             :   ulong                 tile_to_bank_idx[ FD_SCHED_MAX_EXEC_TILE_CNT ]; /* Index of the bank that the exec tile is executing against. */
     258             :   txn_bitset_t          exec_done_set[ txn_bitset_word_cnt ];      /* Indexed by txn_idx. */
     259             :   txn_bitset_t          sigverify_done_set[ txn_bitset_word_cnt ]; /* Indexed by txn_idx. */
     260             :   txn_bitset_t          poh_mixin_done_set[ txn_bitset_word_cnt ]; /* Indexed by txn_idx. */
     261             :   fd_sched_block_t *    block_pool; /* Just a flat array. */
     262             :   ulong                 block_pool_popcnt;
     263             :   ulong *               ref_q;
     264             : };
     265             : typedef struct fd_sched fd_sched_t;
     266             : 
     267             : 
     268             : /* Internal helpers. */
     269             : 
     270             : static int
     271             : verify_ticks_eager( fd_sched_block_t * block );
     272             : 
     273             : static int
     274             : verify_ticks_final( fd_sched_block_t * block );
     275             : 
     276             : static void
     277             : add_block( fd_sched_t * sched,
     278             :            ulong        bank_idx,
     279             :            ulong        parent_bank_idx );
     280             : 
     281             : FD_WARN_UNUSED static int
     282             : fd_sched_parse( fd_sched_t * sched, fd_sched_block_t * block, fd_sched_alut_ctx_t * alut_ctx );
     283             : 
     284             : FD_WARN_UNUSED static int
     285             : fd_sched_parse_txn( fd_sched_t * sched, fd_sched_block_t * block, fd_sched_alut_ctx_t * alut_ctx );
     286             : 
     287             : static void
     288             : dispatch_sigverify( fd_sched_t * sched, fd_sched_block_t * block, ulong bank_idx, int exec_tile_idx, fd_sched_task_t * out );
     289             : 
     290             : static void
     291             : dispatch_poh( fd_sched_t * sched, fd_sched_block_t * block, ulong bank_idx, int exec_tile_idx, fd_sched_task_t * out );
     292             : 
     293             : FD_WARN_UNUSED static int
     294             : maybe_mixin( fd_sched_t * sched, fd_sched_block_t * block );
     295             : 
     296             : static void
     297           9 : free_mblk( fd_sched_t * sched, fd_sched_block_t * block, uint mblk_idx ) {
     298           9 :   sched->mblk_pool[ mblk_idx ].next = sched->mblk_pool_free_head;
     299           9 :   sched->mblk_pool_free_head = mblk_idx;
     300           9 :   sched->mblk_pool_free_cnt++;
     301           9 :   block->mblk_freed_cnt++;
     302           9 : }
     303             : 
     304             : static void
     305           0 : free_mblk_slist( fd_sched_t * sched, fd_sched_block_t * block, mblk_slist_t * list ) {
     306           0 :   while( !mblk_slist_is_empty( list, sched->mblk_pool ) ) {
     307           0 :     uint idx = (uint)mblk_slist_idx_pop_head( list, sched->mblk_pool );
     308           0 :     free_mblk( sched, block, idx );
     309           0 :   }
     310           0 : }
     311             : 
     312             : static void
     313             : try_activate_block( fd_sched_t * sched );
     314             : 
     315             : static void
     316             : check_or_set_active_block( fd_sched_t * sched );
     317             : 
     318             : static void
     319             : subtree_abandon( fd_sched_t * sched, fd_sched_block_t * block );
     320             : 
     321             : static void
     322             : subtree_release_refcnt( fd_sched_t * sched, fd_sched_block_t * block );
     323             : 
     324             : static void
     325             : subtree_prune( fd_sched_t * sched, ulong bank_idx, ulong except_idx );
     326             : 
     327             : static void
     328             : maybe_switch_block( fd_sched_t * sched, ulong bank_idx );
     329             : 
     330             : FD_FN_UNUSED static ulong
     331             : find_and_stage_longest_unstaged_fork( fd_sched_t * sched, int lane_idx );
     332             : 
     333             : static ulong
     334             : compute_longest_unstaged_fork( fd_sched_t * sched, ulong bank_idx );
     335             : 
     336             : static ulong
     337             : stage_longest_unstaged_fork( fd_sched_t * sched, ulong bank_idx, int lane_idx );
     338             : 
     339             : static int
     340             : lane_is_demotable( fd_sched_t * sched, int lane_idx );
     341             : 
     342             : static ulong
     343             : demote_lane( fd_sched_t * sched, int lane_idx );
     344             : 
     345             : static inline fd_sched_block_t *
     346         747 : block_pool_ele( fd_sched_t * sched, ulong idx ) {
     347         747 :   FD_TEST( idx<sched->block_cnt_max || idx==ULONG_MAX );
     348         747 :   return idx==ULONG_MAX ? NULL : sched->block_pool+idx;
     349         747 : }
     350             : 
     351             : FD_FN_UNUSED static inline int
     352           0 : block_is_void( fd_sched_block_t * block ) {
     353           0 :   /* We've seen everything in the block and no transaction got parsed
     354           0 :      out. */
     355           0 :   return block->fec_eos && block->txn_parsed_cnt==0;
     356           0 : }
     357             : 
     358             : static inline int
     359           3 : block_should_signal_end( fd_sched_block_t * block ) {
     360             :   /* Under the current policy of eager synchronous PoH mixin, hashing
     361             :      done plus fec_eos imply that all mixins have been done. */
     362           3 :   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 ));
     363           3 :   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;
     364           3 : }
     365             : 
     366             : static inline int
     367         153 : block_will_signal_end( fd_sched_block_t * block ) {
     368         153 :   return block->fec_eos && !block->block_end_signaled;
     369         153 : }
     370             : 
     371             : /* Is there something known to be dispatchable in the block?  This is an
     372             :    important liveness property.  A block that doesn't contain any known
     373             :    dispatchable tasks will be deactivated or demoted. */
     374             : static inline int
     375         222 : block_is_dispatchable( fd_sched_block_t * block ) {
     376         222 :   ulong exec_queued_cnt      = block->txn_parsed_cnt-block->txn_exec_in_flight_cnt-block->txn_exec_done_cnt;
     377         222 :   ulong sigverify_queued_cnt = block->txn_parsed_cnt-block->txn_sigverify_in_flight_cnt-block->txn_sigverify_done_cnt;
     378         222 :   ulong poh_queued_cnt       = block->mblk_cnt-block->poh_hashing_in_flight_cnt-block->poh_hashing_done_cnt;
     379         222 :   return exec_queued_cnt>0UL ||
     380         222 :          sigverify_queued_cnt>0UL ||
     381         222 :          poh_queued_cnt>0UL ||
     382         222 :          !block->block_start_signaled ||
     383         222 :          block_will_signal_end( block );
     384         222 : }
     385             : 
     386             : static inline int
     387          45 : block_is_in_flight( fd_sched_block_t * block ) {
     388          45 :   return block->txn_exec_in_flight_cnt || block->txn_sigverify_in_flight_cnt || block->poh_hashing_in_flight_cnt || (block->block_end_signaled && !block->block_end_done);
     389          45 : }
     390             : 
     391             : static inline int
     392         384 : block_is_done( fd_sched_block_t * block ) {
     393         384 :   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;
     394         384 : }
     395             : 
     396             : static inline int
     397         273 : block_is_stageable( fd_sched_block_t * block ) {
     398         273 :   int rv = !block_is_done( block ) && !block->dying;
     399         273 :   if( FD_UNLIKELY( rv && !block->in_rdisp ) ) {
     400             :     /* Invariant: stageable blocks may be currently staged or unstaged,
     401             :        but must be in the dispatcher either way.  When a block
     402             :        transitions to DONE, it will be immediately removed from the
     403             :        dispatcher.  When a block transitions to DYING, it will be
     404             :        eventually abandoned from the dispatcher. */
     405           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",
     406           0 :                   block->txn_parsed_cnt, block->txn_done_cnt, (uint)block->fec_eos, block->slot, block->parent_slot ));
     407           0 :   }
     408         273 :   return rv;
     409         273 : }
     410             : 
     411             : static inline int
     412         102 : block_is_promotable( fd_sched_block_t * block ) {
     413         102 :   return block_is_stageable( block ) && block_is_dispatchable( block ) && !block->staged;
     414         102 : }
     415             : 
     416             : static inline int
     417          12 : block_is_demotable( fd_sched_block_t * block ) {
     418             :   /* A block can only be demoted from rdisp if it is empty, meaning no
     419             :      PENDING, READY, or DISPATCHED transactions.  This is equivalent to
     420             :      having no in-flight transactions (DISPATCHED) and no queued
     421             :      transactions (PENDING or READY).  This function actually implements
     422             :      a stronger requirement.  We consider a block demotable only if
     423             :      there are no in-flight or queued tasks of any kind. */
     424          12 :   return !block_is_in_flight( block ) && !block_is_dispatchable( block ) && block->staged;
     425          12 : }
     426             : 
     427             : static inline int
     428         147 : block_is_activatable( fd_sched_block_t * block ) {
     429         147 :   return block_is_stageable( block ) && block_is_dispatchable( block ) && block->staged;
     430         147 : }
     431             : 
     432             : static inline int
     433          63 : block_should_deactivate( fd_sched_block_t * block ) {
     434             :   /* We allow a grace period, during which a block has nothing to
     435             :      dispatch, but has something in-flight.  The block is allowed to
     436             :      stay activated and ingest FEC sets during this time.  The block
     437             :      will be deactivated if there's still nothing to dispatch by the
     438             :      time all in-flight tasks are completed. */
     439          63 :   return !block_is_activatable( block ) && !block_is_in_flight( block );
     440          63 : }
     441             : 
     442             : static inline int
     443           9 : block_is_prunable( fd_sched_block_t * block ) {
     444           9 :   return !block->in_rdisp && !block_is_in_flight( block );
     445           9 : }
     446             : 
     447             : static inline ulong
     448          84 : block_to_idx( fd_sched_t * sched, fd_sched_block_t * block ) { return (ulong)(block-sched->block_pool); }
     449             : 
     450             : __attribute__((format(printf,2,3)))
     451             : static void
     452             : fd_sched_printf( fd_sched_t * sched,
     453             :                  char const * fmt,
     454          93 :                  ... ) {
     455          93 :   va_list ap;
     456          93 :   ulong len;
     457          93 :   va_start( ap, fmt );
     458          93 :   int ret = vsnprintf( sched->print_buf+sched->print_buf_sz,
     459          93 :                        FD_SCHED_MAX_PRINT_BUF_SZ-sched->print_buf_sz,
     460          93 :                        fmt, ap );
     461          93 :   va_end( ap );
     462          93 :   len = fd_ulong_if( ret<0, 0UL, fd_ulong_min( (ulong)ret, FD_SCHED_MAX_PRINT_BUF_SZ-sched->print_buf_sz-1UL ) );
     463          93 :   sched->print_buf[ sched->print_buf_sz+len ] = '\0';
     464          93 :   sched->print_buf_sz += len;
     465          93 : }
     466             : 
     467             : FD_FN_UNUSED static void
     468           0 : print_histogram( fd_sched_t * sched, fd_histf_t * hist, ulong converter, char * title ) {
     469           0 :   fd_sched_printf( sched, " +---------------------+----------------------+--------------+\n" );
     470           0 :   fd_sched_printf( sched, " | %-19s |                      | Count        |\n", title );
     471           0 :   fd_sched_printf( sched, " +---------------------+----------------------+--------------+\n" );
     472           0 : 
     473           0 :   ulong total_count = 0;
     474           0 :   for( ulong i=0UL; i<fd_histf_bucket_cnt( hist ); i++ ) {
     475           0 :     total_count += fd_histf_cnt( hist, i );
     476           0 :   }
     477           0 : 
     478           0 :   for( ulong i=0UL; i< fd_histf_bucket_cnt( hist ); i++ ) {
     479           0 :     ulong bucket_count = fd_histf_cnt( hist, i );
     480           0 : 
     481           0 :     char * lt_str;
     482           0 :     char lt_buf[ 64 ];
     483           0 :     if( FD_UNLIKELY( i==fd_histf_bucket_cnt( hist )-1UL ) ) {
     484           0 :       lt_str = "+Inf";
     485           0 :     } else {
     486           0 :       ulong edge = fd_histf_right( hist, i );
     487           0 :       if( converter==FD_METRICS_CONVERTER_NANOSECONDS ) {
     488           0 :         edge = fd_metrics_convert_ticks_to_nanoseconds( edge-1UL );
     489           0 :         FD_TEST( fd_cstr_printf_check( lt_buf, sizeof( lt_buf ), NULL, "<= %lu nanos", edge ) );
     490           0 :       } else if( converter==FD_METRICS_CONVERTER_NONE ) {
     491           0 :         FD_TEST( fd_cstr_printf_check( lt_buf, sizeof( lt_buf ), NULL, "<= %lu", edge-1UL ) );
     492           0 :       }
     493           0 :       lt_str = lt_buf;
     494           0 :     }
     495           0 : 
     496           0 :     /* Create visual bar - scale to max 20 characters. */
     497           0 :     char bar_buf[ 22 ];
     498           0 :     if( bucket_count>0UL && total_count>0UL ) {
     499           0 :       ulong bar_length = (bucket_count*20UL)/total_count;
     500           0 :       if( !bar_length ) bar_length = 1;
     501           0 :       for( ulong j=0UL; j<bar_length; j++ ) { bar_buf[ j ] = '*'; }
     502           0 :       bar_buf[ bar_length ] = '\0';
     503           0 :     } else {
     504           0 :       bar_buf[ 0 ] = '\0';
     505           0 :     }
     506           0 : 
     507           0 :     fd_sched_printf( sched, " | %19s | %-20s | %12lu |\n", lt_str, bar_buf, bucket_count );
     508           0 :   }
     509           0 : }
     510             : 
     511             : FD_FN_UNUSED static void
     512           0 : print_block_metrics( fd_sched_t * sched, fd_sched_block_t * block ) {
     513           0 :   fd_sched_printf( sched, "block idx %lu, block slot %lu, parent_slot %lu, fec_eos %d, rooted %d, 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_freed_cnt %u, mblk_tick_cnt %u, mblk_unhashed_cnt %u, hashcnt %lu, txn_pool_max_popcnt %lu/%lu, mblk_pool_max_popcnt %lu/%lu, block_pool_max_popcnt %lu/%lu, mblks_rem %lu, txns_rem %lu, fec_buf_sz %u, fec_buf_boff %u, fec_buf_soff %u, fec_eob %d, fec_sob %d\n",
     514           0 :                    block_to_idx( sched, block ), block->slot, block->parent_slot, block->fec_eos, block->rooted, 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_freed_cnt, block->mblk_tick_cnt, block->mblk_unhashed_cnt, block->hashcnt, 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->fec_buf_sz, block->fec_buf_boff, block->fec_buf_soff, block->fec_eob, block->fec_sob );
     515           0 : }
     516             : 
     517             : FD_FN_UNUSED static void
     518          57 : print_block_debug( fd_sched_t * sched, fd_sched_block_t * block ) {
     519          57 :   fd_sched_printf( sched, "block idx %lu, block slot %lu, parent_slot %lu, staged %d (lane %lu), dying %d, in_rdisp %d, fec_eos %d, rooted %d, block_start_signaled %d, block_end_signaled %d, block_start_done %d, block_end_done %d, 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_hashing_done_cnt %u, poh_hash_cmp_done_cnt %u, txn_done_cnt %u, shred_cnt %u, mblk_cnt %u, mblk_freed_cnt %u, mblk_tick_cnt %u, mblk_unhashed_cnt %u, hashcnt %lu, txn_pool_max_popcnt %lu/%lu, mblk_pool_max_popcnt %lu/%lu, block_pool_max_popcnt %lu/%lu, tick_hashcnt_wmk %lu, curr_tick_hashcnt %lu, hashes_per_tick %lu, mblks_rem %lu, txns_rem %lu, fec_buf_sz %u, fec_buf_boff %u, fec_buf_soff %u, fec_eob %d, fec_sob %d\n",
     520          57 :                    block_to_idx( sched, block ), block->slot, block->parent_slot, block->staged, block->staging_lane, block->dying, block->in_rdisp, block->fec_eos, block->rooted, block->block_start_signaled, block->block_end_signaled, block->block_start_done, block->block_end_done, 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_hashing_done_cnt, block->poh_hash_cmp_done_cnt, block->txn_done_cnt, block->shred_cnt, block->mblk_cnt, block->mblk_freed_cnt, block->mblk_tick_cnt, block->mblk_unhashed_cnt, block->hashcnt, block->txn_pool_max_popcnt, sched->depth, block->mblk_pool_max_popcnt, sched->depth, block->block_pool_max_popcnt, sched->block_cnt_max, block->tick_hashcnt_wmk, block->curr_tick_hashcnt, block->hashes_per_tick, block->mblks_rem, block->txns_rem, block->fec_buf_sz, block->fec_buf_boff, block->fec_buf_soff, block->fec_eob, block->fec_sob );
     521          57 : }
     522             : 
     523             : FD_FN_UNUSED static void
     524           9 : print_block_and_parent( fd_sched_t * sched, fd_sched_block_t * block ) {
     525           9 :   print_block_debug( sched, block );
     526           9 :   fd_sched_block_t * parent = block_pool_ele( sched, block->parent_idx );
     527           9 :   if( FD_LIKELY( parent ) ) print_block_debug( sched, parent );
     528           9 : }
     529             : 
     530             : FD_FN_UNUSED static void
     531          18 : print_metrics( fd_sched_t * sched ) {
     532          18 :     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_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\n",
     533          18 :                      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_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 );
     534             : 
     535          18 : }
     536             : 
     537             : FD_FN_UNUSED static void
     538          18 : print_sched( fd_sched_t * sched ) {
     539          18 :   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, 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, block_pool_popcnt %lu/%lu\n",
     540          18 :                    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->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->block_pool_popcnt, sched->block_cnt_max );
     541          18 :   fd_sched_block_t * active_block = block_pool_ele( sched, sched->active_bank_idx );
     542          18 :   if( active_block ) print_block_debug( sched, active_block );
     543          90 :   for( int l=0; l<(int)FD_SCHED_MAX_STAGING_LANES; l++ ) {
     544          72 :     if( fd_ulong_extract_bit( sched->staged_bitset, l ) ) {
     545          27 :       fd_sched_block_t * block = block_pool_ele( sched, sched->staged_head_bank_idx[ l ] );
     546          27 :       print_block_debug( sched, block );
     547          27 :     }
     548          72 :   }
     549          18 : }
     550             : 
     551             : FD_FN_UNUSED static void
     552           9 : print_all( fd_sched_t * sched, fd_sched_block_t * block ) {
     553           9 :   print_metrics( sched );
     554           9 :   print_sched( sched );
     555           9 :   print_block_and_parent( sched, block );
     556           9 : }
     557             : 
     558             : static void
     559           9 : handle_bad_block( fd_sched_t * sched, fd_sched_block_t * block ) {
     560           9 :   sched->print_buf_sz = 0UL;
     561           9 :   print_all( sched, block );
     562           9 :   FD_LOG_DEBUG(( "%s", sched->print_buf ));
     563           9 :   subtree_abandon( sched, block );
     564           9 :   sched->metrics->block_bad_cnt++;
     565           9 :   check_or_set_active_block( sched );
     566           9 : }
     567             : 
     568             : 
     569             : /* Public functions. */
     570             : 
     571             : ulong
     572         144 : fd_sched_align( void ) {
     573         144 :   return fd_ulong_max( alignof(fd_sched_t),
     574         144 :          fd_ulong_max( fd_rdisp_align(),
     575         144 :          fd_ulong_max( alignof(fd_sched_block_t), 64UL ))); /* Minimally cache line aligned. */
     576         144 : }
     577             : 
     578             : ulong
     579             : fd_sched_footprint( ulong depth,
     580          12 :                     ulong block_cnt_max ) {
     581          12 :   if( FD_UNLIKELY( depth<FD_SCHED_MIN_DEPTH || depth>FD_SCHED_MAX_DEPTH ) ) return 0UL; /* bad depth */
     582          12 :   if( FD_UNLIKELY( !block_cnt_max ) ) return 0UL; /* bad block_cnt_max */
     583          12 :   if( FD_UNLIKELY( depth>UINT_MAX-1UL ) ) return 0UL; /* mblk_pool use uint as pointers */
     584             : 
     585          12 :   ulong l = FD_LAYOUT_INIT;
     586          12 :   l = FD_LAYOUT_APPEND( l, fd_sched_align(),             sizeof(fd_sched_t)                         );
     587          12 :   l = FD_LAYOUT_APPEND( l, fd_rdisp_align(),             fd_rdisp_footprint( depth, block_cnt_max ) ); /* dispatcher */
     588          12 :   l = FD_LAYOUT_APPEND( l, alignof(fd_sched_block_t),    block_cnt_max*sizeof(fd_sched_block_t)     ); /* block pool */
     589          12 :   l = FD_LAYOUT_APPEND( l, ref_q_align(),                ref_q_footprint( block_cnt_max )           );
     590          12 :   l = FD_LAYOUT_APPEND( l, alignof(fd_txn_p_t),          depth*sizeof(fd_txn_p_t)                   ); /* txn_pool */
     591          12 :   l = FD_LAYOUT_APPEND( l, alignof(fd_sched_txn_info_t), depth*sizeof(fd_sched_txn_info_t)          ); /* txn_info_pool */
     592          12 :   l = FD_LAYOUT_APPEND( l, alignof(fd_sched_mblk_t),     depth*sizeof(fd_sched_mblk_t)              ); /* mblk_pool */
     593          12 :   return FD_LAYOUT_FINI( l, fd_sched_align() );
     594          12 : }
     595             : 
     596             : void *
     597             : fd_sched_new( void *     mem,
     598             :               fd_rng_t * rng,
     599             :               ulong      depth,
     600             :               ulong      block_cnt_max,
     601          12 :               ulong      exec_cnt ) {
     602             : 
     603          12 :   if( FD_UNLIKELY( !mem ) ) {
     604           0 :     FD_LOG_WARNING(( "NULL mem" ));
     605           0 :     return NULL;
     606           0 :   }
     607             : 
     608          12 :   if( FD_UNLIKELY( !rng ) ) {
     609           0 :     FD_LOG_WARNING(( "NULL rng" ));
     610           0 :     return NULL;
     611           0 :   }
     612             : 
     613          12 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_sched_align() ) ) ) {
     614           0 :     FD_LOG_WARNING(( "misaligned mem (%p)", mem ));
     615           0 :     return NULL;
     616           0 :   }
     617             : 
     618          12 :   if( FD_UNLIKELY( depth<FD_SCHED_MIN_DEPTH || depth>FD_SCHED_MAX_DEPTH ) ) {
     619           0 :     FD_LOG_WARNING(( "bad depth (%lu)", depth ));
     620           0 :     return NULL;
     621           0 :   }
     622             : 
     623          12 :   if( FD_UNLIKELY( !block_cnt_max ) ) {
     624           0 :     FD_LOG_WARNING(( "bad block_cnt_max (%lu)", block_cnt_max ));
     625           0 :     return NULL;
     626           0 :   }
     627             : 
     628          12 :   if( FD_UNLIKELY( depth>UINT_MAX-1UL ) ) {
     629           0 :     FD_LOG_WARNING(( "bad depth (%lu)", depth ));
     630           0 :     return NULL;
     631           0 :   }
     632             : 
     633          12 :   if( FD_UNLIKELY( !exec_cnt || exec_cnt>FD_SCHED_MAX_EXEC_TILE_CNT ) ) {
     634           0 :     FD_LOG_WARNING(( "bad exec_cnt (%lu)", exec_cnt ));
     635           0 :     return NULL;
     636           0 :   }
     637             : 
     638          12 :   FD_SCRATCH_ALLOC_INIT( l, mem );
     639          12 :   fd_sched_t *          sched          = FD_SCRATCH_ALLOC_APPEND( l, fd_sched_align(),             sizeof(fd_sched_t)                         );
     640          12 :   void *                _rdisp         = FD_SCRATCH_ALLOC_APPEND( l, fd_rdisp_align(),             fd_rdisp_footprint( depth, block_cnt_max ) );
     641          12 :   void *                _bpool         = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sched_block_t),    block_cnt_max*sizeof(fd_sched_block_t)     );
     642          12 :   void *                _ref_q         = FD_SCRATCH_ALLOC_APPEND( l, ref_q_align(),                ref_q_footprint( block_cnt_max )           );
     643          12 :   fd_txn_p_t *          _txn_pool      = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txn_p_t),          depth*sizeof(fd_txn_p_t)                   );
     644          12 :   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)          );
     645          12 :   fd_sched_mblk_t *     _mblk_pool     = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sched_mblk_t),     depth*sizeof(fd_sched_mblk_t)              );
     646          12 :   FD_SCRATCH_ALLOC_FINI( l, fd_sched_align() );
     647             : 
     648          12 :   sched->txn_pool      = _txn_pool;
     649          12 :   sched->txn_info_pool = _txn_info_pool;
     650          12 :   sched->mblk_pool     = _mblk_pool;
     651             : 
     652          12 :   fd_rdisp_new( _rdisp, depth, block_cnt_max, fd_rng_ulong( rng ) );
     653             : 
     654          12 :   fd_sched_block_t * bpool = (fd_sched_block_t *)_bpool;
     655          72 :   for( ulong i=0; i<block_cnt_max; i++ ) {
     656          60 :     bpool[ i ].in_sched = 0;
     657          60 :     mblk_slist_new( bpool[ i ].mblks_unhashed );
     658          60 :     mblk_slist_new( bpool[ i ].mblks_hashing_in_progress );
     659          60 :     mblk_slist_new( bpool[ i ].mblks_mixin_in_progress );
     660          60 :   }
     661             : 
     662          12 :   FD_TEST( fd_chkdup_new( sched->chkdup, rng ) );
     663             : 
     664          12 :   fd_memset( sched->metrics, 0, sizeof(fd_sched_metrics_t) );
     665          12 :   sched->txn_in_flight_last_tick  = LONG_MAX;
     666          12 :   sched->next_ready_last_tick     = LONG_MAX;
     667          12 :   sched->next_ready_last_bank_idx = ULONG_MAX;
     668             : 
     669          12 :   sched->canary                 = FD_SCHED_MAGIC;
     670          12 :   sched->depth                  = depth;
     671          12 :   sched->block_cnt_max          = block_cnt_max;
     672          12 :   sched->exec_cnt               = exec_cnt;
     673          12 :   sched->bypass_poh_verify      = 0;
     674          12 :   sched->bypass_alut_resolution = 0;
     675          12 :   sched->root_idx               = ULONG_MAX;
     676          12 :   sched->active_bank_idx        = ULONG_MAX;
     677          12 :   sched->last_active_bank_idx   = ULONG_MAX;
     678          12 :   sched->staged_bitset          = 0UL;
     679          12 :   sched->staged_popcnt_wmk      = 0UL;
     680             : 
     681          12 :   sched->txn_exec_ready_bitset[ 0 ]  = fd_ulong_mask_lsb( (int)exec_cnt );
     682          12 :   sched->sigverify_ready_bitset[ 0 ] = fd_ulong_mask_lsb( (int)exec_cnt );
     683          12 :   sched->poh_ready_bitset[ 0 ]       = fd_ulong_mask_lsb( (int)exec_cnt );
     684             : 
     685          12 :   sched->txn_pool_free_cnt = depth-1UL; /* -1 because index 0 is unusable as a sentinel reserved by the dispatcher */
     686             : 
     687        6144 :   for( ulong i=0UL; i<depth-1UL; i++ ) sched->mblk_pool[ i ].next = (uint)(i+1UL);
     688          12 :   sched->mblk_pool[ depth-1UL ].next = UINT_MAX;
     689          12 :   sched->mblk_pool_free_head = 0U;
     690          12 :   sched->mblk_pool_free_cnt  = depth;
     691             : 
     692          12 :   txn_bitset_new( sched->exec_done_set );
     693          12 :   txn_bitset_new( sched->sigverify_done_set );
     694          12 :   txn_bitset_new( sched->poh_mixin_done_set );
     695             : 
     696          12 :   sched->block_pool_popcnt = 0UL;
     697             : 
     698          12 :   ref_q_new( _ref_q, block_cnt_max );
     699             : 
     700          12 :   return sched;
     701          12 : }
     702             : 
     703             : fd_sched_t *
     704          12 : fd_sched_join( void * mem ) {
     705             : 
     706          12 :   if( FD_UNLIKELY( !mem ) ) {
     707           0 :     FD_LOG_WARNING(( "NULL mem" ));
     708           0 :     return NULL;
     709           0 :   }
     710             : 
     711          12 :   fd_sched_t * sched         = (fd_sched_t *)mem;
     712          12 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
     713          12 :   ulong        depth         = sched->depth;
     714          12 :   ulong        block_cnt_max = sched->block_cnt_max;
     715             : 
     716          12 :   FD_SCRATCH_ALLOC_INIT( l, mem );
     717          12 :   /*                     */ FD_SCRATCH_ALLOC_APPEND( l, fd_sched_align(),             sizeof(fd_sched_t)                         );
     718          12 :   void *           _rdisp = FD_SCRATCH_ALLOC_APPEND( l, fd_rdisp_align(),             fd_rdisp_footprint( depth, block_cnt_max ) );
     719          12 :   void *           _bpool = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sched_block_t),    block_cnt_max*sizeof(fd_sched_block_t)     );
     720          12 :   void *           _ref_q = FD_SCRATCH_ALLOC_APPEND( l, ref_q_align(),                ref_q_footprint( block_cnt_max )           );
     721          12 :   /*                     */ FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txn_p_t),          depth*sizeof(fd_txn_p_t)                   );
     722          12 :   /*                     */ FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sched_txn_info_t), depth*sizeof(fd_sched_txn_info_t)          );
     723          12 :   /*                     */ FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sched_mblk_t),     depth*sizeof(fd_sched_mblk_t)              );
     724          12 :   FD_SCRATCH_ALLOC_FINI( l, fd_sched_align() );
     725             : 
     726          12 :   sched->rdisp      = fd_rdisp_join( _rdisp );
     727          12 :   sched->ref_q      = ref_q_join( _ref_q );
     728          12 :   sched->block_pool = _bpool;
     729             : 
     730          72 :   for( ulong i=0; i<block_cnt_max; i++ ) {
     731          60 :     mblk_slist_join( sched->block_pool[ i ].mblks_unhashed );
     732          60 :     mblk_slist_join( sched->block_pool[ i ].mblks_hashing_in_progress );
     733          60 :     mblk_slist_join( sched->block_pool[ i ].mblks_mixin_in_progress );
     734          60 :   }
     735             : 
     736          12 :   txn_bitset_join( sched->exec_done_set );
     737          12 :   txn_bitset_join( sched->sigverify_done_set );
     738          12 :   txn_bitset_join( sched->poh_mixin_done_set );
     739             : 
     740          12 :   return sched;
     741          12 : }
     742             : 
     743             : int
     744          24 : fd_sched_fec_can_ingest( fd_sched_t * sched, fd_sched_fec_t * fec ) {
     745          24 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
     746          24 :   FD_TEST( fec->bank_idx<sched->block_cnt_max );
     747          24 :   FD_TEST( fec->parent_bank_idx<sched->block_cnt_max );
     748             : 
     749          24 :   if( FD_UNLIKELY( fec->fec->data_sz>FD_SCHED_MAX_PAYLOAD_PER_FEC ) ) {
     750           0 :     sched->print_buf_sz = 0UL;
     751           0 :     print_metrics( sched );
     752           0 :     print_sched( sched );
     753           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
     754           0 :     FD_LOG_CRIT(( "invalid FEC set: fec->data_sz %lu, slot %lu, parent slot %lu", fec->fec->data_sz, fec->slot, fec->parent_slot ));
     755           0 :   }
     756             : 
     757          24 :   ulong fec_buf_sz = 0UL;
     758          24 :   fd_sched_block_t * block = block_pool_ele( sched, fec->bank_idx );
     759          24 :   if( FD_LIKELY( !fec->is_first_in_block ) ) {
     760           0 :     fec_buf_sz += block->fec_buf_sz-block->fec_buf_soff;
     761          24 :   } else {
     762             :     /* No residual data as this is a fresh new block. */
     763          24 :   }
     764             :   /* Addition is safe and won't overflow because we checked the FEC set
     765             :      size above. */
     766          24 :   fec_buf_sz += fec->fec->data_sz;
     767             :   /* Assuming every transaction is min size, do we have enough free
     768             :      entries in the txn pool?  For a more precise txn count, we would
     769             :      have to do some parsing. */
     770          24 :   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);
     771          24 : }
     772             : 
     773             : ulong
     774           3 : fd_sched_can_ingest_cnt( fd_sched_t * sched ) {
     775           3 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
     776             :   /* Worst case, we need one byte from the incoming data to extract a
     777             :      transaction out of the residual data, and the rest of the incoming
     778             :      data contributes toward min sized transactions. */
     779           3 :   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 );
     780           3 : }
     781             : 
     782             : int
     783          27 : fd_sched_is_drained( fd_sched_t * sched ) {
     784          27 :   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 ] );
     785          27 :   int nothing_queued = sched->active_bank_idx==ULONG_MAX;
     786          27 :   return nothing_inflight && nothing_queued;
     787          27 : }
     788             : 
     789             : FD_WARN_UNUSED int
     790             : fd_sched_fec_ingest( fd_sched_t *     sched,
     791          24 :                      fd_sched_fec_t * fec ) {
     792          24 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
     793          24 :   FD_TEST( fec->bank_idx<sched->block_cnt_max );
     794          24 :   FD_TEST( fec->parent_bank_idx<sched->block_cnt_max );
     795          24 :   FD_TEST( ref_q_empty( sched->ref_q ) );
     796             : 
     797          24 :   fd_sched_block_t * block = block_pool_ele( sched, fec->bank_idx );
     798             : 
     799          24 :   if( FD_UNLIKELY( fec->fec->data_sz>FD_SCHED_MAX_PAYLOAD_PER_FEC ) ) {
     800           0 :     sched->print_buf_sz = 0UL;
     801           0 :     print_all( sched, block );
     802           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
     803           0 :     FD_LOG_CRIT(( "invalid FEC set: fec->data_sz %lu, slot %lu, parent slot %lu", fec->fec->data_sz, fec->slot, fec->parent_slot ));
     804           0 :   }
     805             : 
     806          24 :   sched->metrics->fec_cnt++;
     807             : 
     808          24 :   if( FD_UNLIKELY( fec->is_first_in_block ) ) {
     809             :     /* This is a new block. */
     810          24 :     add_block( sched, fec->bank_idx, fec->parent_bank_idx );
     811          24 :     block->slot        = fec->slot;
     812          24 :     block->parent_slot = fec->parent_slot;
     813             : 
     814          24 :     if( FD_UNLIKELY( block->dying ) ) {
     815             :       /* The child of a dead block is also dead.  We added it to our
     816             :          fork tree just so we could track an entire lineage of dead
     817             :          children and propagate the dead property to the entire lineage,
     818             :          in case there were frags for more than one dead children
     819             :          in-flight at the time the parent was abandoned.  That being
     820             :          said, we shouldn't need to add the dead child to the
     821             :          dispatcher. */
     822           0 :       sched->metrics->block_added_dead_ood_cnt++;
     823             : 
     824             :       /* Release the refcnt right away since we don't intend to replay
     825             :          it at all.  We do this so its bank does not linger and stall
     826             :          root advancement until the next root notify. */
     827           0 :       subtree_release_refcnt( sched, block );
     828             : 
     829             :       /* Ignore the FEC set for a dead block. */
     830           0 :       sched->metrics->bytes_dropped_cnt += fec->fec->data_sz;
     831           0 :       return 0;
     832           0 :     }
     833             : 
     834             :     /* Try to find a staging lane for this block. */
     835          24 :     int alloc_lane = 0;
     836          24 :     fd_sched_block_t * parent_block = block_pool_ele( sched, fec->parent_bank_idx );
     837          24 :     if( FD_LIKELY( parent_block->staged ) ) {
     838             :       /* Parent is staged.  So see if we can continue down the same
     839             :          staging lane. */
     840           0 :       ulong staging_lane = parent_block->staging_lane;
     841           0 :       ulong child_idx    = parent_block->child_idx;
     842           0 :       while( child_idx!=ULONG_MAX ) {
     843           0 :         fd_sched_block_t * child = block_pool_ele( sched, child_idx );
     844           0 :         if( child->staged && child->staging_lane==staging_lane ) {
     845             :           /* Found a child on the same lane.  So we're done. */
     846           0 :           staging_lane = FD_RDISP_UNSTAGED;
     847           0 :           break;
     848           0 :         }
     849           0 :         child_idx = child->sibling_idx;
     850           0 :       }
     851             :       /* No child is staged on the same lane as the parent.  So stage
     852             :          this block.  This is the common case. */
     853           0 :       if( FD_LIKELY( staging_lane!=FD_RDISP_UNSTAGED ) ) {
     854           0 :         block->in_rdisp     = 1;
     855           0 :         block->staged       = 1;
     856           0 :         block->staging_lane = staging_lane;
     857           0 :         fd_rdisp_add_block( sched->rdisp, fec->bank_idx, staging_lane );
     858           0 :         sched->metrics->block_added_cnt++;
     859           0 :         sched->metrics->block_added_staged_cnt++;
     860           0 :         FD_LOG_DEBUG(( "block %lu:%lu entered lane %lu: add", block->slot, fec->bank_idx, staging_lane ));
     861           0 :       } else {
     862           0 :         alloc_lane = 1;
     863           0 :       }
     864          24 :     } else {
     865          24 :       if( block_is_stageable( parent_block ) ) {
     866             :         /* Parent is unstaged but stageable.  So let's be unstaged too.
     867             :            This is not only a policy decision to be lazy and not promote
     868             :            the parent at the moment, but also an important invariant
     869             :            that we maintain for deadlock freeness in the face of staging
     870             :            lane shortage.  See the comments in lane eviction for how
     871             :            this invariant is relevant. */
     872           0 :         block->in_rdisp = 1;
     873           0 :         block->staged   = 0;
     874           0 :         fd_rdisp_add_block( sched->rdisp, fec->bank_idx, FD_RDISP_UNSTAGED );
     875           0 :         sched->metrics->block_added_cnt++;
     876           0 :         sched->metrics->block_added_unstaged_cnt++;
     877           0 :         FD_LOG_DEBUG(( "block %lu:%lu entered lane unstaged: add", block->slot, fec->bank_idx ));
     878          24 :       } else {
     879          24 :         alloc_lane = 1;
     880          24 :       }
     881          24 :     }
     882          24 :     if( FD_UNLIKELY( alloc_lane ) ) {
     883             :       /* We weren't able to inherit the parent's staging lane.  So try
     884             :          to find a new staging lane. */
     885          24 :       if( FD_LIKELY( sched->staged_bitset!=fd_ulong_mask_lsb( FD_SCHED_MAX_STAGING_LANES ) ) ) { /* Optimize for lane available. */
     886          21 :         int lane_idx = fd_ulong_find_lsb( ~sched->staged_bitset );
     887          21 :         if( FD_UNLIKELY( lane_idx>=(int)FD_SCHED_MAX_STAGING_LANES ) ) {
     888           0 :           FD_LOG_CRIT(( "invariant violation: lane_idx %d, sched->staged_bitset %lx",
     889           0 :                         lane_idx, sched->staged_bitset ));
     890           0 :         }
     891          21 :         sched->staged_bitset = fd_ulong_set_bit( sched->staged_bitset, lane_idx );
     892          21 :         sched->staged_head_bank_idx[ lane_idx ] = fec->bank_idx;
     893          21 :         sched->staged_popcnt_wmk = fd_ulong_max( sched->staged_popcnt_wmk, (ulong)fd_ulong_popcnt( sched->staged_bitset ) );
     894          21 :         block->in_rdisp     = 1;
     895          21 :         block->staged       = 1;
     896          21 :         block->staging_lane = (ulong)lane_idx;
     897          21 :         fd_rdisp_add_block( sched->rdisp, fec->bank_idx, block->staging_lane );
     898          21 :         sched->metrics->block_added_cnt++;
     899          21 :         sched->metrics->block_added_staged_cnt++;
     900          21 :         FD_LOG_DEBUG(( "block %lu:%lu entered lane %lu: add", block->slot, fec->bank_idx, block->staging_lane ));
     901          21 :       } else {
     902             :         /* No lanes available. */
     903           3 :         block->in_rdisp = 1;
     904           3 :         block->staged   = 0;
     905           3 :         fd_rdisp_add_block( sched->rdisp, fec->bank_idx, FD_RDISP_UNSTAGED );
     906           3 :         sched->metrics->block_added_cnt++;
     907           3 :         sched->metrics->block_added_unstaged_cnt++;
     908           3 :         FD_LOG_DEBUG(( "block %lu:%lu entered lane unstaged: add", block->slot, fec->bank_idx ));
     909           3 :       }
     910          24 :     }
     911          24 :   }
     912             : 
     913          24 :   block->txn_pool_max_popcnt   = fd_ulong_max( block->txn_pool_max_popcnt, sched->depth - sched->txn_pool_free_cnt - 1UL );
     914          24 :   block->mblk_pool_max_popcnt  = fd_ulong_max( block->mblk_pool_max_popcnt, sched->depth - sched->mblk_pool_free_cnt );
     915          24 :   block->block_pool_max_popcnt = fd_ulong_max( block->block_pool_max_popcnt, sched->block_pool_popcnt );
     916             : 
     917          24 :   if( FD_UNLIKELY( block->dying ) ) {
     918             :     /* Ignore the FEC set for a dead block. */
     919           0 :     sched->metrics->bytes_dropped_cnt += fec->fec->data_sz;
     920           0 :     return 1;
     921           0 :   }
     922             : 
     923          24 :   if( FD_UNLIKELY( !block->in_rdisp ) ) {
     924             :     /* Invariant: block must be in the dispatcher at this point. */
     925           0 :     sched->print_buf_sz = 0UL;
     926           0 :     print_all( sched, block );
     927           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
     928           0 :     FD_LOG_CRIT(( "invariant violation: block->in_rdisp==0, slot %lu, parent slot %lu",
     929           0 :                   block->slot, block->parent_slot ));
     930           0 :   }
     931             : 
     932          24 :   if( FD_UNLIKELY( block->fec_eos ) ) {
     933             :     /* This means something is wrong upstream.  We're getting more FEC
     934             :        sets for a block that has already ended, or so we were told. */
     935           0 :     sched->print_buf_sz = 0UL;
     936           0 :     print_all( sched, block );
     937           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
     938           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 ));
     939           0 :   }
     940          24 :   if( FD_UNLIKELY( block->fec_eob ) ) {
     941             :     /* If the previous FEC set ingestion and parse was successful,
     942             :        block->fec_eob should be cleared.  The fact that fec_eob is set
     943             :        means that the previous batch didn't parse properly.  So this is
     944             :        a bad block.  We should refuse to replay down the fork. */
     945           0 :     FD_LOG_INFO(( "bad block: failed to parse, slot %lu, parent slot %lu", fec->slot, fec->parent_slot ));
     946           0 :     handle_bad_block( sched, block );
     947           0 :     sched->metrics->bytes_dropped_cnt += fec->fec->data_sz;
     948           0 :     return 0;
     949           0 :   }
     950          24 :   if( FD_UNLIKELY( block->child_idx!=ULONG_MAX ) ) {
     951             :     /* This means something is wrong upstream.  FEC sets are not being
     952             :        delivered in replay order.  We got a child block FEC set before
     953             :        this block was completely delivered. */
     954           0 :     sched->print_buf_sz = 0UL;
     955           0 :     print_all( sched, block );
     956           0 :     fd_sched_block_t * child_block = block_pool_ele( sched, block->child_idx );
     957           0 :     print_block_debug( sched, child_block );
     958           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
     959           0 :     FD_LOG_CRIT(( "invariant violation: block->child_idx %lu, slot %lu, parent slot %lu", block->child_idx, fec->slot, fec->parent_slot ));
     960           0 :   }
     961             : 
     962          24 :   FD_TEST( block->fec_buf_sz>=block->fec_buf_soff );
     963          24 :   if( FD_LIKELY( block->fec_buf_sz>block->fec_buf_soff ) ) {
     964             :     /* If there is residual data from the previous FEC set within the
     965             :        same batch, we move it to the beginning of the buffer and append
     966             :        the new FEC set. */
     967           0 :     memmove( block->fec_buf, block->fec_buf+block->fec_buf_soff, block->fec_buf_sz-block->fec_buf_soff );
     968           0 :   }
     969          24 :   block->fec_buf_boff += block->fec_buf_soff;
     970          24 :   block->fec_buf_sz   -= block->fec_buf_soff;
     971          24 :   block->fec_buf_soff  = 0;
     972             :   /* Addition is safe and won't overflow because we checked the FEC
     973             :      set size above. */
     974          24 :   if( FD_UNLIKELY( block->fec_buf_sz+fec->fec->data_sz>FD_SCHED_MAX_FEC_BUF_SZ ) ) {
     975             :     /* The residual carried over from the previous parse is bounded: it
     976             :        can only be a partial transaction or a microblock/batch header
     977             :        that straddles a FEC set boundary.  Any trailing bytes past the
     978             :        last microblock within the same batch are discarded by the
     979             :        parser, so they never contribute to the residual.  So residual <=
     980             :        max(sizeof(ulong), sizeof(fd_microblock_hdr_t), FD_TXN_MTU), and
     981             :        the buffer is sized to always fit the residual plus a single FEC
     982             :        set.  Otherwise, it's a bad block.  Instead of crashing, we
     983             :        should refuse to replay down the fork. */
     984           0 :     FD_LOG_INFO(( "bad block: fec_buf_sz %u, fec->data_sz %lu, slot %lu, parent slot %lu", block->fec_buf_sz, fec->fec->data_sz, fec->slot, fec->parent_slot ));
     985           0 :     handle_bad_block( sched, block );
     986           0 :     sched->metrics->bytes_dropped_cnt += fec->fec->data_sz;
     987           0 :     return 0;
     988           0 :   }
     989             : 
     990             :   /* Append the new FEC set to the end of the buffer. */
     991          24 :   fd_memcpy( block->fec_buf+block->fec_buf_sz, fec->data, fec->fec->data_sz );
     992          24 :   block->fec_buf_sz += (uint)fec->fec->data_sz;
     993          24 :   sched->metrics->bytes_ingested_cnt += fec->fec->data_sz;
     994             : 
     995          24 :   block->fec_eob = fec->is_last_in_batch;
     996          24 :   block->fec_eos = fec->is_last_in_block;
     997             : 
     998          24 :   ulong block_sz = block->shred_cnt>0 ? block->shred_blk_offs[ block->shred_cnt-1 ] : 0UL;
     999          48 :   for( ulong i=0; i<fec->shred_cnt; i++ ) {
    1000          24 :     if( FD_LIKELY( i<32UL ) ) {
    1001          24 :       block->shred_blk_offs[ block->shred_cnt++ ] = (uint)block_sz + fec->fec->shred_offs[ i ];
    1002          24 :     } else if( FD_UNLIKELY( i!=fec->shred_cnt-1UL ) ) {
    1003             :       /* We don't track shred boundaries after 32 shreds, assume they're
    1004             :          sized uniformly */
    1005           0 :       ulong num_overflow_shreds = fec->shred_cnt-32UL;
    1006           0 :       ulong overflow_idx        = i-32UL;
    1007           0 :       ulong overflow_data_sz    = fec->fec->data_sz-fec->fec->shred_offs[ 31 ];
    1008           0 :       block->shred_blk_offs[ block->shred_cnt++ ] = (uint)block_sz + fec->fec->shred_offs[ 31 ] + (uint)(overflow_data_sz / num_overflow_shreds * (overflow_idx + 1UL));
    1009           0 :     } else {
    1010           0 :       block->shred_blk_offs[ block->shred_cnt++ ] = (uint)block_sz + (uint)fec->fec->data_sz;
    1011           0 :     }
    1012          24 :   }
    1013             : 
    1014          24 :   int err = fd_sched_parse( sched, block, fec->alut_ctx );
    1015             : 
    1016          24 :   if( FD_UNLIKELY( err==FD_SCHED_BAD_BLOCK ) ) {
    1017           0 :     handle_bad_block( sched, block );
    1018           0 :     sched->metrics->bytes_dropped_cnt += block->fec_buf_sz-block->fec_buf_soff;
    1019           0 :     return 0;
    1020           0 :   }
    1021             : 
    1022          24 :   if( FD_UNLIKELY( (fec->is_last_in_batch||fec->is_last_in_block) && (block->txns_rem||block->mblks_rem||block->fec_eob) ) ) {
    1023             :     /* A malformed block that fails to parse out exactly as many
    1024             :        transactions and microblocks as it should.
    1025             : 
    1026             :        Upon getting a last-in-batch FEC, everything in the ongoing batch
    1027             :        should completely parse, and the eob flag should be reset.  There
    1028             :        should be no go around for a last-in-batch FEC. */
    1029           0 :     FD_LOG_INFO(( "bad block: bytes_rem %u, txns_rem %lu, mblks_rem %lu, fec_eob %d, slot %lu, parent slot %lu", block->fec_buf_sz-block->fec_buf_soff, block->txns_rem, block->mblks_rem, block->fec_eob, block->slot, block->parent_slot ));
    1030           0 :     handle_bad_block( sched, block );
    1031           0 :     return 0;
    1032           0 :   }
    1033             : 
    1034          24 :   if( FD_UNLIKELY( block->fec_eos && !block->last_mblk_is_tick ) ) {
    1035             :     /* The last microblock should be a tick.
    1036             : 
    1037             :        Note that this early parse-time detection could cause us to throw
    1038             :        a slightly different error from Agave, in the case that there are
    1039             :        too few ticks, since the tick count check precedes the trailing
    1040             :        entry check in Agave.  That being said, ultimately a
    1041             :        TRAILING_ENTRY renders a block invalid, regardless of anything
    1042             :        else. */
    1043           0 :     FD_LOG_INFO(( "bad block: TRAILING_ENTRY, slot %lu, parent slot %lu, mblk_cnt %u", block->slot, block->parent_slot, block->mblk_cnt ));
    1044           0 :     handle_bad_block( sched, block );
    1045           0 :     return 0;
    1046           0 :   }
    1047             : 
    1048             :   /* We just received a FEC set, which may have made all transactions in
    1049             :      a partially parsed microblock available.  If this were a malformed
    1050             :      block that ends in a non-tick microblock, there's not going to be a
    1051             :      hashing task from the missing ending tick to drain the mixin queue.
    1052             :      So we try to drain the mixin queue right here.  Another option is
    1053             :      to drain it at dispatch time, when we are about to dispatch the end
    1054             :      of block signal, right before the check for whether block should
    1055             :      end. */
    1056          24 :   int mixin_res;
    1057          24 :   while( (mixin_res=maybe_mixin( sched, block )) ) {
    1058           0 :     if( FD_UNLIKELY( mixin_res==-1 ) ) {
    1059           0 :       handle_bad_block( sched, block );
    1060           0 :       return 0;
    1061           0 :     }
    1062           0 :     FD_TEST( mixin_res==1||mixin_res==2 );
    1063           0 :   }
    1064             : 
    1065             :   /* Check if we need to set the active block. */
    1066          24 :   check_or_set_active_block( sched );
    1067             : 
    1068          24 :   return 1;
    1069          24 : }
    1070             : 
    1071             : ulong
    1072          45 : fd_sched_task_next_ready( fd_sched_t * sched, fd_sched_task_t * out ) {
    1073          45 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    1074          45 :   FD_TEST( ref_q_empty( sched->ref_q ) );
    1075             : 
    1076          45 :   ulong exec_ready_bitset0 = sched->txn_exec_ready_bitset[ 0 ];
    1077          45 :   ulong exec_fully_ready_bitset = sched->sigverify_ready_bitset[ 0 ] & sched->poh_ready_bitset[ 0 ] & exec_ready_bitset0;
    1078          45 :   if( FD_UNLIKELY( !exec_fully_ready_bitset ) ) {
    1079             :     /* Early exit if no exec tiles available. */
    1080           0 :     return 0UL;
    1081           0 :   }
    1082             : 
    1083          45 :   if( FD_UNLIKELY( sched->active_bank_idx==ULONG_MAX ) ) {
    1084             :     /* No need to try activating a block.  If we're in this state,
    1085             :        there's truly nothing to execute.  We will activate something
    1086             :        when we ingest a FEC set with transactions. */
    1087           9 :     return 0UL;
    1088           9 :   }
    1089             : 
    1090          36 :   out->task_type = FD_SCHED_TT_NULL;
    1091             : 
    1092             :   /* We could in theory reevaluate staging lane allocation here and do
    1093             :      promotion/demotion as needed.  It's a policy decision to minimize
    1094             :      fork churn for now and just execute down the same active fork. */
    1095             : 
    1096          36 :   ulong bank_idx = sched->active_bank_idx;
    1097          36 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    1098          36 :   if( FD_UNLIKELY( block_should_deactivate( block ) ) ) {
    1099           0 :     sched->print_buf_sz = 0UL;
    1100           0 :     print_all( sched, block );
    1101           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
    1102           0 :     FD_LOG_CRIT(( "invariant violation: active block %lu:%lu is not activatable nor has anything in-flight", block->slot, sched->active_bank_idx ));
    1103           0 :   }
    1104             : 
    1105          36 :   block->txn_pool_max_popcnt   = fd_ulong_max( block->txn_pool_max_popcnt, sched->depth - sched->txn_pool_free_cnt - 1UL );
    1106          36 :   block->mblk_pool_max_popcnt  = fd_ulong_max( block->mblk_pool_max_popcnt, sched->depth - sched->mblk_pool_free_cnt );
    1107          36 :   block->block_pool_max_popcnt = fd_ulong_max( block->block_pool_max_popcnt, sched->block_pool_popcnt );
    1108             : 
    1109          36 :   if( FD_UNLIKELY( !block->block_start_signaled ) ) {
    1110          24 :     out->task_type = FD_SCHED_TT_BLOCK_START;
    1111          24 :     out->block_start->bank_idx        = bank_idx;
    1112          24 :     out->block_start->parent_bank_idx = block->parent_idx;
    1113          24 :     out->block_start->slot            = block->slot;
    1114          24 :     block->block_start_signaled = 1;
    1115          24 :     sched->next_ready_last_tick     = fd_tickcount();
    1116          24 :     sched->next_ready_last_bank_idx = bank_idx;
    1117          24 :     return 1UL;
    1118          24 :   }
    1119             : 
    1120          12 :   ulong exec_tile_idx0 = fd_ulong_if( !!exec_fully_ready_bitset, (ulong)fd_ulong_find_lsb( exec_fully_ready_bitset ), ULONG_MAX );
    1121          12 :   ulong exec_queued_cnt = block->txn_parsed_cnt-block->txn_exec_in_flight_cnt-block->txn_exec_done_cnt;
    1122          12 :   if( FD_LIKELY( exec_queued_cnt>0UL && fd_ulong_popcnt( exec_fully_ready_bitset ) ) ) { /* Optimize for no fork switching. */
    1123             :     /* Transaction execution has the highest priority.  Current mainnet
    1124             :        block times are very much dominated by critical path transaction
    1125             :        execution.  To achieve the fastest block replay speed, we can't
    1126             :        afford to make any mistake in critical path dispatching.  Any
    1127             :        deviation from perfect critical path dispatching is basically
    1128             :        irrecoverable.  As such, we try to keep all the exec tiles busy
    1129             :        with transaction execution, but we allow at most one transaction
    1130             :        to be in-flight per exec tile.  This is to ensure that whenever a
    1131             :        critical path transaction completes, we have at least one exec
    1132             :        tile, e.g. the one that just completed said transaction, readily
    1133             :        available to continue executing down the critical path. */
    1134           0 :     out->txn_exec->txn_idx = fd_rdisp_get_next_ready( sched->rdisp, bank_idx );
    1135           0 :     if( FD_UNLIKELY( out->txn_exec->txn_idx==0UL ) ) {
    1136             :       /* There are transactions queued but none ready for execution.
    1137             :          This implies that there must be in-flight transactions on whose
    1138             :          completion the queued transactions depend. So we return and
    1139             :          wait for those in-flight transactions to retire.  This is a
    1140             :          policy decision to execute as much as we can down the current
    1141             :          fork. */
    1142           0 :       if( FD_UNLIKELY( !block->txn_exec_in_flight_cnt ) ) {
    1143           0 :         sched->print_buf_sz = 0UL;
    1144           0 :         print_all( sched, block );
    1145           0 :         FD_LOG_NOTICE(( "%s", sched->print_buf ));
    1146           0 :         FD_LOG_CRIT(( "invariant violation: no ready transaction found but block->txn_exec_in_flight_cnt==0" ));
    1147           0 :       }
    1148             : 
    1149             :       /* Next up are PoH tasks.  Same dispatching policy as sigverify
    1150             :          tasks. */
    1151           0 :       ulong poh_ready_bitset = exec_fully_ready_bitset;
    1152           0 :       ulong poh_hashing_queued_cnt = block->mblk_cnt-block->poh_hashing_in_flight_cnt-block->poh_hashing_done_cnt;
    1153           0 :       if( FD_LIKELY( poh_hashing_queued_cnt>0UL && fd_ulong_popcnt( poh_ready_bitset )>fd_int_if( block->txn_exec_in_flight_cnt>0U, 0, 1 ) ) ) {
    1154           0 :         dispatch_poh( sched, block, bank_idx, fd_ulong_find_lsb( poh_ready_bitset ), out );
    1155           0 :         sched->next_ready_last_tick     = fd_tickcount();
    1156           0 :         sched->next_ready_last_bank_idx = bank_idx;
    1157           0 :         return 1UL;
    1158           0 :       }
    1159             : 
    1160             :       /* Dispatch more sigverify tasks only if at least one exec tile is
    1161             :          executing transactions or completely idle.  Allow at most one
    1162             :          sigverify task in-flight per tile, and only dispatch to
    1163             :          completely idle tiles. */
    1164           0 :       ulong sigverify_ready_bitset = exec_fully_ready_bitset;
    1165           0 :       ulong sigverify_queued_cnt = block->txn_parsed_cnt-block->txn_sigverify_in_flight_cnt-block->txn_sigverify_done_cnt;
    1166           0 :       if( FD_LIKELY( sigverify_queued_cnt>0UL && fd_ulong_popcnt( sigverify_ready_bitset )>fd_int_if( block->txn_exec_in_flight_cnt>0U, 0, 1 ) ) ) {
    1167           0 :         dispatch_sigverify( sched, block, bank_idx, fd_ulong_find_lsb( sigverify_ready_bitset ), out );
    1168           0 :         sched->next_ready_last_tick = sched->txn_info_pool[ out->txn_sigverify->txn_idx ].tick_sigverify_disp = fd_tickcount();
    1169           0 :         sched->next_ready_last_bank_idx = bank_idx;
    1170           0 :         return 1UL;
    1171           0 :       }
    1172           0 :       return 0UL;
    1173           0 :     }
    1174           0 :     out->task_type = FD_SCHED_TT_TXN_EXEC;
    1175           0 :     out->txn_exec->bank_idx = bank_idx;
    1176           0 :     out->txn_exec->slot     = block->slot;
    1177           0 :     out->txn_exec->exec_idx = exec_tile_idx0;
    1178           0 :     FD_TEST( out->txn_exec->exec_idx!=ULONG_MAX );
    1179             : 
    1180           0 :     long now = fd_tickcount();
    1181           0 :     ulong delta = (ulong)(now-sched->txn_in_flight_last_tick);
    1182           0 :     ulong txn_exec_busy_cnt = sched->exec_cnt-(ulong)fd_ulong_popcnt( exec_ready_bitset0 );
    1183           0 :     sched->metrics->txn_none_in_flight_tickcount     += fd_ulong_if( txn_exec_busy_cnt==0UL && sched->txn_in_flight_last_tick!=LONG_MAX, delta, 0UL );
    1184           0 :     sched->metrics->txn_weighted_in_flight_tickcount += fd_ulong_if( txn_exec_busy_cnt!=0UL, delta, 0UL );
    1185           0 :     sched->metrics->txn_weighted_in_flight_cnt       += delta*txn_exec_busy_cnt;
    1186           0 :     sched->txn_in_flight_last_tick = now;
    1187             : 
    1188           0 :     sched->txn_info_pool[ out->txn_exec->txn_idx ].tick_exec_disp = now;
    1189             : 
    1190           0 :     sched->txn_exec_ready_bitset[ 0 ] = fd_ulong_clear_bit( exec_ready_bitset0, (int)exec_tile_idx0);
    1191           0 :     sched->tile_to_bank_idx[ exec_tile_idx0 ] = bank_idx;
    1192             : 
    1193           0 :     block->txn_exec_in_flight_cnt++;
    1194           0 :     sched->metrics->txn_max_in_flight_cnt = fd_uint_max( sched->metrics->txn_max_in_flight_cnt, block->txn_exec_in_flight_cnt );
    1195             : 
    1196           0 :     if( FD_UNLIKELY( (~sched->txn_exec_ready_bitset[ 0 ])&(~sched->sigverify_ready_bitset[ 0 ])&(~sched->poh_ready_bitset[ 0 ])&fd_ulong_mask_lsb( (int)sched->exec_cnt ) ) ) FD_LOG_CRIT(( "invariant violation: txn_exec_ready_bitset 0x%lx sigverify_ready_bitset 0x%lx poh_ready_bitset 0x%lx", sched->txn_exec_ready_bitset[ 0 ], sched->sigverify_ready_bitset[ 0 ], sched->poh_ready_bitset[ 0 ] ));
    1197           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 ] );
    1198           0 :     if( FD_UNLIKELY( block->txn_exec_in_flight_cnt+block->txn_sigverify_in_flight_cnt+block->poh_hashing_in_flight_cnt!=total_exec_busy_cnt ) ) {
    1199             :       /* Ideally we'd simply assert that the two sides of the equation
    1200             :          are equal.  But abandoned blocks throw a wrench into this.  We
    1201             :          allow abandoned blocks to have in-flight transactions that are
    1202             :          naturally drained while we try to dispatch from another block.
    1203             :          In such cases, the total number of in-flight transactions
    1204             :          should include the abandoned blocks too.  The contract is that
    1205             :          blocks with in-flight transactions cannot be abandoned or
    1206             :          demoted from rdisp.  So a dying block has to be the head of one
    1207             :          of the staging lanes. */
    1208             :       // FIXME This contract no longer true if we implement immediate
    1209             :       // demotion of abandoned blocks.
    1210           0 :       ulong total_in_flight = 0UL;
    1211           0 :       for( int l=0; l<(int)FD_SCHED_MAX_STAGING_LANES; l++ ) {
    1212           0 :         if( fd_ulong_extract_bit( sched->staged_bitset, l ) ) {
    1213           0 :           fd_sched_block_t * staged_block = block_pool_ele( sched, sched->staged_head_bank_idx[ l ] );
    1214           0 :           if( FD_UNLIKELY( block_is_in_flight( staged_block )&&!(staged_block==block||staged_block->dying) ) ) {
    1215           0 :             sched->print_buf_sz = 0UL;
    1216           0 :             print_all( sched, staged_block );
    1217           0 :             FD_LOG_NOTICE(( "%s", sched->print_buf ));
    1218           0 :             FD_LOG_CRIT(( "invariant violation: in-flight block is neither active nor dying" ));
    1219           0 :           }
    1220           0 :           total_in_flight += staged_block->txn_exec_in_flight_cnt;
    1221           0 :           total_in_flight += staged_block->txn_sigverify_in_flight_cnt;
    1222           0 :           total_in_flight += staged_block->poh_hashing_in_flight_cnt;
    1223           0 :         }
    1224           0 :       }
    1225           0 :       if( FD_UNLIKELY( total_in_flight!=total_exec_busy_cnt ) ) {
    1226           0 :         sched->print_buf_sz = 0UL;
    1227           0 :         print_all( sched, block );
    1228           0 :         FD_LOG_NOTICE(( "%s", sched->print_buf ));
    1229           0 :         FD_LOG_CRIT(( "invariant violation: total_in_flight %lu != total_exec_busy_cnt %lu", total_in_flight, total_exec_busy_cnt ));
    1230           0 :       }
    1231           0 :       FD_LOG_DEBUG(( "exec_busy_cnt %lu checks out", total_exec_busy_cnt ));
    1232           0 :     }
    1233           0 :     sched->next_ready_last_tick     = now;
    1234           0 :     sched->next_ready_last_bank_idx = bank_idx;
    1235           0 :     return 1UL;
    1236           0 :   }
    1237             : 
    1238             :   /* At this point txn_queued_cnt==0 */
    1239             : 
    1240             :   /* Next up are PoH tasks.  Same dispatching policy as sigverify. */
    1241          12 :   ulong poh_ready_bitset = exec_fully_ready_bitset;
    1242          12 :   ulong poh_hashing_queued_cnt = block->mblk_cnt-block->poh_hashing_in_flight_cnt-block->poh_hashing_done_cnt;
    1243          12 :   if( FD_LIKELY( poh_hashing_queued_cnt>0UL && fd_ulong_popcnt( poh_ready_bitset )>fd_int_if( block->fec_eos||block->txn_exec_in_flight_cnt>0U||sched->exec_cnt==1UL, 0, 1 ) ) ) {
    1244           9 :     dispatch_poh( sched, block, bank_idx, fd_ulong_find_lsb( poh_ready_bitset ), out );
    1245           9 :     sched->next_ready_last_tick     = fd_tickcount();
    1246           9 :     sched->next_ready_last_bank_idx = bank_idx;
    1247           9 :     return 1UL;
    1248           9 :   }
    1249             : 
    1250             :   /* Try to dispatch a sigverify task, but leave one exec tile idle for
    1251             :      critical path execution, unless there's not going to be any more
    1252             :      real transactions for the critical path.  In the degenerate case of
    1253             :      only one exec tile, keep it busy. */
    1254           3 :   ulong sigverify_ready_bitset = exec_fully_ready_bitset;
    1255           3 :   ulong sigverify_queued_cnt = block->txn_parsed_cnt-block->txn_sigverify_in_flight_cnt-block->txn_sigverify_done_cnt;
    1256           3 :   if( FD_LIKELY( sigverify_queued_cnt>0UL && fd_ulong_popcnt( sigverify_ready_bitset )>fd_int_if( block->fec_eos||block->txn_exec_in_flight_cnt>0U||sched->exec_cnt==1UL, 0, 1 ) ) ) {
    1257           0 :     dispatch_sigverify( sched, block, bank_idx, fd_ulong_find_lsb( sigverify_ready_bitset ), out );
    1258           0 :     sched->next_ready_last_tick     = sched->txn_info_pool[ out->txn_sigverify->txn_idx ].tick_sigverify_disp = fd_tickcount();
    1259           0 :     sched->next_ready_last_bank_idx = bank_idx;
    1260           0 :     return 1UL;
    1261           0 :   }
    1262             : 
    1263           3 :   if( FD_UNLIKELY( block_should_signal_end( block ) ) ) {
    1264           3 :     FD_TEST( block->block_start_signaled );
    1265           3 :     if( FD_UNLIKELY( verify_ticks_final( block ) ) ) {
    1266             :       /* Tick verification can't be done at parse time (except for
    1267             :          TRAILING_ENTRY), because we may not know the expected number of
    1268             :          hashes yet.  It can't be driven by transaction dispatch or
    1269             :          completion, because the block may be empty.  Similary, it can't
    1270             :          be driven by PoH hashing, because a bad block may simply not
    1271             :          have any microblocks. */
    1272           3 :       handle_bad_block( sched, block );
    1273           3 :       out->task_type = FD_SCHED_TT_MARK_DEAD;
    1274           3 :       out->mark_dead->bank_idx = bank_idx;
    1275           3 :       sched->next_ready_last_tick     = fd_tickcount();
    1276           3 :       sched->next_ready_last_bank_idx = bank_idx;
    1277           3 :       return 1UL;
    1278           3 :     }
    1279           0 :     out->task_type = FD_SCHED_TT_BLOCK_END;
    1280           0 :     out->block_end->bank_idx = bank_idx;
    1281           0 :     block->block_end_signaled = 1;
    1282           0 :     FD_TEST( block->refcnt );
    1283           0 :     block->refcnt = 0;
    1284           0 :     FD_TEST( ref_q_avail( sched->ref_q ) );
    1285           0 :     ref_q_push_tail( sched->ref_q, bank_idx );
    1286           0 :     sched->next_ready_last_tick     = fd_tickcount();
    1287           0 :     sched->next_ready_last_bank_idx = bank_idx;
    1288           0 :     return 1UL;
    1289           0 :   }
    1290             : 
    1291             :   /* Nothing queued for the active block.  If we haven't received all
    1292             :      the FEC sets for it, then return and wait for more FEC sets, while
    1293             :      there are in-flight transactions.  This is a policy decision to
    1294             :      minimize fork churn and allow for executing down the current fork
    1295             :      as much as we can.  If we have received all the FEC sets for it,
    1296             :      then we'd still like to return and wait for the in-flight
    1297             :      transactions to retire, before switching to a different block.
    1298             : 
    1299             :      Either way, there should be in-flight transactions.  We deactivate
    1300             :      the active block the moment we exhausted transactions from it.
    1301             : 
    1302             :      We don't assert block_is_in_flight() here because there might be
    1303             :      in-flight tasks from dying blocks that are preventing us from
    1304             :      dispatching anything momentarily.  So we assert the global exec
    1305             :      tile busy count.  This doesn't lose too much assertion coverage
    1306             :      since dying blocks are few and far between. */
    1307           0 :   ulong total_exec_busy_cnt = sched->exec_cnt-(ulong)fd_ulong_popcnt( exec_fully_ready_bitset );
    1308           0 :   if( FD_UNLIKELY( !total_exec_busy_cnt ) ) {
    1309           0 :     sched->print_buf_sz = 0UL;
    1310           0 :     print_all( sched, block );
    1311           0 :     FD_LOG_NOTICE(( "%s", sched->print_buf ));
    1312           0 :     FD_LOG_CRIT(( "invariant violation: expected in-flight tasks but none found" ));
    1313           0 :   }
    1314             : 
    1315           0 :   return 0UL;
    1316           0 : }
    1317             : 
    1318             : int
    1319          33 : fd_sched_task_done( fd_sched_t * sched, ulong task_type, ulong txn_idx, ulong exec_idx, void * data ) {
    1320          33 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    1321             : 
    1322          33 :   ulong bank_idx = ULONG_MAX;
    1323          33 :   switch( task_type ) {
    1324          24 :     case FD_SCHED_TT_BLOCK_START:
    1325          24 :     case FD_SCHED_TT_BLOCK_END: {
    1326          24 :       (void)txn_idx;
    1327          24 :       (void)data;
    1328          24 :       bank_idx = sched->active_bank_idx;
    1329          24 :       break;
    1330          24 :     }
    1331           0 :     case FD_SCHED_TT_TXN_EXEC:
    1332           0 :     case FD_SCHED_TT_TXN_SIGVERIFY: {
    1333           0 :       (void)data;
    1334           0 :       FD_TEST( txn_idx < sched->depth );
    1335           0 :       bank_idx = sched->tile_to_bank_idx[ exec_idx ];
    1336           0 :       break;
    1337           0 :     }
    1338           9 :     case FD_SCHED_TT_POH_HASH: {
    1339           9 :       (void)txn_idx;
    1340           9 :       bank_idx = sched->tile_to_bank_idx[ exec_idx ];
    1341           9 :       break;
    1342           0 :     }
    1343           0 :     default: FD_LOG_CRIT(( "unsupported task_type %lu", task_type ));
    1344          33 :   }
    1345          33 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    1346             : 
    1347          33 :   if( FD_UNLIKELY( !block->in_sched ) ) {
    1348           0 :     FD_LOG_CRIT(( "invariant violation: block->in_sched==0, block %lu:%lu, parent slot %lu",
    1349           0 :                   block->slot, bank_idx, block->parent_slot ));
    1350           0 :   }
    1351          33 :   if( FD_UNLIKELY( !block->staged ) ) {
    1352             :     /* Invariant: only staged blocks can have in-flight transactions. */
    1353           0 :     FD_LOG_CRIT(( "invariant violation: block->staged==0, block %lu:%lu, parent slot %lu",
    1354           0 :                   block->slot, bank_idx, block->parent_slot ));
    1355           0 :   }
    1356          33 :   if( FD_UNLIKELY( !block->in_rdisp ) ) {
    1357             :     /* Invariant: staged blocks must be in the dispatcher. */
    1358           0 :     FD_LOG_CRIT(( "invariant violation: block->in_rdisp==0, block %lu:%lu, parent slot %lu",
    1359           0 :                   block->slot, bank_idx, block->parent_slot ));
    1360           0 :   }
    1361             : 
    1362          33 :   block->txn_pool_max_popcnt   = fd_ulong_max( block->txn_pool_max_popcnt, sched->depth - sched->txn_pool_free_cnt - 1UL );
    1363          33 :   block->mblk_pool_max_popcnt  = fd_ulong_max( block->mblk_pool_max_popcnt, sched->depth - sched->mblk_pool_free_cnt );
    1364          33 :   block->block_pool_max_popcnt = fd_ulong_max( block->block_pool_max_popcnt, sched->block_pool_popcnt );
    1365             : 
    1366          33 :   int exec_tile_idx = (int)exec_idx;
    1367             : 
    1368          33 :   switch( task_type ) {
    1369          24 :     case FD_SCHED_TT_BLOCK_START: {
    1370          24 :       FD_TEST( !block->block_start_done );
    1371          24 :       block->block_start_done = 1;
    1372          24 :       break;
    1373          24 :     }
    1374           0 :     case FD_SCHED_TT_BLOCK_END: {
    1375             :       /* It may seem redundant to be invoking task_done() on these
    1376             :          somewhat fake tasks.  But these are necessary to drive state
    1377             :          transition for empty blocks or slow blocks. */
    1378           0 :       FD_TEST( !block->block_end_done );
    1379           0 :       block->block_end_done = 1;
    1380           0 :       sched->print_buf_sz = 0UL;
    1381           0 :       print_block_metrics( sched, block );
    1382           0 :       FD_LOG_DEBUG(( "block %lu:%lu replayed fully: %s", block->slot, bank_idx, sched->print_buf ));
    1383           0 :       break;
    1384           0 :     }
    1385           0 :     case FD_SCHED_TT_TXN_EXEC: {
    1386           0 :       long now = fd_tickcount();
    1387           0 :       ulong delta = (ulong)(now-sched->txn_in_flight_last_tick);
    1388           0 :       ulong txn_exec_busy_cnt = sched->exec_cnt-(ulong)fd_ulong_popcnt( sched->txn_exec_ready_bitset[ 0 ] );
    1389           0 :       sched->metrics->txn_weighted_in_flight_tickcount += delta;
    1390           0 :       sched->metrics->txn_weighted_in_flight_cnt       += delta*txn_exec_busy_cnt;
    1391           0 :       sched->txn_in_flight_last_tick = now;
    1392             : 
    1393           0 :       sched->txn_info_pool[ txn_idx ].tick_exec_done = now;
    1394             : 
    1395           0 :       block->txn_exec_done_cnt++;
    1396           0 :       block->txn_exec_in_flight_cnt--;
    1397           0 :       FD_TEST( !fd_ulong_extract_bit( sched->txn_exec_ready_bitset[ 0 ], exec_tile_idx ) );
    1398           0 :       sched->txn_exec_ready_bitset[ 0 ] = fd_ulong_set_bit( sched->txn_exec_ready_bitset[ 0 ], exec_tile_idx );
    1399           0 :       sched->metrics->txn_exec_done_cnt++;
    1400           0 :       txn_bitset_insert( sched->exec_done_set, txn_idx );
    1401           0 :       sched->txn_info_pool[ txn_idx ].flags |= FD_SCHED_TXN_EXEC_DONE;
    1402           0 :       if( txn_bitset_test( sched->sigverify_done_set, txn_idx ) && txn_bitset_test( sched->poh_mixin_done_set, txn_idx ) ) {
    1403             :         /* Release the txn_idx if all tasks on it are done.  This is
    1404             :            guaranteed to only happen once per transaction because
    1405             :            whichever one completed first would not release. */
    1406           0 :         fd_rdisp_complete_txn( sched->rdisp, txn_idx, 1 );
    1407           0 :         sched->txn_pool_free_cnt++;
    1408           0 :         block->txn_done_cnt++;
    1409           0 :         sched->metrics->txn_done_cnt++;
    1410           0 :       } else {
    1411           0 :         fd_rdisp_complete_txn( sched->rdisp, txn_idx, 0 );
    1412           0 :       }
    1413           0 :       break;
    1414           0 :     }
    1415           0 :     case FD_SCHED_TT_TXN_SIGVERIFY: {
    1416           0 :       sched->txn_info_pool[ txn_idx ].tick_sigverify_done = fd_tickcount();
    1417           0 :       block->txn_sigverify_done_cnt++;
    1418           0 :       block->txn_sigverify_in_flight_cnt--;
    1419           0 :       FD_TEST( !fd_ulong_extract_bit( sched->sigverify_ready_bitset[ 0 ], exec_tile_idx ) );
    1420           0 :       sched->sigverify_ready_bitset[ 0 ] = fd_ulong_set_bit( sched->sigverify_ready_bitset[ 0 ], exec_tile_idx );
    1421           0 :       sched->metrics->txn_sigverify_done_cnt++;
    1422           0 :       txn_bitset_insert( sched->sigverify_done_set, txn_idx );
    1423           0 :       sched->txn_info_pool[ txn_idx ].flags |= FD_SCHED_TXN_SIGVERIFY_DONE;
    1424           0 :       if( txn_bitset_test( sched->exec_done_set, txn_idx ) && txn_bitset_test( sched->poh_mixin_done_set, txn_idx ) ) {
    1425             :         /* Release the txn_idx if all tasks on it are done.  This is
    1426             :            guaranteed to only happen once per transaction because
    1427             :            whichever one completed first would not release. */
    1428           0 :         fd_rdisp_complete_txn( sched->rdisp, txn_idx, 1 );
    1429           0 :         sched->txn_pool_free_cnt++;
    1430           0 :         block->txn_done_cnt++;
    1431           0 :         sched->metrics->txn_done_cnt++;
    1432           0 :       }
    1433           0 :       break;
    1434           0 :     }
    1435           9 :     case FD_SCHED_TT_POH_HASH: {
    1436           9 :       block->poh_hashing_in_flight_cnt--;
    1437           9 :       FD_TEST( !fd_ulong_extract_bit( sched->poh_ready_bitset[ 0 ], exec_tile_idx ) );
    1438           9 :       sched->poh_ready_bitset[ 0 ] = fd_ulong_set_bit( sched->poh_ready_bitset[ 0 ], exec_tile_idx );
    1439           9 :       fd_execrp_poh_hash_done_msg_t * msg = fd_type_pun( data );
    1440           9 :       fd_sched_mblk_t * mblk = sched->mblk_pool+msg->mblk_idx;
    1441           9 :       mblk->curr_hashcnt += msg->hashcnt;
    1442           9 :       memcpy( mblk->curr_hash, msg->hash, sizeof(fd_hash_t) );
    1443           9 :       ulong hashcnt_todo = mblk->hashcnt-mblk->curr_hashcnt;
    1444           9 :       if( !hashcnt_todo ) {
    1445           9 :         block->poh_hashing_done_cnt++;
    1446           9 :         sched->metrics->mblk_poh_hashed_cnt++;
    1447           9 :         if( FD_LIKELY( !mblk->is_tick ) ) {
    1448             :           /* This is not a tick.  Enqueue for mixin. */
    1449           0 :           mblk_slist_idx_push_tail( block->mblks_mixin_in_progress, msg->mblk_idx, sched->mblk_pool );
    1450           9 :         } else {
    1451             :           /* This is a tick.  No need to mixin.  Check the hash value
    1452             :              right away. */
    1453           9 :           block->poh_hash_cmp_done_cnt++;
    1454           9 :           sched->metrics->mblk_poh_done_cnt++;
    1455           9 :           free_mblk( sched, block, (uint)msg->mblk_idx );
    1456           9 :           if( FD_UNLIKELY( memcmp( mblk->curr_hash, mblk->end_hash, sizeof(fd_hash_t) ) ) ) {
    1457           0 :             FD_BASE58_ENCODE_32_BYTES( mblk->curr_hash->hash, our_str );
    1458           0 :             FD_BASE58_ENCODE_32_BYTES( mblk->end_hash->hash, ref_str );
    1459           0 :             FD_LOG_INFO(( "bad block: poh hash mismatch on mblk %lu, ours %s, claimed %s, hashcnt %lu, is_tick, slot %lu, parent slot %lu", msg->mblk_idx, our_str, ref_str, mblk->hashcnt, block->slot, block->parent_slot ));
    1460           0 :             handle_bad_block( sched, block );
    1461           0 :             return -1;
    1462           0 :           }
    1463           9 :         }
    1464             :         /* Try to drain the mixin queue. */
    1465           9 :         int mixin_res;
    1466           9 :         while( (mixin_res=maybe_mixin( sched, block )) ) {
    1467           0 :           if( FD_UNLIKELY( mixin_res==-1 ) ) {
    1468           0 :             handle_bad_block( sched, block );
    1469           0 :             return -1;
    1470           0 :           }
    1471           0 :           FD_TEST( mixin_res==1||mixin_res==2 );
    1472           0 :         }
    1473           9 :       } else {
    1474           0 :         mblk_slist_idx_push_tail( block->mblks_hashing_in_progress, msg->mblk_idx, sched->mblk_pool );
    1475           0 :       }
    1476           9 :       if( FD_UNLIKELY( verify_ticks_eager( block ) ) ) {
    1477           6 :         handle_bad_block( sched, block );
    1478           6 :         return -1;
    1479           6 :       }
    1480           3 :       break;
    1481           9 :     }
    1482          33 :   }
    1483             : 
    1484          27 :   if( FD_UNLIKELY( block->dying && !block_is_in_flight( block ) ) ) {
    1485           0 :     if( FD_UNLIKELY( sched->active_bank_idx==bank_idx ) ) {
    1486           0 :       FD_LOG_CRIT(( "invariant violation: active block %lu:%lu shouldn't be dying, parent slot %lu",
    1487           0 :                     block->slot, bank_idx, block->parent_slot ));
    1488           0 :     }
    1489           0 :     FD_LOG_DEBUG(( "dying block %lu:%lu drained", block->slot, bank_idx ));
    1490           0 :     subtree_abandon( sched, block );
    1491           0 :     try_activate_block( sched );
    1492           0 :     return 0;
    1493           0 :   }
    1494             : 
    1495          27 :   if( FD_UNLIKELY( !block->dying && sched->active_bank_idx!=bank_idx ) ) {
    1496             :     /* Block is not dead.  So we should be actively replaying it. */
    1497           0 :     fd_sched_block_t * active_block = block_pool_ele( sched, sched->active_bank_idx );
    1498           0 :     FD_LOG_CRIT(( "invariant violation: sched->active_bank_idx %lu, slot %lu, parent slot %lu, bank_idx %lu, slot %lu, parent slot %lu",
    1499           0 :                   sched->active_bank_idx, active_block->slot, active_block->parent_slot,
    1500           0 :                   bank_idx, block->slot, block->parent_slot ));
    1501           0 :   }
    1502             : 
    1503          27 :   maybe_switch_block( sched, bank_idx );
    1504             : 
    1505          27 :   return 0;
    1506          27 : }
    1507             : 
    1508             : void
    1509           0 : fd_sched_block_abandon( fd_sched_t * sched, ulong bank_idx ) {
    1510           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    1511           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    1512             : 
    1513           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    1514           0 :   if( FD_UNLIKELY( !block->in_sched ) ) {
    1515           0 :     FD_LOG_CRIT(( "invariant violation: block->in_sched==0, block %lu:%lu, parent slot %lu",
    1516           0 :                   block->slot, bank_idx, block->parent_slot ));
    1517           0 :   }
    1518             : 
    1519           0 :   FD_LOG_INFO(( "abandoning block %lu:%lu", block->slot, bank_idx ));
    1520           0 :   sched->print_buf_sz = 0UL;
    1521           0 :   print_all( sched, block );
    1522           0 :   FD_LOG_DEBUG(( "%s", sched->print_buf ));
    1523             : 
    1524           0 :   subtree_abandon( sched, block );
    1525           0 :   try_activate_block( sched );
    1526           0 : }
    1527             : 
    1528             : void
    1529           0 : fd_sched_cancel( fd_sched_t * sched, ulong bank_idx ) {
    1530           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    1531           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    1532             : 
    1533           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    1534           0 :   if( FD_UNLIKELY( !block->in_sched ) ) return;
    1535           0 :   FD_LOG_INFO(( "canceling subtree at block %lu:%lu", block->slot, bank_idx ));
    1536           0 :   fd_sched_block_t * parent = block_pool_ele( sched, block->parent_idx );
    1537           0 :   if( FD_LIKELY( parent ) ) {
    1538             :     /* Splice the block out of its parent's children list. */
    1539           0 :     ulong block_idx = block_to_idx( sched, block );
    1540           0 :     ulong * idx_p = &parent->child_idx;
    1541           0 :     while( *idx_p!=block_idx ) {
    1542           0 :       idx_p = &(block_pool_ele( sched, *idx_p )->sibling_idx);
    1543           0 :     }
    1544           0 :     *idx_p = block->sibling_idx;
    1545           0 :   }
    1546           0 :   subtree_prune( sched, bank_idx, ULONG_MAX );
    1547           0 : }
    1548             : 
    1549             : void
    1550          12 : fd_sched_block_add_done( fd_sched_t * sched, ulong bank_idx, ulong parent_bank_idx, ulong slot ) {
    1551          12 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    1552          12 :   FD_TEST( bank_idx<sched->block_cnt_max );
    1553             : 
    1554          12 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    1555          12 :   add_block( sched, bank_idx, parent_bank_idx );
    1556          12 :   block->slot                   = slot;
    1557          12 :   block->fec_eos                = 1;
    1558          12 :   block->block_start_signaled   = 1;
    1559          12 :   block->block_end_signaled     = 1;
    1560          12 :   block->block_start_done       = 1;
    1561          12 :   block->block_end_done         = 1;
    1562          12 :   block->refcnt                 = 0;
    1563          12 :   if( FD_LIKELY( parent_bank_idx!=ULONG_MAX ) ) {
    1564           0 :     fd_sched_block_t * parent_block = block_pool_ele( sched, parent_bank_idx );
    1565           0 :     block->parent_slot = parent_block->slot;
    1566           0 :   }
    1567          12 :   if( FD_UNLIKELY( parent_bank_idx==ULONG_MAX ) ) {
    1568             :     /* Assumes that a NULL parent implies the snapshot slot. */
    1569          12 :     block->parent_slot = ULONG_MAX;
    1570          12 :     block->rooted      = 1;
    1571          12 :     sched->root_idx    = bank_idx;
    1572          12 :   }
    1573          12 : }
    1574             : 
    1575             : void
    1576           0 : fd_sched_advance_root( fd_sched_t * sched, ulong root_idx ) {
    1577           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    1578           0 :   FD_TEST( root_idx<sched->block_cnt_max );
    1579           0 :   FD_TEST( sched->root_idx<sched->block_cnt_max );
    1580           0 :   FD_TEST( ref_q_empty( sched->ref_q ) );
    1581             : 
    1582           0 :   fd_sched_block_t * new_root = block_pool_ele( sched, root_idx );
    1583           0 :   fd_sched_block_t * old_root = block_pool_ele( sched, sched->root_idx );
    1584           0 :   if( FD_UNLIKELY( !old_root->rooted ) ) {
    1585           0 :     FD_LOG_CRIT(( "invariant violation: old_root is not rooted, slot %lu, parent slot %lu",
    1586           0 :                   old_root->slot, old_root->parent_slot ));
    1587           0 :   }
    1588             : 
    1589             :   /* Early exit if the new root is the same as the old root. */
    1590           0 :   if( FD_UNLIKELY( root_idx==sched->root_idx ) ) {
    1591           0 :     FD_LOG_INFO(( "new root is the same as the old root, slot %lu, parent slot %lu",
    1592           0 :                   new_root->slot, new_root->parent_slot ));
    1593           0 :     return;
    1594           0 :   }
    1595             : 
    1596           0 :   subtree_prune( sched, sched->root_idx, root_idx );
    1597             : 
    1598           0 :   new_root->parent_idx = ULONG_MAX;
    1599           0 :   sched->root_idx = root_idx;
    1600           0 : }
    1601             : 
    1602             : void
    1603           0 : fd_sched_root_notify( fd_sched_t * sched, ulong root_idx ) {
    1604           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    1605           0 :   FD_TEST( root_idx<sched->block_cnt_max );
    1606           0 :   FD_TEST( sched->root_idx<sched->block_cnt_max );
    1607           0 :   FD_TEST( ref_q_empty( sched->ref_q ) );
    1608             : 
    1609           0 :   fd_sched_block_t * block    = block_pool_ele( sched, root_idx );
    1610           0 :   fd_sched_block_t * old_root = block_pool_ele( sched, sched->root_idx );
    1611           0 :   if( FD_UNLIKELY( !old_root->rooted ) ) {
    1612           0 :     FD_LOG_CRIT(( "invariant violation: old_root is not rooted, slot %lu, parent slot %lu",
    1613           0 :                   old_root->slot, old_root->parent_slot ));
    1614           0 :   }
    1615             : 
    1616             :   /* Early exit if the new root is the same as the old root. */
    1617           0 :   if( FD_UNLIKELY( root_idx==sched->root_idx ) ) {
    1618           0 :     FD_LOG_INFO(( "new root is the same as the old root, slot %lu, parent slot %lu",
    1619           0 :                   block->slot, block->parent_slot ));
    1620           0 :     return;
    1621           0 :   }
    1622             : 
    1623             :   /* Mark every node from the new root up through its parents to the
    1624             :      old root as being rooted. */
    1625           0 :   fd_sched_block_t * curr = block;
    1626           0 :   fd_sched_block_t * prev = NULL;
    1627           0 :   while( curr ) {
    1628           0 :     if( FD_UNLIKELY( !block_is_done( curr ) ) ) {
    1629           0 :       FD_LOG_CRIT(( "invariant violation: rooting a block that is not done, slot %lu, parent slot %lu",
    1630           0 :                     curr->slot, curr->parent_slot ));
    1631           0 :     }
    1632           0 :     if( FD_UNLIKELY( curr->dying ) ) {
    1633           0 :       FD_LOG_CRIT(( "invariant violation: rooting a block that is dying, slot %lu, parent slot %lu",
    1634           0 :                     curr->slot, curr->parent_slot ));
    1635           0 :     }
    1636           0 :     if( FD_UNLIKELY( curr->staged ) ) {
    1637           0 :       FD_LOG_CRIT(( "invariant violation: rooting a block that is staged, slot %lu, parent slot %lu",
    1638           0 :                     curr->slot, curr->parent_slot ));
    1639           0 :     }
    1640           0 :     if( FD_UNLIKELY( curr->in_rdisp ) ) {
    1641           0 :       FD_LOG_CRIT(( "invariant violation: rooting a block that is in the dispatcher, slot %lu, parent slot %lu",
    1642           0 :                     curr->slot, curr->parent_slot ));
    1643           0 :     }
    1644           0 :     curr->rooted = 1;
    1645           0 :     prev = curr;
    1646           0 :     curr = block_pool_ele( sched, curr->parent_idx );
    1647           0 :   }
    1648             : 
    1649             :   /* If we didn't reach the old root, the new root is not a descendant. */
    1650           0 :   if( FD_UNLIKELY( prev!=old_root ) ) {
    1651           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",
    1652           0 :                   block->slot, block->parent_slot, old_root->slot, old_root->parent_slot ));
    1653           0 :   }
    1654             : 
    1655           0 :   ulong old_active_bank_idx = sched->active_bank_idx;
    1656             : 
    1657             :   /* Now traverse from old root towards new root, and abandon all
    1658             :      minority forks. */
    1659           0 :   curr = old_root;
    1660           0 :   while( curr && curr->rooted && curr!=block ) { /* curr!=block to avoid abandoning good forks. */
    1661           0 :     fd_sched_block_t * rooted_child_block = NULL;
    1662           0 :     ulong              child_idx          = curr->child_idx;
    1663           0 :     while( child_idx!=ULONG_MAX ) {
    1664           0 :       fd_sched_block_t * child = block_pool_ele( sched, child_idx );
    1665           0 :       child_idx = child->sibling_idx;
    1666           0 :       if( child->rooted ) {
    1667           0 :         rooted_child_block = child;
    1668           0 :       } else {
    1669             :         /* This is a minority fork. */
    1670           0 :         ulong abandoned_cnt = sched->metrics->block_abandoned_cnt;
    1671           0 :         subtree_abandon( sched, child );
    1672           0 :         abandoned_cnt = sched->metrics->block_abandoned_cnt-abandoned_cnt;
    1673           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 ) ));
    1674           0 :       }
    1675           0 :     }
    1676           0 :     curr = rooted_child_block;
    1677           0 :   }
    1678             : 
    1679             :   /* If the active block got abandoned, we need to reset it. */
    1680           0 :   if( sched->active_bank_idx==ULONG_MAX ) {
    1681           0 :     sched->metrics->deactivate_pruned_cnt += fd_uint_if( old_active_bank_idx!=ULONG_MAX, 1U, 0U );
    1682           0 :     try_activate_block( sched );
    1683           0 :   }
    1684           0 : }
    1685             : 
    1686             : ulong
    1687          48 : fd_sched_pruned_block_next( fd_sched_t * sched ) {
    1688          48 :   if( !ref_q_empty( sched->ref_q ) ) {
    1689           9 :     ulong bank_idx = ref_q_pop_head( sched->ref_q );
    1690           9 :     return bank_idx;
    1691           9 :   }
    1692          39 :   return ULONG_MAX;
    1693          48 : }
    1694             : 
    1695             : void
    1696          24 : fd_sched_set_poh_params( fd_sched_t * sched, ulong bank_idx, ulong tick_height, ulong max_tick_height, ulong hashes_per_tick, fd_hash_t const * start_poh ) {
    1697          24 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    1698          24 :   FD_TEST( bank_idx<sched->block_cnt_max );
    1699          24 :   FD_TEST( max_tick_height>tick_height );
    1700          24 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    1701          24 :   block->tick_height = tick_height;
    1702          24 :   block->max_tick_height = max_tick_height;
    1703          24 :   block->hashes_per_tick = hashes_per_tick;
    1704             :   #if FD_SCHED_SKIP_POH
    1705             :   /* No-op. */
    1706             :   (void)start_poh;
    1707             :   #else
    1708          24 :   if( FD_LIKELY( block->mblk_cnt ) ) {
    1709             :     /* Fix up the first mblk's curr_hash. */
    1710           9 :     FD_TEST( block->mblk_unhashed_cnt );
    1711           9 :     FD_TEST( !mblk_slist_is_empty( block->mblks_unhashed, sched->mblk_pool ) );
    1712           9 :     FD_TEST( !block->mblk_freed_cnt );
    1713           9 :     fd_sched_mblk_t * first_mblk = sched->mblk_pool + mblk_slist_idx_peek_head( block->mblks_unhashed, sched->mblk_pool );
    1714           9 :     memcpy( first_mblk->curr_hash, start_poh, sizeof(fd_hash_t) );
    1715          15 :   } else {
    1716          15 :     memcpy( block->poh_hash, start_poh, sizeof(fd_hash_t) );
    1717          15 :   }
    1718          24 :   #endif
    1719          24 : }
    1720             : 
    1721             : void
    1722           0 : fd_sched_set_bypass_poh_verify( fd_sched_t * sched, int bypass_poh_verify ) {
    1723           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    1724           0 :   sched->bypass_poh_verify = !!bypass_poh_verify;
    1725           0 : }
    1726             : 
    1727             : void
    1728           0 : fd_sched_set_bypass_alut_resolution( fd_sched_t * sched, int bypass_alut_resolution ) {
    1729           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    1730           0 :   sched->bypass_alut_resolution = !!bypass_alut_resolution;
    1731           0 : }
    1732             : 
    1733             : fd_txn_p_t *
    1734           0 : fd_sched_get_txn( fd_sched_t * sched, ulong txn_idx ) {
    1735           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    1736           0 :   if( FD_UNLIKELY( txn_idx>=sched->depth ) ) {
    1737           0 :     return NULL;
    1738           0 :   }
    1739           0 :   return sched->txn_pool+txn_idx;
    1740           0 : }
    1741             : 
    1742             : fd_sched_txn_info_t *
    1743           0 : fd_sched_get_txn_info( fd_sched_t * sched, ulong txn_idx ) {
    1744           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    1745           0 :   if( FD_UNLIKELY( txn_idx>=sched->depth ) ) {
    1746           0 :     return NULL;
    1747           0 :   }
    1748           0 :   return sched->txn_info_pool+txn_idx;
    1749           0 : }
    1750             : 
    1751             : fd_hash_t *
    1752           0 : fd_sched_get_poh( fd_sched_t * sched, ulong bank_idx ) {
    1753           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    1754           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    1755           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    1756           0 :   FD_TEST( block->fec_eos );
    1757           0 :   FD_TEST( block->mblk_cnt );
    1758           0 :   return block->poh_hash;
    1759           0 : }
    1760             : 
    1761             : uint
    1762           0 : fd_sched_get_shred_cnt( fd_sched_t * sched, ulong bank_idx ) {
    1763           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    1764           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    1765           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    1766           0 :   return block->shred_cnt;
    1767           0 : }
    1768             : 
    1769             : void
    1770           0 : fd_sched_metrics_write( fd_sched_t * sched ) {
    1771           0 :   FD_MGAUGE_SET( REPLAY, SCHED_ACTIVE_BANK_INDEX, sched->active_bank_idx );
    1772           0 :   FD_MGAUGE_SET( REPLAY, SCHED_LAST_DISPATCH_BANK_INDEX, sched->next_ready_last_bank_idx );
    1773           0 :   FD_MGAUGE_SET( REPLAY, SCHED_LAST_DISPATCH_TIMESTAMP_NANOS, fd_ulong_if( sched->next_ready_last_tick!=LONG_MAX, (ulong)sched->next_ready_last_tick, ULONG_MAX ) );
    1774           0 :   FD_MGAUGE_SET( REPLAY, SCHED_STAGING_LANE_OCCUPIED, (ulong)fd_ulong_popcnt( sched->staged_bitset ) );
    1775           0 :   FD_MGAUGE_SET( REPLAY, SCHED_STAGING_LANE_OCCUPIED_WATERMARK, sched->staged_popcnt_wmk );
    1776           0 :   ulong staging_lane_head_bank_idx[ FD_METRICS_ENUM_STAGING_LANE_CNT ];
    1777           0 :   for( ulong lane=0UL; lane<FD_METRICS_ENUM_STAGING_LANE_CNT; lane++ ) {
    1778           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 );
    1779           0 :   }
    1780           0 :   FD_MGAUGE_ENUM_COPY( REPLAY, SCHED_STAGING_LANE_HEAD_BANK_INDEX, staging_lane_head_bank_idx );
    1781           0 :   FD_MGAUGE_SET( REPLAY, SCHED_TXN_POOL_OCCUPIED, sched->depth-sched->txn_pool_free_cnt-1UL );
    1782           0 :   FD_MGAUGE_SET( REPLAY, SCHED_TXN_POOL_SIZE, sched->depth-1UL );
    1783           0 :   FD_MGAUGE_SET( REPLAY, SCHED_MICROBLOCK_POOL_OCCUPIED, sched->depth-sched->mblk_pool_free_cnt );
    1784           0 :   FD_MGAUGE_SET( REPLAY, SCHED_MICROBLOCK_POOL_SIZE, sched->depth );
    1785           0 :   FD_MGAUGE_SET( REPLAY, SCHED_BLOCK_POOL_OCCUPIED, sched->block_pool_popcnt );
    1786           0 :   FD_MGAUGE_SET( REPLAY, SCHED_BLOCK_POOL_SIZE, sched->block_cnt_max );
    1787             : 
    1788           0 :   ulong sched_block_added[ FD_METRICS_ENUM_SCHED_BLOCK_STAGING_CNT ];
    1789           0 :   sched_block_added[ FD_METRICS_ENUM_SCHED_BLOCK_STAGING_V_STAGED_IDX   ] = sched->metrics->block_added_staged_cnt;
    1790           0 :   sched_block_added[ FD_METRICS_ENUM_SCHED_BLOCK_STAGING_V_UNSTAGED_IDX ] = sched->metrics->block_added_unstaged_cnt;
    1791           0 :   FD_MCNT_ENUM_COPY( REPLAY, SCHED_BLOCK_ADDED, sched_block_added );
    1792           0 :   FD_MCNT_SET( REPLAY, SCHED_BLOCK_REPLAYED, sched->metrics->block_removed_cnt );
    1793           0 :   FD_MCNT_SET( REPLAY, SCHED_BLOCK_ABANDONED, sched->metrics->block_abandoned_cnt );
    1794           0 :   FD_MCNT_SET( REPLAY, SCHED_BLOCK_REJECTED, sched->metrics->block_bad_cnt );
    1795           0 :   FD_MCNT_SET( REPLAY, SCHED_BLOCK_PROMOTED, sched->metrics->block_promoted_cnt );
    1796           0 :   FD_MCNT_SET( REPLAY, SCHED_BLOCK_DEMOTED, sched->metrics->block_demoted_cnt );
    1797           0 :   ulong sched_deactivate[ FD_METRICS_ENUM_SCHED_DEACTIVATE_REASON_CNT ];
    1798           0 :   sched_deactivate[ FD_METRICS_ENUM_SCHED_DEACTIVATE_REASON_V_NO_CHILD_IDX  ] = sched->metrics->deactivate_no_child_cnt;
    1799           0 :   sched_deactivate[ FD_METRICS_ENUM_SCHED_DEACTIVATE_REASON_V_NO_WORK_IDX   ] = sched->metrics->deactivate_no_txn_cnt;
    1800           0 :   sched_deactivate[ FD_METRICS_ENUM_SCHED_DEACTIVATE_REASON_V_ABANDONED_IDX ] = sched->metrics->deactivate_abandoned_cnt;
    1801           0 :   sched_deactivate[ FD_METRICS_ENUM_SCHED_DEACTIVATE_REASON_V_MINORITY_IDX  ] = sched->metrics->deactivate_pruned_cnt;
    1802           0 :   FD_MCNT_ENUM_COPY( REPLAY, SCHED_DEACTIVATE, sched_deactivate );
    1803           0 :   FD_MCNT_SET( REPLAY, SCHED_LANE_SWITCHED, sched->metrics->lane_switch_cnt );
    1804           0 :   FD_MCNT_SET( REPLAY, SCHED_LANE_PROMOTED, sched->metrics->lane_promoted_cnt );
    1805           0 :   FD_MCNT_SET( REPLAY, SCHED_LANE_DEMOTED, sched->metrics->lane_demoted_cnt );
    1806           0 :   FD_MCNT_SET( REPLAY, SCHED_FORK_OBSERVED, sched->metrics->fork_observed_cnt );
    1807           0 :   ulong sched_alut[ FD_METRICS_ENUM_SCHED_ALUT_RESULT_CNT ];
    1808           0 :   sched_alut[ FD_METRICS_ENUM_SCHED_ALUT_RESULT_V_SUCCESS_IDX ] = sched->metrics->alut_success_cnt;
    1809           0 :   sched_alut[ FD_METRICS_ENUM_SCHED_ALUT_RESULT_V_FAILED_IDX  ] = sched->metrics->alut_serializing_cnt;
    1810           0 :   FD_MCNT_ENUM_COPY( REPLAY, SCHED_ALUT, sched_alut );
    1811           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_PARSED_ABANDONED, sched->metrics->txn_abandoned_parsed_cnt );
    1812           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_EXECUTED_ABANDONED, sched->metrics->txn_abandoned_exec_done_cnt );
    1813           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_DONE_ABANDONED, sched->metrics->txn_abandoned_done_cnt );
    1814           0 :   FD_MCNT_SET( REPLAY, SCHED_WEIGHTED_IN_FLIGHT, sched->metrics->txn_weighted_in_flight_cnt );
    1815           0 :   FD_MCNT_SET( REPLAY, SCHED_WEIGHTED_IN_FLIGHT_DURATION_NANOS, sched->metrics->txn_weighted_in_flight_tickcount );
    1816           0 :   FD_MCNT_SET( REPLAY, SCHED_NONE_IN_FLIGHT_DURATION_NANOS, sched->metrics->txn_none_in_flight_tickcount );
    1817           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_PARSED, sched->metrics->txn_parsed_cnt );
    1818           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_EXECUTED, sched->metrics->txn_exec_done_cnt );
    1819           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_SIGNATURE_VERIFIED, sched->metrics->txn_sigverify_done_cnt );
    1820           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_POH_MIXED, sched->metrics->txn_mixin_done_cnt );
    1821           0 :   FD_MCNT_SET( REPLAY, SCHED_TXN_DONE, sched->metrics->txn_done_cnt );
    1822           0 :   FD_MCNT_SET( REPLAY, SCHED_MICROBLOCK_PARSED, sched->metrics->mblk_parsed_cnt );
    1823           0 :   FD_MCNT_SET( REPLAY, SCHED_MICROBLOCK_HASHED, sched->metrics->mblk_poh_hashed_cnt );
    1824           0 :   FD_MCNT_SET( REPLAY, SCHED_MICROBLOCK_DONE, sched->metrics->mblk_poh_done_cnt );
    1825           0 :   FD_MCNT_SET( REPLAY, SCHED_BYTES_INGESTED, sched->metrics->bytes_ingested_cnt );
    1826           0 :   FD_MCNT_SET( REPLAY, SCHED_BYTES_INGESTED_PADDING, sched->metrics->bytes_ingested_unparsed_cnt );
    1827           0 :   FD_MCNT_SET( REPLAY, SCHED_BYTES_DROPPED, sched->metrics->bytes_dropped_cnt );
    1828           0 :   FD_MCNT_SET( REPLAY, SCHED_FEC_INGESTED, sched->metrics->fec_cnt );
    1829           0 : }
    1830             : 
    1831             : char *
    1832           9 : fd_sched_get_state_cstr( fd_sched_t * sched ) {
    1833           9 :   sched->print_buf_sz = 0UL;
    1834           9 :   print_metrics( sched );
    1835           9 :   print_sched( sched );
    1836           9 :   return sched->print_buf;
    1837           9 : }
    1838             : 
    1839          12 : void * fd_sched_leave ( fd_sched_t * sched ) { return sched; }
    1840          12 : void * fd_sched_delete( void * mem         ) { return   mem; }
    1841             : 
    1842             : 
    1843             : /* Internal helpers. */
    1844             : 
    1845             : static void
    1846             : add_block( fd_sched_t * sched,
    1847             :            ulong        bank_idx,
    1848          36 :            ulong        parent_bank_idx ) {
    1849          36 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    1850          36 :   FD_TEST( !block->in_sched );
    1851          36 :   sched->block_pool_popcnt++;
    1852             : 
    1853          36 :   block->txn_parsed_cnt              = 0U;
    1854          36 :   block->txn_exec_in_flight_cnt      = 0U;
    1855          36 :   block->txn_exec_done_cnt           = 0U;
    1856          36 :   block->txn_sigverify_in_flight_cnt = 0U;
    1857          36 :   block->txn_sigverify_done_cnt      = 0U;
    1858          36 :   block->poh_hashing_in_flight_cnt   = 0U;
    1859          36 :   block->poh_hashing_done_cnt        = 0U;
    1860          36 :   block->poh_hash_cmp_done_cnt       = 0U;
    1861          36 :   block->txn_done_cnt                = 0U;
    1862          36 :   block->shred_cnt                   = 0U;
    1863          36 :   block->mblk_cnt                    = 0U;
    1864          36 :   block->mblk_freed_cnt              = 0U;
    1865          36 :   block->mblk_tick_cnt               = 0U;
    1866          36 :   block->mblk_unhashed_cnt           = 0U;
    1867          36 :   block->hashcnt                     = 0UL;
    1868          36 :   block->txn_pool_max_popcnt         = sched->depth - sched->txn_pool_free_cnt - 1UL;
    1869          36 :   block->mblk_pool_max_popcnt        = sched->depth - sched->mblk_pool_free_cnt;
    1870          36 :   block->block_pool_max_popcnt       = sched->block_pool_popcnt;
    1871             : 
    1872          36 :   mblk_slist_remove_all( block->mblks_unhashed, sched->mblk_pool );
    1873          36 :   mblk_slist_remove_all( block->mblks_hashing_in_progress, sched->mblk_pool );
    1874          36 :   mblk_slist_remove_all( block->mblks_mixin_in_progress, sched->mblk_pool );
    1875          36 :   block->last_mblk_is_tick            = 0;
    1876          36 :   block->tick_hashcnt_wmk             = 0UL;
    1877          36 :   block->curr_tick_hashcnt            = 0UL;
    1878          36 :   block->tick_height                  = ULONG_MAX;
    1879          36 :   block->max_tick_height              = ULONG_MAX;
    1880          36 :   block->hashes_per_tick              = ULONG_MAX;
    1881          36 :   block->inconsistent_hashes_per_tick = 0;
    1882          36 :   block->zero_hash_tick               = 0;
    1883             : 
    1884          36 :   block->mblks_rem        = 0UL;
    1885          36 :   block->txns_rem         = 0UL;
    1886          36 :   block->fec_buf_sz       = 0U;
    1887          36 :   block->fec_buf_boff     = 0U;
    1888          36 :   block->fec_buf_soff     = 0U;
    1889          36 :   block->fec_eob          = 0;
    1890          36 :   block->fec_sob          = 1;
    1891             : 
    1892          36 :   block->fec_eos              = 0;
    1893          36 :   block->rooted               = 0;
    1894          36 :   block->dying                = 0;
    1895          36 :   block->refcnt               = 1;
    1896          36 :   block->in_sched             = 1;
    1897          36 :   block->in_rdisp             = 0;
    1898          36 :   block->block_start_signaled = 0;
    1899          36 :   block->block_end_signaled   = 0;
    1900          36 :   block->block_start_done     = 0;
    1901          36 :   block->block_end_done       = 0;
    1902          36 :   block->staged               = 0;
    1903             : 
    1904          36 :   block->luf_depth = 0UL;
    1905             : 
    1906             :   /* New leaf node, no child, no sibling. */
    1907          36 :   block->child_idx   = ULONG_MAX;
    1908          36 :   block->sibling_idx = ULONG_MAX;
    1909          36 :   block->parent_idx  = ULONG_MAX;
    1910             : 
    1911          36 :   if( FD_UNLIKELY( parent_bank_idx==ULONG_MAX ) ) {
    1912          12 :     return;
    1913          12 :   }
    1914             : 
    1915             :   /* node->parent link */
    1916          24 :   fd_sched_block_t * parent_block = block_pool_ele( sched, parent_bank_idx );
    1917          24 :   block->parent_idx = parent_bank_idx;
    1918             : 
    1919             :   /* parent->node and sibling->node links */
    1920          24 :   ulong child_idx = bank_idx;
    1921          24 :   if( FD_LIKELY( parent_block->child_idx==ULONG_MAX ) ) { /* Optimize for no forking. */
    1922          12 :     parent_block->child_idx = child_idx;
    1923          12 :   } else {
    1924          12 :     fd_sched_block_t * curr_block = block_pool_ele( sched, parent_block->child_idx );
    1925          30 :     while( curr_block->sibling_idx!=ULONG_MAX ) {
    1926          18 :       curr_block = block_pool_ele( sched, curr_block->sibling_idx );
    1927          18 :     }
    1928          12 :     curr_block->sibling_idx = child_idx;
    1929          12 :     sched->metrics->fork_observed_cnt++;
    1930          12 :   }
    1931             : 
    1932          24 :   if( FD_UNLIKELY( parent_block->dying ) ) {
    1933           0 :     block->dying = 1;
    1934           0 :   }
    1935          24 : }
    1936             : 
    1937             : /* Agave invokes verify_ticks() anywhere between once per slot and once
    1938             :    per entry batch, before tranactions are parsed or dispatched for
    1939             :    execution.  We can't do quite the same thing due to out-of-order
    1940             :    scheduling and the fact that we allow parsing to run well ahead of
    1941             :    block boundaries.  Out-of-order scheduling is good, so is overlapping
    1942             :    parsing with execution.  The easiest thing for us would be to just
    1943             :    delay verify_ticks() wholesale till the end of a slot, except that
    1944             :    this opens us up to bogus tick and hash counts, potentially causing
    1945             :    runaway consumption of compute cycles.  Of all the checks that are
    1946             :    performed in verify_ticks(), two types are relevant to mitigating
    1947             :    this risk.  One is constraining the number of ticks, and the other is
    1948             :    constraining the number of hashes per tick.  So we implement these
    1949             :    checks here, and perform them on the fly as eagerly as possible.
    1950             : 
    1951             :    Returns 0 on success. */
    1952             : static int
    1953           9 : verify_ticks_eager( fd_sched_block_t * block ) {
    1954           9 :   FD_TEST( block->hashes_per_tick!=ULONG_MAX ); /* PoH params initialized. */
    1955             : 
    1956           9 :   if( FD_UNLIKELY( block->mblk_tick_cnt+block->tick_height>block->max_tick_height ) ) {
    1957           3 :     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 ));
    1958           3 :     return -1;
    1959           3 :   }
    1960           6 :   if( FD_UNLIKELY( block->hashes_per_tick>1UL && block->zero_hash_tick ) ) {
    1961           0 :     FD_LOG_INFO(( "bad block: INVALID_TICK_HASH_COUNT, slot %lu, parent slot %lu, has at least one zero hash tick", block->slot, block->parent_slot ));
    1962           0 :     return -1;
    1963           0 :   }
    1964           6 :   if( FD_UNLIKELY( block->hashes_per_tick>1UL && block->mblk_tick_cnt && (block->hashes_per_tick!=block->tick_hashcnt_wmk||block->inconsistent_hashes_per_tick) ) ) {
    1965           3 :     FD_LOG_INFO(( "bad block: INVALID_TICK_HASH_COUNT, slot %lu, parent slot %lu, expected %lu, got %lu", block->slot, block->parent_slot, block->hashes_per_tick, block->tick_hashcnt_wmk ));
    1966           3 :     return -1;
    1967           3 :   }
    1968           3 :   if( FD_UNLIKELY( block->hashes_per_tick>1UL && block->curr_tick_hashcnt>block->hashes_per_tick ) ) { /* >1 to ignore low power hashing or no hashing cases */
    1969             :     /* We couldn't really check this at parse time because we may not
    1970             :        have the expected hashes per tick value yet.  We couldn't delay
    1971             :        this till after all PoH hashing is done, because this would be a
    1972             :        DoS vector.  This can't be merged with the above check, because a
    1973             :        malformed block might not end with a tick.  As in, a block might
    1974             :        end with a non-tick microblock with a high hashcnt.  Note that
    1975             :        checking the hashcnt between ticks transitively places an upper
    1976             :        bound on the hashcnt of individual microblocks, thus mitigating
    1977             :        the DoS vector. */
    1978           0 :     FD_LOG_INFO(( "bad block: INVALID_TICK_HASH_COUNT, slot %lu, parent slot %lu, observed cumulative tick_hashcnt %lu, expected %lu", block->slot, block->parent_slot, block->curr_tick_hashcnt, block->hashes_per_tick ));
    1979           0 :     return -1;
    1980           0 :   }
    1981             : 
    1982           3 :   return 0;
    1983           3 : }
    1984             : 
    1985             : /* https://github.com/anza-xyz/agave/blob/v3.0.6/ledger/src/blockstore_processor.rs#L1057
    1986             : 
    1987             :    The only check we don't do here is TRAILING_ENTRY, which can be done
    1988             :    independently when we parse the final FEC set of a block.
    1989             : 
    1990             :    Returns 0 on success. */
    1991             : static int
    1992           3 : verify_ticks_final( fd_sched_block_t * block ) {
    1993           3 :   FD_TEST( block->fec_eos );
    1994             : 
    1995           3 :   if( FD_UNLIKELY( block->mblk_tick_cnt+block->tick_height<block->max_tick_height ) ) {
    1996           3 :     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 ));
    1997           3 :     return -1;
    1998           3 :   }
    1999             : 
    2000           0 :   return verify_ticks_eager( block );
    2001           3 : }
    2002             : 
    2003             : int
    2004             : fd_sched_block_verify_ticks( fd_sched_t * sched,
    2005             :                              ulong        bank_idx,
    2006             :                              ulong        tick_height,
    2007             :                              ulong        max_tick_height,
    2008           0 :                              ulong        hashes_per_tick ) {
    2009           0 :   FD_TEST( sched->canary==FD_SCHED_MAGIC );
    2010           0 :   FD_TEST( bank_idx<sched->block_cnt_max );
    2011           0 :   FD_TEST( max_tick_height>tick_height );
    2012           0 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    2013             :   /* Set the tick window directly; skip fd_sched_set_poh_params
    2014             :      PoH fixup, unneeded here and unsafe to repeat per FEC set. */
    2015           0 :   block->tick_height     = tick_height;
    2016           0 :   block->max_tick_height = max_tick_height;
    2017           0 :   block->hashes_per_tick = hashes_per_tick;
    2018             :   /* fec_eos => slot fully ingested, so the TOO_FEW check is meaningful
    2019             :      too. */
    2020           0 :   return block->fec_eos ? verify_ticks_final( block ) : verify_ticks_eager( block );
    2021           0 : }
    2022             : 
    2023          39 : #define CHECK( cond )  do {             \
    2024          39 :   if( FD_UNLIKELY( !(cond) ) ) {        \
    2025          15 :     return FD_SCHED_AGAIN_LATER;        \
    2026          15 :   }                                     \
    2027          39 : } while( 0 )
    2028             : 
    2029             : /* CHECK that it is safe to read at least n more bytes. */
    2030          39 : #define CHECK_LEFT( n ) CHECK( (n)<=(block->fec_buf_sz-block->fec_buf_soff) )
    2031             : 
    2032             : /* Consume as much as possible from the buffer.  By the end of this
    2033             :    function, there could be residual data left over in the buffer.  That
    2034             :    residual has to be either a partially received microblock header or a
    2035             :    transaction that straddles a FEC set boundary.  These bytes are kept
    2036             :    and will be concatenated with the next FEC set.  This residual is
    2037             :    bounded.
    2038             : 
    2039             :    Trailing bytes within a batch are dropped immediately.  These
    2040             :    trailing bytes can span arbitrarily many FEC sets. */
    2041             : FD_WARN_UNUSED static int
    2042          24 : fd_sched_parse( fd_sched_t * sched, fd_sched_block_t * block, fd_sched_alut_ctx_t * alut_ctx ) {
    2043          48 :   while( 1 ) {
    2044          48 :     while( block->txns_rem>0UL ) {
    2045           0 :       int err;
    2046           0 :       if( FD_UNLIKELY( (err=fd_sched_parse_txn( sched, block, alut_ctx ))!=FD_SCHED_OK ) ) {
    2047           0 :         return err;
    2048           0 :       }
    2049           0 :     }
    2050          48 :     if( block->txns_rem==0UL && block->mblks_rem>0UL ) {
    2051          15 :       if( FD_UNLIKELY( block->mblk_cnt>=FD_SCHED_MAX_MBLK_PER_SLOT ) ) {
    2052             :         /* A valid block shouldn't contain more than this amount of
    2053             :            microblocks. */
    2054           0 :         FD_LOG_INFO(( "bad block: slot %lu, parent slot %lu, mblk_cnt %u (%u ticks) >= %lu", block->slot, block->parent_slot, block->mblk_cnt, block->mblk_tick_cnt, FD_SCHED_MAX_MBLK_PER_SLOT ));
    2055           0 :         return FD_SCHED_BAD_BLOCK;
    2056           0 :       }
    2057             : 
    2058          15 :       CHECK_LEFT( sizeof(fd_microblock_hdr_t) );
    2059          15 :       fd_microblock_hdr_t * hdr = (fd_microblock_hdr_t *)fd_type_pun( block->fec_buf+block->fec_buf_soff );
    2060          15 :       block->fec_buf_soff      += (uint)sizeof(fd_microblock_hdr_t);
    2061             : 
    2062          15 :       if( FD_UNLIKELY( hdr->txn_cnt>fd_ulong_sat_sub( FD_MAX_TXN_PER_SLOT, block->txn_parsed_cnt ) ) ) {
    2063           0 :         FD_LOG_INFO(( "bad block: illegally many transactions specified in microblock header in 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 ));
    2064           0 :         return FD_SCHED_BAD_BLOCK;
    2065           0 :       }
    2066          15 :       if( FD_UNLIKELY( hdr->hash_cnt>fd_ulong_sat_sub( FD_RUNTIME_MAX_HASHES_PER_TICK, block->curr_tick_hashcnt ) ) ) {
    2067           0 :         FD_LOG_INFO(( "bad block: slot %lu, parent slot %lu, curr_tick_hashcnt %lu, hdr->hash_cnt %lu", block->slot, block->parent_slot, block->curr_tick_hashcnt, hdr->hash_cnt ));
    2068           0 :         return FD_SCHED_BAD_BLOCK;
    2069           0 :       }
    2070             : 
    2071          15 :       block->mblks_rem--;
    2072          15 :       block->txns_rem = hdr->txn_cnt;
    2073             : 
    2074             :       /* One might think that every microblock needs to have at least
    2075             :          one hash, otherwise the block should be considered invalid.  A
    2076             :          vanilla validator certainly produces microblocks that conform
    2077             :          to this.  But a modded validator could in theory produce
    2078             :          zero-hash microblocks.  Agave's replay stage will happily take
    2079             :          those microblocks.  The Agave implementation-defined way of
    2080             :          doing PoH verify is as follows:
    2081             : 
    2082             :          For a tick microblock, do the same number of hashes as
    2083             :          specified by the microblock.  Zero hashes are not allowed.
    2084             : 
    2085             :          For a transaction microblock, if the number of hashes specified
    2086             :          by the microblock is <= 1, then do zero pure hashes, and simply
    2087             :          do a mixin/record.  Otherwise, do (number of hashes-1) amount
    2088             :          of pure hashing, and then do a mixin.  However, note that for
    2089             :          the purposes of tick_verify, the number of hashes specified by
    2090             :          the microblock is taken verbatim.
    2091             : 
    2092             :          An additional constraint is that Agave expects non-tick
    2093             :          microblocks to leave the cumulative tick hash count at least
    2094             :          one away from hashes_per_tick at the end of a batch:
    2095             :          https://github.com/anza-xyz/agave/blob/v4.0.0-rc.0/entry/src/entry.rs#L672
    2096             : 
    2097             :          Observe that this is not a net new constraint.  Since ticks are
    2098             :          not allowed to have zero hashes, and ticks have to end at
    2099             :          exactly hashes_per_tick, this check is redundant.
    2100             : 
    2101             : 
    2102             :          Some additional references to Agave:
    2103             : 
    2104             :          On the consumer side, non-tick microblocks can have a zero hash
    2105             :          count, and the mixin will happen anyways:
    2106             :          https://github.com/anza-xyz/agave/blob/v4.0.0-rc.0/entry/src/entry.rs#L326
    2107             :          https://github.com/anza-xyz/agave/blob/v4.0.0-rc.0/entry/src/entry.rs#L542
    2108             : 
    2109             :          Ticks cannot have a zero hash count:
    2110             :          https://github.com/anza-xyz/agave/blob/v4.1.1/entry/src/entry.rs#L684
    2111             : 
    2112             :          On the producer side, Agave reserves at least one hash for the
    2113             :          tick, so an Agave produced tick would satisfy the verifier:
    2114             :          https://github.com/anza-xyz/agave/blob/v4.0.0-rc.0/entry/src/poh.rs#L78
    2115             :          https://github.com/anza-xyz/agave/blob/v4.0.0-rc.0/entry/src/poh.rs#L101 */
    2116          15 :       block->curr_tick_hashcnt = fd_ulong_sat_add( hdr->hash_cnt, block->curr_tick_hashcnt ); /* For tick_verify, take the number of hashes verbatim. */
    2117          15 :       sched->metrics->mblk_parsed_cnt++;
    2118          15 :       if( FD_UNLIKELY( !hdr->txn_cnt ) ) {
    2119             :         /* This is a tick microblock. */
    2120          15 :         if( FD_UNLIKELY( block->mblk_tick_cnt && block->tick_hashcnt_wmk!=block->curr_tick_hashcnt ) ) {
    2121           3 :           block->inconsistent_hashes_per_tick = 1;
    2122           3 :           if( FD_LIKELY( block->hashes_per_tick!=ULONG_MAX && block->hashes_per_tick>1UL ) ) {
    2123             :             /* >1 to ignore low power hashing or hashing disabled */
    2124           0 :             FD_LOG_INFO(( "bad block: INVALID_TICK_HASH_COUNT, slot %lu, parent slot %lu, tick idx %u, tick_hashcnt_wmk %lu, curr hashcnt %lu, hashes_per_tick %lu", block->slot, block->parent_slot, block->mblk_tick_cnt, block->tick_hashcnt_wmk, block->curr_tick_hashcnt, block->hashes_per_tick ));
    2125           0 :             return FD_SCHED_BAD_BLOCK;
    2126           0 :           }
    2127           3 :         }
    2128          15 :         if( FD_UNLIKELY( !hdr->hash_cnt ) ) {
    2129           0 :           block->zero_hash_tick = 1;
    2130           0 :           if( FD_LIKELY( block->hashes_per_tick!=ULONG_MAX && block->hashes_per_tick>1UL ) ) {
    2131           0 :             FD_LOG_INFO(( "bad block: INVALID_TICK_HASH_COUNT, slot %lu, parent slot %lu, tick idx %u has zero hashes", block->slot, block->parent_slot, block->mblk_tick_cnt ));
    2132           0 :             return FD_SCHED_BAD_BLOCK;
    2133           0 :           }
    2134           0 :         }
    2135          15 :         block->tick_hashcnt_wmk  = fd_ulong_max( block->curr_tick_hashcnt, block->tick_hashcnt_wmk );
    2136          15 :         block->curr_tick_hashcnt = 0UL;
    2137          15 :         block->mblk_tick_cnt++;
    2138          15 :       }
    2139             : 
    2140          15 :       FD_TEST( sched->mblk_pool_free_cnt ); /* can_ingest should have guaranteed sufficient free capacity. */
    2141          15 :       uint mblk_idx = sched->mblk_pool_free_head;
    2142          15 :       sched->mblk_pool_free_head = sched->mblk_pool[ mblk_idx ].next;
    2143          15 :       sched->mblk_pool_free_cnt--;
    2144             : 
    2145          15 :       fd_sched_mblk_t * mblk = sched->mblk_pool+mblk_idx;
    2146          15 :       mblk->start_txn_idx = block->txn_parsed_cnt;
    2147          15 :       mblk->end_txn_idx   = mblk->start_txn_idx+hdr->txn_cnt;
    2148          15 :       mblk->curr_txn_idx  = mblk->start_txn_idx;
    2149          15 :       mblk->hashcnt       = fd_ulong_sat_sub( hdr->hash_cnt, fd_ulong_if( !hdr->txn_cnt, 0UL, 1UL ) ); /* For pure hashing, implement saturating sub for non-tick microblocks. */
    2150          15 :       mblk->curr_hashcnt  = 0UL;
    2151          15 :       mblk->curr_sig_cnt  = 0U;
    2152          15 :       mblk->is_tick       = !hdr->txn_cnt;
    2153          15 :       memcpy( mblk->end_hash, hdr->hash, sizeof(fd_hash_t) );
    2154          15 :       memcpy( mblk->curr_hash, block->poh_hash, sizeof(fd_hash_t) );
    2155             : 
    2156             :       /* Update block tracking. */
    2157          15 :       block->hashcnt += mblk->hashcnt+fd_ulong_if( !hdr->txn_cnt, 0UL, 1UL );
    2158          15 :       memcpy( block->poh_hash, hdr->hash, sizeof(fd_hash_t) );
    2159          15 :       block->last_mblk_is_tick = mblk->is_tick;
    2160          15 :       block->mblk_cnt++;
    2161             : 
    2162             :       #if FD_SCHED_SKIP_POH
    2163             :       block->poh_hashing_done_cnt++;
    2164             :       block->poh_hash_cmp_done_cnt++;
    2165             :       free_mblk( sched, block, mblk_idx );
    2166             :       #else
    2167          15 :       mblk_slist_idx_push_tail( block->mblks_unhashed, mblk_idx, sched->mblk_pool );
    2168          15 :       block->mblk_unhashed_cnt++;
    2169          15 :       #endif
    2170          15 :       continue;
    2171          15 :     }
    2172          33 :     if( block->txns_rem==0UL && block->mblks_rem==0UL && block->fec_sob ) {
    2173          24 :       CHECK_LEFT( sizeof(ulong) );
    2174           9 :       FD_TEST( block->fec_buf_soff==0U );
    2175           9 :       block->mblks_rem     = FD_LOAD( ulong, block->fec_buf );
    2176           9 :       block->fec_buf_soff += (uint)sizeof(ulong);
    2177             : 
    2178           9 :       if( FD_UNLIKELY( block->mblks_rem>fd_ulong_sat_sub( FD_SCHED_MAX_MBLK_PER_SLOT, block->mblk_cnt ) ) ) {
    2179           0 :         FD_LOG_INFO(( "bad block: 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, FD_SCHED_MAX_MBLK_PER_SLOT ));
    2180           0 :         return FD_SCHED_BAD_BLOCK;
    2181           0 :       }
    2182             : 
    2183           9 :       block->fec_sob = 0;
    2184           9 :       if( FD_UNLIKELY( !block->mblks_rem ) ) {
    2185           0 :         FD_LOG_INFO(( "bad block: slot %lu, parent slot %lu, mblk_cnt %u (%u ticks), empty batch detected", block->slot, block->parent_slot, block->mblk_cnt, block->mblk_tick_cnt ));
    2186           0 :         return FD_SCHED_BAD_BLOCK;
    2187           0 :       }
    2188           9 :       continue;
    2189           9 :     }
    2190           9 :     if( block->txns_rem==0UL && block->mblks_rem==0UL ) {
    2191           9 :       break;
    2192           9 :     }
    2193           9 :   }
    2194           9 :   if( !block->fec_sob && block->txns_rem==0UL && block->mblks_rem==0UL ) {
    2195             :     /* All microblocks announced by the current batch header have been
    2196             :        parsed out.  Anything still in the buffer must be trailing bytes.
    2197             :        Drop them now because trailing bytes can span many FEC sets and
    2198             :        accumulating them would overflow the parse buffer.  Any followup
    2199             :        FEC sets within the same batch will simply be discarded
    2200             :        wholesale. */
    2201           9 :     sched->metrics->bytes_ingested_unparsed_cnt += block->fec_buf_sz-block->fec_buf_soff;
    2202           9 :     block->fec_buf_boff += block->fec_buf_sz;
    2203           9 :     block->fec_buf_soff  = 0U;
    2204           9 :     block->fec_buf_sz    = 0U;
    2205           9 :   }
    2206           9 :   if( block->fec_eob ) {
    2207           9 :     block->fec_sob = 1;
    2208           9 :     block->fec_eob = 0;
    2209           9 :   }
    2210           9 :   return FD_SCHED_OK;
    2211          24 : }
    2212             : 
    2213             : FD_WARN_UNUSED static int
    2214           0 : fd_sched_parse_txn( fd_sched_t * sched, fd_sched_block_t * block, fd_sched_alut_ctx_t * alut_ctx ) {
    2215           0 :   fd_txn_t * txn = fd_type_pun( block->txn );
    2216             : 
    2217           0 :   uchar * payload = block->fec_buf+block->fec_buf_soff;
    2218           0 :   ulong pay_sz = 0UL;
    2219           0 :   ulong txn_sz = fd_txn_parse_core( payload,
    2220           0 :                                     fd_ulong_min( FD_TXN_MTU, block->fec_buf_sz-block->fec_buf_soff ),
    2221           0 :                                     txn,
    2222           0 :                                     NULL,
    2223           0 :                                     &pay_sz );
    2224             : 
    2225           0 :   if( FD_UNLIKELY( !pay_sz || !txn_sz ) ) {
    2226             :     /* Can't parse out a full transaction. */
    2227           0 :     return FD_SCHED_AGAIN_LATER;
    2228           0 :   }
    2229             : 
    2230           0 :   if( FD_UNLIKELY( block->txn_parsed_cnt>=FD_MAX_TXN_PER_SLOT ) ) {
    2231             :     /* The block contains more transactions than a valid block would.
    2232             :        Mark the block dead instead of keep processing it. */
    2233           0 :     FD_LOG_INFO(( "bad block: illegally many transactions in slot %lu, parent slot %lu, txn_parsed_cnt %u", block->slot, block->parent_slot, block->txn_parsed_cnt ));
    2234           0 :     return FD_SCHED_BAD_BLOCK;
    2235           0 :   }
    2236             : 
    2237           0 :   ulong imm_cnt = fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_IMM );
    2238           0 :   ulong alt_cnt = fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_ALT );
    2239             : 
    2240             :   /* Try to expand ALUTs. */
    2241           0 :   int serializing = 0;
    2242           0 :   if( alt_cnt>0UL ) {
    2243           0 :     if( FD_UNLIKELY( sched->bypass_alut_resolution ) ) {
    2244             :       /* test/fuzz: no accdb to query, so treat ALUT txns as serializing. */
    2245           0 :       serializing = 1;
    2246           0 :     } else {
    2247             :       /* Copy the slot hashes sysvar out and release the read BEFORE
    2248             :          resolving the ALTs.  fd_runtime_load_txn_address_lookup_tables
    2249             :          issues its own fd_accdb_read_one per lookup table, and the accdb
    2250             :          acquire state is a single non-nestable flag — holding this read
    2251             :          open across that call would trip the IDLE assertion in
    2252             :          fd_accdb_acquire.  The slot_hashes view aliases the record data,
    2253             :          so it must view the copy, not the released record. */
    2254           0 :       static uchar slot_hashes_buf[ FD_SYSVAR_SLOT_HASHES_BINCODE_SZ ];
    2255           0 :       ulong        slot_hashes_sz = 0UL;
    2256           0 :       int          have_slot_hashes = 0;
    2257           0 :       fd_acc_t ro = fd_accdb_read_one( alut_ctx->accdb, alut_ctx->fork_id, fd_sysvar_slot_hashes_id.uc );
    2258           0 :       if( FD_LIKELY( ro.lamports && ro.data_len<=sizeof(slot_hashes_buf) ) ) {
    2259           0 :         fd_memcpy( slot_hashes_buf, ro.data, ro.data_len );
    2260           0 :         slot_hashes_sz   = ro.data_len;
    2261           0 :         have_slot_hashes = 1;
    2262           0 :       }
    2263           0 :       fd_accdb_unread_one( alut_ctx->accdb, &ro );
    2264             : 
    2265           0 :       fd_slot_hashes_t slot_hashes_view[1];
    2266           0 :       if( FD_LIKELY( have_slot_hashes &&
    2267           0 :                      fd_sysvar_slot_hashes_view( slot_hashes_view, slot_hashes_buf, slot_hashes_sz ) ) ) {
    2268           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 );
    2269           0 :         sched->metrics->alut_success_cnt += (uint)!serializing;
    2270           0 :       } else {
    2271           0 :         serializing = 1;
    2272           0 :       }
    2273           0 :     }
    2274           0 :   }
    2275             : 
    2276             :   /* Transactions should not have duplicate accounts.
    2277             :      https://github.com/anza-xyz/agave/blob/v3.1.11/ledger/src/blockstore_processor.rs#L778-L790 */
    2278           0 :   fd_acct_addr_t const * imms = fd_txn_get_acct_addrs( txn, payload );
    2279           0 :   fd_acct_addr_t * alts = (!alt_cnt||serializing) ? NULL : sched->aluts;
    2280           0 :   alt_cnt = alts ? alt_cnt : 0UL;
    2281           0 :   if( FD_UNLIKELY( fd_chkdup_check( sched->chkdup, imms, imm_cnt, alts, alt_cnt ) ) ) {
    2282           0 :     FD_LOG_INFO(( "bad block: duplicate accounts in slot %lu, parent slot %lu, txn_parsed_cnt %u", block->slot, block->parent_slot, block->txn_parsed_cnt ));
    2283           0 :     return FD_SCHED_BAD_BLOCK;
    2284           0 :   }
    2285             : 
    2286           0 :   ulong bank_idx = (ulong)(block-sched->block_pool);
    2287           0 :   ulong txn_idx  = fd_rdisp_add_txn( sched->rdisp, bank_idx, txn, payload, alts, serializing );
    2288           0 :   FD_TEST( txn_idx!=0UL );
    2289           0 :   sched->metrics->txn_parsed_cnt++;
    2290           0 :   sched->metrics->alut_serializing_cnt += (uint)serializing;
    2291           0 :   sched->txn_pool_free_cnt--;
    2292           0 :   fd_txn_p_t * txn_p = sched->txn_pool + txn_idx;
    2293           0 :   txn_p->payload_sz  = pay_sz;
    2294             : 
    2295           0 :   txn_p->start_shred_idx = (ushort)fd_sort_up_uint_split( block->shred_blk_offs, block->shred_cnt, block->fec_buf_boff+block->fec_buf_soff );
    2296           0 :   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 );
    2297           0 :   txn_p->end_shred_idx = (ushort)fd_sort_up_uint_split( block->shred_blk_offs, block->shred_cnt, block->fec_buf_boff+block->fec_buf_soff+(uint)pay_sz );
    2298             : 
    2299           0 :   fd_memcpy( txn_p->payload, payload, pay_sz );
    2300           0 :   fd_memcpy( TXN(txn_p),     txn,     txn_sz );
    2301           0 :   txn_bitset_remove( sched->exec_done_set, txn_idx );
    2302           0 :   txn_bitset_remove( sched->sigverify_done_set, txn_idx );
    2303           0 :   txn_bitset_remove( sched->poh_mixin_done_set, txn_idx );
    2304           0 :   sched->txn_info_pool[ txn_idx ].flags = 0UL;
    2305           0 :   sched->txn_info_pool[ txn_idx ].txn_err = 0;
    2306           0 :   sched->txn_info_pool[ txn_idx ].tick_parsed = fd_tickcount();
    2307           0 :   sched->txn_info_pool[ txn_idx ].tick_sigverify_disp = LONG_MAX;
    2308           0 :   sched->txn_info_pool[ txn_idx ].tick_sigverify_done = LONG_MAX;
    2309           0 :   sched->txn_info_pool[ txn_idx ].tick_exec_disp = LONG_MAX;
    2310           0 :   sched->txn_info_pool[ txn_idx ].tick_exec_done = LONG_MAX;
    2311           0 :   sched->txn_info_pool[ txn_idx ].index_in_slot  = block->txn_parsed_cnt;
    2312           0 :   block->txn_idx[ block->txn_parsed_cnt ] = txn_idx;
    2313           0 :   block->fec_buf_soff += (uint)pay_sz;
    2314           0 :   block->txn_parsed_cnt++;
    2315             : #if FD_SCHED_SKIP_SIGVERIFY
    2316             :   txn_bitset_insert( sched->sigverify_done_set, txn_idx );
    2317             :   block->txn_sigverify_done_cnt++;
    2318             : #endif
    2319             : #if FD_SCHED_SKIP_POH
    2320             :   txn_bitset_insert( sched->poh_mixin_done_set, txn_idx );
    2321             : #endif
    2322           0 :   block->txns_rem--;
    2323           0 :   return FD_SCHED_OK;
    2324           0 : }
    2325             : 
    2326             : #undef CHECK
    2327             : #undef CHECK_LEFT
    2328             : 
    2329             : static void
    2330           0 : dispatch_sigverify( fd_sched_t * sched, fd_sched_block_t * block, ulong bank_idx, int exec_tile_idx, fd_sched_task_t * out ) {
    2331             :   /* Dispatch transactions for sigverify in parse order. */
    2332           0 :   out->task_type = FD_SCHED_TT_TXN_SIGVERIFY;
    2333           0 :   out->txn_sigverify->bank_idx = bank_idx;
    2334           0 :   out->txn_sigverify->txn_idx  = block->txn_idx[ block->txn_sigverify_done_cnt+block->txn_sigverify_in_flight_cnt ];
    2335           0 :   out->txn_sigverify->exec_idx = (ulong)exec_tile_idx;
    2336           0 :   sched->sigverify_ready_bitset[ 0 ] = fd_ulong_clear_bit( sched->sigverify_ready_bitset[ 0 ], exec_tile_idx );
    2337           0 :   sched->tile_to_bank_idx[ exec_tile_idx ] = bank_idx;
    2338           0 :   block->txn_sigverify_in_flight_cnt++;
    2339           0 :   if( FD_UNLIKELY( (~sched->txn_exec_ready_bitset[ 0 ])&(~sched->sigverify_ready_bitset[ 0 ])&(~sched->poh_ready_bitset[ 0 ])&fd_ulong_mask_lsb( (int)sched->exec_cnt ) ) ) FD_LOG_CRIT(( "invariant violation: txn_exec_ready_bitset 0x%lx sigverify_ready_bitset 0x%lx poh_ready_bitset 0x%lx", sched->txn_exec_ready_bitset[ 0 ], sched->sigverify_ready_bitset[ 0 ], sched->poh_ready_bitset[ 0 ] ));
    2340           0 : }
    2341             : 
    2342             : /* Assumes there is a PoH task available for dispatching. */
    2343             : static void
    2344           9 : dispatch_poh( fd_sched_t * sched, fd_sched_block_t * block, ulong bank_idx, int exec_tile_idx, fd_sched_task_t * out ) {
    2345           9 :   fd_sched_mblk_t * mblk = NULL;
    2346           9 :   uint mblk_idx;
    2347           9 :   if( FD_LIKELY( !mblk_slist_is_empty( block->mblks_hashing_in_progress, sched->mblk_pool ) ) ) {
    2348             :     /* There's a PoH task in progress, just continue working on that. */
    2349           0 :     mblk_idx = (uint)mblk_slist_idx_pop_head( block->mblks_hashing_in_progress, sched->mblk_pool );
    2350           0 :     mblk = sched->mblk_pool+mblk_idx;
    2351           9 :   } else {
    2352             :     /* No in progress PoH task, so start a new one. */
    2353           9 :     FD_TEST( block->mblk_unhashed_cnt );
    2354           9 :     mblk_idx = (uint)mblk_slist_idx_pop_head( block->mblks_unhashed, sched->mblk_pool );
    2355           9 :     mblk = sched->mblk_pool+mblk_idx;
    2356           9 :     block->mblk_unhashed_cnt--;
    2357           9 :   }
    2358           9 :   out->task_type = FD_SCHED_TT_POH_HASH;
    2359           9 :   out->poh_hash->bank_idx = bank_idx;
    2360           9 :   out->poh_hash->mblk_idx = mblk_idx;
    2361           9 :   out->poh_hash->exec_idx = (ulong)exec_tile_idx;
    2362           9 :   ulong hashcnt_todo = mblk->hashcnt-mblk->curr_hashcnt;
    2363           9 :   out->poh_hash->hashcnt  = fd_ulong_min( hashcnt_todo, FD_SCHED_MAX_POH_HASHES_PER_TASK );
    2364           9 :   memcpy( out->poh_hash->hash, mblk->curr_hash, sizeof(fd_hash_t) );
    2365           9 :   sched->poh_ready_bitset[ 0 ] = fd_ulong_clear_bit( sched->poh_ready_bitset[ 0 ], exec_tile_idx );
    2366           9 :   sched->tile_to_bank_idx[ exec_tile_idx ] = bank_idx;
    2367           9 :   block->poh_hashing_in_flight_cnt++;
    2368           9 :   if( FD_UNLIKELY( (~sched->txn_exec_ready_bitset[ 0 ])&(~sched->sigverify_ready_bitset[ 0 ])&(~sched->poh_ready_bitset[ 0 ])&fd_ulong_mask_lsb( (int)sched->exec_cnt ) ) ) FD_LOG_CRIT(( "invariant violation: txn_exec_ready_bitset 0x%lx sigverify_ready_bitset 0x%lx poh_ready_bitset 0x%lx", sched->txn_exec_ready_bitset[ 0 ], sched->sigverify_ready_bitset[ 0 ], sched->poh_ready_bitset[ 0 ] ));
    2369           9 : }
    2370             : 
    2371             : /* Does up to one transaction mixin.  Returns 1 if one mixin was done, 2
    2372             :    if that mixin also completed a microblock, 0 if no transaction mixin
    2373             :    was available, -1 if there is a PoH verify error. */
    2374             : FD_WARN_UNUSED static int
    2375          33 : maybe_mixin( fd_sched_t * sched, fd_sched_block_t * block ) {
    2376          33 :   if( FD_UNLIKELY( mblk_slist_is_empty( block->mblks_mixin_in_progress, sched->mblk_pool ) ) ) return 0;
    2377           0 :   FD_TEST( block->poh_hashing_done_cnt-block->poh_hash_cmp_done_cnt>0 );
    2378             : 
    2379             :   /* The microblock we would like to do mixin on is at the head of the
    2380             :      queue.  It may have had some mixin, it may have never had any
    2381             :      mixin.  In the case of the former, we should continue to mixin the
    2382             :      same head microblock until it's done, lest the per-block bmtree
    2383             :      gets clobbered when we start a new one. */
    2384           0 :   ulong mblk_idx = mblk_slist_idx_pop_head( block->mblks_mixin_in_progress, sched->mblk_pool );
    2385           0 :   fd_sched_mblk_t * mblk = sched->mblk_pool+mblk_idx;
    2386             : 
    2387           0 :   if( FD_UNLIKELY( mblk->end_txn_idx>block->txn_parsed_cnt ) ) {
    2388             :     /* A partially parsed microblock is by definition at the end of the
    2389             :        FEC stream.  If such a microblock is in progress, there should be
    2390             :        no other microblock in this block so far that hasn't been
    2391             :        dispatched, because microblocks are dispatched in parse order. */
    2392           0 :     if( FD_UNLIKELY( block->mblk_unhashed_cnt ) ) {
    2393           0 :       sched->print_buf_sz = 0UL;
    2394           0 :       print_all( sched, block );
    2395           0 :       FD_LOG_CRIT(( "invariant violation end_txn_idx %lu: %s", mblk->end_txn_idx, sched->print_buf ));
    2396           0 :     }
    2397             : 
    2398             :     /* If we've decided to start mixin on a partially parsed microblock,
    2399             :        there better be nothing else in-progress.  Otherwise, they might
    2400             :        clobber the per-block bmtree for mixin. */
    2401           0 :     if( FD_UNLIKELY( mblk->curr_txn_idx!=mblk->start_txn_idx && (block->poh_hashing_in_flight_cnt||!mblk_slist_is_empty( block->mblks_hashing_in_progress, sched->mblk_pool )||!mblk_slist_is_empty( block->mblks_mixin_in_progress, sched->mblk_pool )) ) ) {
    2402           0 :       sched->print_buf_sz = 0UL;
    2403           0 :       print_all( sched, block );
    2404           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 ));
    2405           0 :     }
    2406           0 :   }
    2407             : 
    2408             :   /* Very rarely, we've finished hashing, but not all transactions in
    2409             :      the microblock have been parsed out.  This can happen if we haven't
    2410             :      received all the FEC sets for this microblock.  We can't yet fully
    2411             :      mixin the microblock.  So we'll stick it back into the end of the
    2412             :      queue, and try to see if there's a fully parsed microblock.
    2413             :      Unless, there's truly nothing else to mixin.  Then we would start
    2414             :      mixin with the partially parsed microblock.  We do this because the
    2415             :      txn pool is meant to be an OOO scheduling window not tied to
    2416             :      max_live_slots sizing requirements, so there shouldn't be a way for
    2417             :      external input to tie up txn pool entries for longer than
    2418             :      necessary. */
    2419           0 :   if( FD_UNLIKELY( mblk->curr_txn_idx>=block->txn_parsed_cnt || /* Nothing more to mixin for this microblock. */
    2420           0 :                    (mblk->end_txn_idx>block->txn_parsed_cnt &&  /* There is something to mixin, but the microblock isn't fully parsed yet ... */
    2421           0 :                     mblk->curr_txn_idx==mblk->start_txn_idx &&  /* ... and we haven't started mixin on it yet ... */
    2422           0 :                     (block->poh_hashing_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. */
    2423           0 :                      !mblk_slist_is_empty( block->mblks_hashing_in_progress, sched->mblk_pool ) ||
    2424           0 :                      !mblk_slist_is_empty( block->mblks_mixin_in_progress, sched->mblk_pool ))) ) ) {
    2425           0 :     mblk_slist_idx_push_tail( block->mblks_mixin_in_progress, mblk_idx, sched->mblk_pool );
    2426             : 
    2427             :     /* No other microblock in the mixin queue. */
    2428           0 :     if( FD_UNLIKELY( block->poh_hashing_done_cnt-block->poh_hash_cmp_done_cnt==1 ) ) return 0;
    2429             : 
    2430             :     /* At this point, there's at least one more microblock in the mixin
    2431             :        queue we could try.  It's a predecessor (in parse order) that
    2432             :        finished hashing later than the partially parsed microblock at
    2433             :        the head of the mixin queue. */
    2434             : 
    2435             :     /* It should never clobber the bmtree for a microblock that has had some mixin done on it. */
    2436           0 :     if( FD_UNLIKELY( mblk->curr_txn_idx!=mblk->start_txn_idx ) ) {
    2437           0 :       sched->print_buf_sz = 0UL;
    2438           0 :       print_all( sched, block );
    2439           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 ));
    2440           0 :     }
    2441             : 
    2442           0 :     mblk_idx = mblk_slist_idx_pop_head( block->mblks_mixin_in_progress, sched->mblk_pool );
    2443           0 :     mblk = sched->mblk_pool+mblk_idx;
    2444             : 
    2445             :     /* It should be a fresh microblock for mixin. */
    2446           0 :     FD_TEST( mblk->curr_txn_idx==mblk->start_txn_idx );
    2447             :     /* Invariant: at any given point in time, there can be at most one
    2448             :        microblock that hasn't been fully parsed yet, due to the nature
    2449             :        of sequential parsing.  So this microblock has to be fully
    2450             :        parsed. */
    2451           0 :     FD_TEST( mblk->end_txn_idx<=block->txn_parsed_cnt );
    2452           0 :   }
    2453             : 
    2454           0 :   FD_TEST( mblk->curr_txn_idx<mblk->end_txn_idx );
    2455             : 
    2456             :   /* Now mixin. */
    2457           0 :   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. */
    2458             : 
    2459           0 :   ulong txn_gidx = block->txn_idx[ mblk->curr_txn_idx ];
    2460           0 :   fd_txn_p_t * _txn = sched->txn_pool+txn_gidx;
    2461           0 :   fd_txn_t * txn = TXN(_txn);
    2462           0 :   for( ulong j=0; j<txn->signature_cnt; j++ ) {
    2463           0 :     fd_bmtree_node_t node[ 1 ];
    2464           0 :     fd_bmtree_hash_leaf( node, _txn->payload+txn->signature_off+FD_TXN_SIGNATURE_SZ*j, 64UL, 1UL );
    2465           0 :     fd_bmtree_commit_append( block->bmtree, node, 1UL );
    2466           0 :     mblk->curr_sig_cnt++;
    2467           0 :   }
    2468             : 
    2469             :   /* Release the txn_idx. */
    2470           0 :   txn_bitset_insert( sched->poh_mixin_done_set, txn_gidx );
    2471           0 :   sched->metrics->txn_mixin_done_cnt++;
    2472           0 :   if( txn_bitset_test( sched->exec_done_set, txn_gidx ) && txn_bitset_test( sched->sigverify_done_set, txn_gidx ) ) {
    2473           0 :     fd_rdisp_complete_txn( sched->rdisp, txn_gidx, 1 );
    2474           0 :     sched->txn_pool_free_cnt++;
    2475           0 :     block->txn_done_cnt++;
    2476           0 :     sched->metrics->txn_done_cnt++;
    2477           0 :   }
    2478             : 
    2479           0 :   mblk->curr_txn_idx++;
    2480           0 :   int rv = 2;
    2481           0 :   if( FD_LIKELY( mblk->curr_txn_idx==mblk->end_txn_idx ) ) {
    2482             :     /* Ready to compute the final hash for this microblock. */
    2483           0 :     block->poh_hash_cmp_done_cnt++;
    2484           0 :     sched->metrics->mblk_poh_done_cnt++;
    2485           0 :     uchar * root = fd_bmtree_commit_fini( block->bmtree );
    2486           0 :     uchar mixin_buf[ 64 ];
    2487           0 :     fd_memcpy( mixin_buf, mblk->curr_hash, 32UL );
    2488           0 :     fd_memcpy( mixin_buf+32UL, root, 32UL );
    2489           0 :     fd_sha256_hash( mixin_buf, 64UL, mblk->curr_hash );
    2490           0 :     free_mblk( sched, block, (uint)mblk_idx );
    2491             :     /* Bypass PoH verification for fuzzing/testing throughput. */
    2492           0 :     if( FD_UNLIKELY( !sched->bypass_poh_verify && memcmp( mblk->curr_hash, mblk->end_hash, sizeof(fd_hash_t) ) ) ) {
    2493           0 :       FD_BASE58_ENCODE_32_BYTES( mblk->curr_hash->hash, our_str );
    2494           0 :       FD_BASE58_ENCODE_32_BYTES( mblk->end_hash->hash, ref_str );
    2495           0 :       FD_LOG_INFO(( "bad block: poh hash mismatch on mblk %lu, ours %s, claimed %s, hashcnt %lu, txns [%lu,%lu), %u sigs, slot %lu, parent slot %lu", mblk_idx, our_str, ref_str, mblk->hashcnt, mblk->start_txn_idx, mblk->end_txn_idx, mblk->curr_sig_cnt, block->slot, block->parent_slot ));
    2496           0 :       return -1;
    2497           0 :     }
    2498           0 :   } else {
    2499             :     /* There are more transactions to mixin in this microblock. */
    2500           0 :     mblk_slist_idx_push_head( block->mblks_mixin_in_progress, mblk_idx, sched->mblk_pool );
    2501           0 :     rv = 1;
    2502           0 :   }
    2503             : 
    2504           0 :   return rv;
    2505           0 : }
    2506             : 
    2507             : static void
    2508          48 : try_activate_block( fd_sched_t * sched ) {
    2509             :   /* Early return if there's already an active block. */
    2510          48 :   if( FD_LIKELY( sched->active_bank_idx!=ULONG_MAX ) ) return;
    2511             : 
    2512             :   /* See if there are any allocated staging lanes that we can activate
    2513             :      for scheduling ... */
    2514          48 :   ulong staged_bitset = sched->staged_bitset;
    2515         111 :   while( staged_bitset ) {
    2516          84 :     int lane_idx  = fd_ulong_find_lsb( staged_bitset );
    2517          84 :     staged_bitset = fd_ulong_pop_lsb( staged_bitset );
    2518             : 
    2519          84 :     ulong              head_idx     = sched->staged_head_bank_idx[ lane_idx ];
    2520          84 :     fd_sched_block_t * head_block   = block_pool_ele( sched, head_idx );
    2521          84 :     fd_sched_block_t * parent_block = block_pool_ele( sched, head_block->parent_idx );
    2522             :     //FIXME: restore these invariant checks when we have immediate demotion of dying blocks
    2523             :     //Today, dying blocks can remain staged if they have in-flight transactions.
    2524             :     // if( FD_UNLIKELY( parent_block->dying ) ) {
    2525             :     //   /* Invariant: no child of a dying block should be staged. */
    2526             :     //   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",
    2527             :     //                 head_idx, head_block->slot, head_block->parent_slot, lane_idx, parent_block->slot, parent_block->parent_slot ));
    2528             :     // }
    2529             :     // if( FD_UNLIKELY( head_block->dying ) ) {
    2530             :     //   /* Invariant: no dying block should be staged. */
    2531             :     //   FD_LOG_CRIT(( "invariant violation: staged_head_bank_idx %lu, slot %lu, prime %lu on lane %u has head_block->dying set",
    2532             :     //                 head_idx, (ulong)head_block->block_id.slot, (ulong)head_block->block_id.prime, lane_idx ));
    2533             :     // }
    2534          84 :     if( block_is_done( parent_block ) && block_is_activatable( head_block ) ) {
    2535             :       /* ... Yes, on this staging lane the parent block is done.  So we
    2536             :          can activate the staged child. */
    2537          21 :       if( FD_UNLIKELY( head_idx!=sched->last_active_bank_idx ) ) { /* Unlikely because only possible under forking or on slot boundary. */
    2538          21 :         if( FD_UNLIKELY( sched->last_active_bank_idx!=head_block->parent_idx ) ) { /* Forking is rare. */
    2539          21 :           FD_LOG_DEBUG(( "activating block %lu:%lu: lane switch to %d", head_block->slot, head_idx, lane_idx ));
    2540          21 :           sched->metrics->lane_switch_cnt++;
    2541          21 :         } else {
    2542           0 :           FD_LOG_DEBUG(( "activating block %lu:%lu: lane %d waking up on slot boundary", head_block->slot, head_idx, lane_idx ));
    2543           0 :         }
    2544          21 :       }
    2545          21 :       sched->active_bank_idx = head_idx;
    2546          21 :       return;
    2547          21 :     }
    2548          84 :   }
    2549             : 
    2550             :   /* ... No, promote unstaged blocks. */
    2551          27 :   ulong root_idx = sched->root_idx;
    2552          27 :   if( FD_UNLIKELY( root_idx==ULONG_MAX ) ) {
    2553           0 :     FD_LOG_CRIT(( "invariant violation: root_idx==ULONG_MAX indicating fd_sched is uninitialized" ));
    2554           0 :   }
    2555             :   /* Find and stage the longest stageable unstaged fork.  This is a
    2556             :      policy decision. */
    2557          27 :   ulong depth = compute_longest_unstaged_fork( sched, root_idx );
    2558          27 :   if( FD_LIKELY( depth>0UL ) ) {
    2559           3 :     if( FD_UNLIKELY( sched->staged_bitset==fd_ulong_mask_lsb( FD_SCHED_MAX_STAGING_LANES ) ) ) {
    2560             :       /* No more staging lanes available.  All of them are occupied by
    2561             :          slow squatters.  Only empty blocks can be demoted, and so
    2562             :          blocks with in-flight transactions, including dying in-flight
    2563             :          blocks, shouldn't be demoted.  We demote all demotable lanes.
    2564             :          Demotion isn't all that expensive, since demotable blocks have
    2565             :          no transactions in them.  If a demoted block proves to be
    2566             :          active still, it'll naturally promote back into a staging lane.
    2567             : 
    2568             :          In fact, all lanes should be demotable at this point.  None of
    2569             :          the lanes have anything dispatchable, otherwise we would have
    2570             :          simply activated one of the dispatchable lanes.  None of the
    2571             :          lanes have anything in-flight either, as we allow for a grace
    2572             :          period while something is in-flight, before we deactivate any
    2573             :          block.  In principle, we could get rid of the grace period and
    2574             :          deactivate right away.  In that case, it's okay if nothing is
    2575             :          demotable at the moment, as that simply implies that all lanes
    2576             :          have in-flight tasks.  We would get another chance to try to
    2577             :          demote when the last in-flight task on any lane completes.
    2578             : 
    2579             :          Another interesting side effect of the current dispatching and
    2580             :          lane switching policy is that each lane should have exactly one
    2581             :          block in it at this point.  A parent block by definition can't
    2582             :          be partially ingested.  Any parent block that is fully ingested
    2583             :          and dispatchable would have made the lane dispatchable, and we
    2584             :          wouldn't be here.  Any parent that is fully ingested and fully
    2585             :          dispatched would be fully done after the grace period.  So
    2586             :          there could only be one block per lane, and it is
    2587             :          simultaneously the head and the tail of the lane.
    2588             : 
    2589             :          A note on why this whole thing does not deadlock:
    2590             : 
    2591             :          One might reasonably wonder what happens if all the lanes are
    2592             :          non-empty, non-dead, but for some reason couldn't be activated
    2593             :          for dispatching.  We would deadlock in this case, as no lane
    2594             :          dispatches to the point of being demotable, and no unstaged
    2595             :          block can be promoted.  Such is not in fact possible.  The only
    2596             :          way a dispatchable lane can be ineligible for activation is if
    2597             :          it has a parent block that isn't done yet.  So a deadlock
    2598             :          happens when this parent block, or any of its dispatchable
    2599             :          ancestors, is unstaged.  An important invariant we maintain is
    2600             :          that a staged block can't have an unstaged stageable parent.
    2601             :          This invariant, by induction, gives us the guarantee that at
    2602             :          least one of the lanes can be activated. */
    2603          15 :       for( int l=0; l<(int)FD_SCHED_MAX_STAGING_LANES; l++ ) {
    2604             :         /* We would be able to assert that all lanes are demotable,
    2605             :            except that abandoned blocks are given no grace period for
    2606             :            deactivation.  So there could be lanes transiently occupied
    2607             :            by dying blocks that are neither demotable (due to in-flight
    2608             :            tasks) nor activatable (due to being dying).  If
    2609             :            rdisp_demote() supported non-empty blocks, then we could
    2610             :            probably restore the assertion. */
    2611          12 :         if( FD_UNLIKELY( !lane_is_demotable( sched, l ) ) ) continue;
    2612          12 :         ulong demoted_cnt = demote_lane( sched, l );
    2613          12 :         if( FD_UNLIKELY( demoted_cnt!=1UL ) ) {
    2614           0 :           FD_LOG_CRIT(( "invariant violation: %lu blocks demoted from lane %d, expected 1 demotion", demoted_cnt, l ));
    2615           0 :         }
    2616          12 :         sched->metrics->lane_demoted_cnt++;
    2617          12 :       }
    2618             :       /* We weren't able to successfully demote anything.  This is
    2619             :          likely because all lanes are occupied by dying blocks with
    2620             :          in-flight tasks.  We would get another chance to try to demote
    2621             :          when the last in-flight task on any lane completes. */
    2622           3 :       if( FD_UNLIKELY( sched->staged_bitset==fd_ulong_mask_lsb( FD_SCHED_MAX_STAGING_LANES ) ) ) return;
    2623           3 :     }
    2624           3 :     FD_TEST( sched->staged_bitset!=fd_ulong_mask_lsb( FD_SCHED_MAX_STAGING_LANES ) );
    2625           3 :     int lane_idx = fd_ulong_find_lsb( ~sched->staged_bitset );
    2626           3 :     if( FD_UNLIKELY( lane_idx>=(int)FD_SCHED_MAX_STAGING_LANES ) ) {
    2627           0 :       FD_LOG_CRIT(( "invariant violation: lane_idx %d, sched->staged_bitset %lx",
    2628           0 :                     lane_idx, sched->staged_bitset ));
    2629           0 :     }
    2630           3 :     ulong head_bank_idx = stage_longest_unstaged_fork( sched, root_idx, lane_idx );
    2631           3 :     if( FD_UNLIKELY( head_bank_idx==ULONG_MAX ) ) {
    2632             :       /* We found a promotable fork depth>0.  This should not happen. */
    2633           0 :       FD_LOG_CRIT(( "invariant violation: head_bank_idx==ULONG_MAX" ));
    2634           0 :     }
    2635             :     /* We don't bother with promotion unless the block is immediately
    2636             :        dispatchable.  So it's okay to set the active block here.  This
    2637             :        doesn't cause out-of-order block replay because any parent block
    2638             :        must be fully done.  If the parent block were dead, this fork
    2639             :        would be marked dead too and ineligible for promotion.  If the
    2640             :        parent block were not dead and not done and staged, we wouldn't
    2641             :        be trying to promote an unstaged fork.  If the parent block were
    2642             :        not dead and not done and unstaged, it would've been part of this
    2643             :        unstaged fork. */
    2644           3 :     fd_sched_block_t * head_block = block_pool_ele( sched, head_bank_idx );
    2645           3 :     FD_LOG_DEBUG(( "activating block %lu:%lu: unstaged promotion to lane %d", head_block->slot, head_bank_idx, lane_idx ));
    2646           3 :     sched->active_bank_idx = head_bank_idx;
    2647           3 :     return;
    2648           3 :   }
    2649             :   /* No unstaged blocks to promote.  So we're done.  Yay. */
    2650          27 : }
    2651             : 
    2652             : static void
    2653          33 : check_or_set_active_block( fd_sched_t * sched ) {
    2654          33 :   if( FD_UNLIKELY( sched->active_bank_idx==ULONG_MAX ) ) {
    2655          33 :     try_activate_block( sched );
    2656          33 :   } else {
    2657           0 :     fd_sched_block_t * active_block = block_pool_ele( sched, sched->active_bank_idx );
    2658           0 :     if( FD_UNLIKELY( block_should_deactivate( active_block ) ) ) {
    2659           0 :       sched->print_buf_sz = 0UL;
    2660           0 :       print_all( sched, active_block );
    2661           0 :       FD_LOG_NOTICE(( "%s", sched->print_buf ));
    2662           0 :       FD_LOG_CRIT(( "invariant violation: should have been deactivated" ));
    2663           0 :     }
    2664           0 :   }
    2665          33 : }
    2666             : 
    2667             : /* This function has two main jobs:
    2668             :    - Mark everything on the fork tree dying.
    2669             :    - Take blocks out of rdisp if possible. */
    2670             : static void
    2671           9 : subtree_mark_and_maybe_prune_rdisp( fd_sched_t * sched, fd_sched_block_t * block ) {
    2672           9 :   if( FD_UNLIKELY( block->rooted ) ) {
    2673           0 :     FD_LOG_CRIT(( "invariant violation: rooted block should not be abandoned, slot %lu, parent slot %lu",
    2674           0 :                   block->slot, block->parent_slot ));
    2675           0 :   }
    2676             :   /* All minority fork nodes pass through this function eventually.  So
    2677             :      this is a good point to check per-node invariants for minority
    2678             :      forks. */
    2679           9 :   if( FD_UNLIKELY( block->staged && !block->in_rdisp ) ) {
    2680           0 :     FD_LOG_CRIT(( "invariant violation: staged block is not in the dispatcher, slot %lu, parent slot %lu",
    2681           0 :                   block->slot, block->parent_slot ));
    2682           0 :   }
    2683             : 
    2684             :   /* Setting the flag is non-optional and can happen more than once. */
    2685           9 :   block->dying = 1;
    2686             : 
    2687             :   /* Removal from dispatcher should only happen once. */
    2688           9 :   if( block->in_rdisp ) {
    2689           9 :     fd_sched_block_t * parent = block_pool_ele( sched, block->parent_idx );
    2690           9 :     if( FD_UNLIKELY( !parent ) ) {
    2691             :       /* Only the root has no parent.  Abandon should never be called on
    2692             :          the root.  So any block we are trying to abandon should have a
    2693             :          parent. */
    2694           0 :       FD_LOG_CRIT(( "invariant violation: parent not found slot %lu, parent slot %lu",
    2695           0 :                     block->slot, block->parent_slot ));
    2696           0 :     }
    2697             : 
    2698             :     /* The dispatcher expects blocks to be abandoned in the same order
    2699             :        that they were added on each lane.  There are no requirements on
    2700             :        the order of abandoning if two blocks are not on the same lane,
    2701             :        or if a block is unstaged.  This means that in general we
    2702             :        shouldn't abandon a child block if the parent hasn't been
    2703             :        abandoned yet, if and only if they are on the same lane.  So wait
    2704             :        until we can abandon the parent, and then descend down the fork
    2705             :        tree to ensure orderly abandoning. */
    2706           9 :     int in_order = !parent->in_rdisp || /* parent is not in the dispatcher */
    2707           9 :                    !parent->staged   || /* parent is in the dispatcher but not staged */
    2708           9 :                    !block->staged    || /* parent is in the dispatcher and staged but this block is unstaged */
    2709           9 :                    block->staging_lane!=parent->staging_lane; /* this block is on a different staging lane than its parent */
    2710             : 
    2711           9 :     if( FD_UNLIKELY( in_order && block->staged && sched->active_bank_idx==sched->staged_head_bank_idx[ block->staging_lane ] && sched->active_bank_idx!=ULONG_MAX ) ) {
    2712           9 :       FD_TEST( block_pool_ele( sched, sched->active_bank_idx )==block );
    2713           9 :       FD_LOG_DEBUG(( "reset active_bank_idx %lu: abandon", sched->active_bank_idx ));
    2714           9 :       sched->last_active_bank_idx = sched->active_bank_idx;
    2715           9 :       sched->active_bank_idx = ULONG_MAX;
    2716           9 :       sched->metrics->deactivate_abandoned_cnt++;
    2717           9 :     }
    2718             : 
    2719             :     /* We inform the dispatcher of an abandon only when there are no
    2720             :        more in-flight transactions.  Otherwise, if the dispatcher
    2721             :        recycles the same txn_id that was just abandoned, and we receive
    2722             :        completion of an in-flight transaction whose txn_id was just
    2723             :        recycled. */
    2724             :     // FIXME The recycling might be fine now that we no longer use
    2725             :     // txn_id to index into anything.  We might be able to just drop
    2726             :     // txn_id on abandoned blocks.  Though would this leak transaction
    2727             :     // content if the txn_id is recycled?
    2728             :     // Note that subtree pruning from sched isn't dependent on the
    2729             :     // in-flight check being present here, as is_prunable already checks
    2730             :     // for in-flight==0.
    2731           9 :     int abandon = in_order && !block_is_in_flight( block );
    2732             : 
    2733           9 :     if( abandon ) {
    2734           9 :       block->in_rdisp = 0;
    2735           9 :       fd_rdisp_abandon_block( sched->rdisp, (ulong)(block-sched->block_pool) );
    2736           9 :       sched->txn_pool_free_cnt += block->txn_parsed_cnt-block->txn_done_cnt; /* in_flight_cnt==0 */
    2737             : 
    2738           9 :       sched->metrics->block_abandoned_cnt++;
    2739           9 :       sched->metrics->txn_abandoned_parsed_cnt    += block->txn_parsed_cnt;
    2740           9 :       sched->metrics->txn_abandoned_exec_done_cnt += block->txn_exec_done_cnt;
    2741           9 :       sched->metrics->txn_abandoned_done_cnt      += block->txn_done_cnt;
    2742             : 
    2743             :       //FIXME when demote supports non-empty blocks, we should demote
    2744             :       //the block from the lane unconditionally and immediately,
    2745             :       //regardles of whether it's safe to abandon or not.  So a block
    2746             :       //would go immediately from staged to unstaged and eventually to
    2747             :       //abandoned.
    2748           9 :       if( FD_LIKELY( block->staged ) ) {
    2749           9 :         FD_LOG_DEBUG(( "block %lu:%lu exited lane %lu: abandon", block->slot, block_to_idx( sched, block ), block->staging_lane ));
    2750           9 :         block->staged = 0;
    2751             :         /* Now release the staging lane.  This will release the lane as
    2752             :            soon as we abandon the head block on a lane.  Technically a
    2753             :            release should only happen when we remove the tail block on a
    2754             :            lane.  This is fine though.  The way we abandon guarantees by
    2755             :            induction that an entire lane will be abandoned.  Only the
    2756             :            head block on a lane can possibly have in-flight
    2757             :            transactions, and so once a head block becomes eligible for
    2758             :            abandoning, the entire lane all the way to the tail block,
    2759             :            will be eligible. */
    2760           9 :         sched->staged_bitset = fd_ulong_clear_bit( sched->staged_bitset, (int)block->staging_lane );
    2761           9 :         sched->staged_head_bank_idx[ block->staging_lane ] = ULONG_MAX;
    2762           9 :       }
    2763           9 :     }
    2764           9 :   }
    2765             : 
    2766             :   /* Abandon the entire fork chaining off of this block. */
    2767           9 :   ulong child_idx = block->child_idx;
    2768           9 :   while( child_idx!=ULONG_MAX ) {
    2769           0 :     fd_sched_block_t * child = block_pool_ele( sched, child_idx );
    2770           0 :     subtree_mark_and_maybe_prune_rdisp( sched, child );
    2771           0 :     child_idx = child->sibling_idx;
    2772           0 :   }
    2773           9 : }
    2774             : 
    2775             : /* This function tells sched that the subtree is no longer worth
    2776             :    replaying.  It can happen as a result of one of the following.
    2777             :      - Bad block at head of lane.
    2778             :      - Bad block at tail of lane.
    2779             :      - Root notify.
    2780             : 
    2781             :    It's safe to call this function more than once on the same block. */
    2782             : static void
    2783           9 : subtree_abandon( fd_sched_t * sched, fd_sched_block_t * block ) {
    2784           9 :   subtree_mark_and_maybe_prune_rdisp( sched, block );
    2785             :   /* We do not gate refcnt releases on the entire subtree being
    2786             :      prunable.  In the root notify case there can be in-flight tasks in
    2787             :      the middle of a subtree, and gating on whole-subtree prunability
    2788             :      would defer the release of an otherwise prunable block, e.g. an
    2789             :      inactive sibling of an in-flight block, to a later abandon such as
    2790             :      a subsequent root notify.  That could stall bank reclamation.
    2791             : 
    2792             :      The sched fork tree still stays in sync with the banks tree.  This
    2793             :      is to prevent attacks targeting lifetime discrepancy.  So while we
    2794             :      know that the subtree can be pruned from sched at this point, we
    2795             :      only release refcnts here and stop short of actually pruning, which
    2796             :      remains solely initiated by banks. */
    2797           9 :   subtree_release_refcnt( sched, block );
    2798           9 : }
    2799             : 
    2800             : /* Release the bank refcnt for every block in the subtree that sched is
    2801             :    done with.  This is evaluated per block.  A block's refcnt is
    2802             :    released as soon as that block individually qualifies, independent of
    2803             :    whether its siblings or descendants still have in-flight tasks.  This
    2804             :    is safe because the actual pruning is initiated separately by banks,
    2805             :    and gated on the whole subtree's refcnts reaching zero.  Releasing
    2806             :    refcnts eagerly per block get us there sooner.  Blocks that are still
    2807             :    in-flight will be released when their last task drains.  This
    2808             :    function is idempotent. */
    2809             : static void
    2810           9 : subtree_release_refcnt( fd_sched_t * sched, fd_sched_block_t * block ) {
    2811           9 :   FD_TEST( block->in_sched );
    2812           9 :   if( block->refcnt && block_is_prunable( block ) ) {
    2813           9 :     FD_TEST( block->dying ); /* The happy path releases the refcnt on full replay.  Only bad blocks end up here. */
    2814           9 :     FD_TEST( !block->block_end_done ); /* Implied by an outstanding refcnt.  The happy path releases the refcnt on full replay. */
    2815           9 :     block->refcnt = 0;
    2816           9 :     FD_TEST( ref_q_avail( sched->ref_q ) );
    2817           9 :     ref_q_push_tail( sched->ref_q, block_to_idx( sched, block ) );
    2818           9 :     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 ) ));
    2819           0 :     else FD_LOG_DEBUG(( "block %lu:%lu replayed nothing, releasing refcnt without any replay", block->slot, block_to_idx( sched, block ) ));
    2820           9 :   }
    2821             : 
    2822           9 :   ulong child_idx = block->child_idx;
    2823           9 :   while( child_idx!=ULONG_MAX ) {
    2824           0 :     fd_sched_block_t * child_block = block_pool_ele( sched, child_idx );
    2825           0 :     subtree_release_refcnt( sched, child_block );
    2826           0 :     child_idx = child_block->sibling_idx;
    2827           0 :   }
    2828           9 : }
    2829             : 
    2830             : static void
    2831           0 : subtree_prune( fd_sched_t * sched, ulong bank_idx, ulong except_idx ) {
    2832           0 :   fd_sched_block_t * head = block_pool_ele( sched, bank_idx );
    2833           0 :   head->parent_idx        = ULONG_MAX;
    2834           0 :   fd_sched_block_t * tail = head;
    2835             : 
    2836           0 :   while( head ) {
    2837           0 :     FD_TEST( !head->refcnt );
    2838           0 :     FD_TEST( head->in_sched );
    2839           0 :     head->in_sched = 0;
    2840             : 
    2841           0 :     ulong child_idx = head->child_idx;
    2842           0 :     while( child_idx!=ULONG_MAX ) {
    2843           0 :       fd_sched_block_t * child = block_pool_ele( sched, child_idx );
    2844             :       /* Add children to be visited.  We abuse the parent_idx field to
    2845             :          link up the next block to visit. */
    2846           0 :       if( child_idx!=except_idx ) {
    2847           0 :         tail->parent_idx = child_idx;
    2848           0 :         tail             = child;
    2849           0 :         tail->parent_idx = ULONG_MAX;
    2850           0 :       }
    2851           0 :       child_idx = child->sibling_idx;
    2852           0 :     }
    2853             : 
    2854             :     /* Prune the current block.  We will never publish halfway into a
    2855             :        staging lane, because anything that we are publishing away should
    2856             :        be out of the dispatcher at this point, much less staged.
    2857             :          - Anything on the rooted fork should have finished replaying
    2858             :            gracefully and be out of the dispatcher.
    2859             :          - Anything on minority forks should have been marked dying and
    2860             :            be taken out of the dispatcher.
    2861             : 
    2862             :        There should be no more in-flight tasks either.  Prunes are
    2863             :        initiated by banks, and the refcnt on the bank won't drop to zero
    2864             :        unless all in-flight tasks have been drained.  So the fact that
    2865             :        we're pruning implies that the bank thinks there's nothing more
    2866             :        in-flight. */
    2867           0 :     if( FD_UNLIKELY( block_is_in_flight( head ) ) ) {
    2868           0 :       FD_LOG_CRIT(( "invariant violation: block has tasks in flight (%u exec %u sigverify %u poh), slot %lu, parent slot %lu",
    2869           0 :                     head->txn_exec_in_flight_cnt, head->txn_sigverify_in_flight_cnt, head->poh_hashing_in_flight_cnt, head->slot, head->parent_slot ));
    2870           0 :     }
    2871           0 :     if( FD_UNLIKELY( head->in_rdisp ) ) {
    2872             :       /* We should have removed it from the dispatcher when we were
    2873             :          notified of the new root, or when in-flight transactions were
    2874             :          drained. */
    2875           0 :       FD_LOG_CRIT(( "invariant violation: block is in the dispatcher, slot %lu, parent slot %lu", head->slot, head->parent_slot ));
    2876           0 :     }
    2877             : 
    2878             :     /* Return remaining mblk descriptors to the shared pool. */
    2879           0 :     free_mblk_slist( sched, head, head->mblks_unhashed );
    2880           0 :     free_mblk_slist( sched, head, head->mblks_hashing_in_progress );
    2881           0 :     free_mblk_slist( sched, head, head->mblks_mixin_in_progress );
    2882             : 
    2883           0 :     if( FD_UNLIKELY( !head->block_end_done ) ) {
    2884           0 :       sched->print_buf_sz = 0UL;
    2885           0 :       print_block_metrics( sched, head );
    2886           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 ));
    2887           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 ));
    2888           0 :     }
    2889             : 
    2890           0 :     sched->block_pool_popcnt--;
    2891             : 
    2892           0 :     fd_sched_block_t * next = block_pool_ele( sched, head->parent_idx );
    2893             : 
    2894             :     /* We don't have to clear the indices here since no one should be
    2895             :        accessing them.  Defensive programming. */
    2896           0 :     head->parent_idx  = ULONG_MAX;
    2897           0 :     head->child_idx   = ULONG_MAX;
    2898           0 :     head->sibling_idx = ULONG_MAX;
    2899             : 
    2900           0 :     head = next;
    2901           0 :   }
    2902           0 : }
    2903             : 
    2904             : static void
    2905          27 : maybe_switch_block( fd_sched_t * sched, ulong bank_idx ) {
    2906             :   /* This only happens rarely when there are dying in-flight blocks.
    2907             :      Early exit and don't let dying blocks affect replay. */
    2908          27 :   if( FD_UNLIKELY( bank_idx!=sched->active_bank_idx ) ) return;
    2909             : 
    2910          27 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    2911          27 :   if( FD_UNLIKELY( block_is_done( block ) ) ) {
    2912           0 :     fd_rdisp_remove_block( sched->rdisp, bank_idx );
    2913           0 :     FD_LOG_DEBUG(( "block %lu:%lu exited lane %lu: remove", block->slot, bank_idx, block->staging_lane ));
    2914           0 :     block->in_rdisp = 0;
    2915           0 :     block->staged   = 0;
    2916           0 :     sched->metrics->block_removed_cnt++;
    2917           0 :     FD_LOG_DEBUG(( "reset active_bank_idx %lu: remove", sched->active_bank_idx ));
    2918           0 :     sched->last_active_bank_idx = sched->active_bank_idx;
    2919           0 :     sched->active_bank_idx = ULONG_MAX;
    2920             : 
    2921             :     /* See if there is a child block down the same staging lane.  This
    2922             :        is a policy decision to minimize fork churn.  We could in theory
    2923             :        reevaluate staging lane allocation here and do promotion/demotion
    2924             :        as needed. */
    2925           0 :     ulong child_idx = block->child_idx;
    2926           0 :     while( child_idx!=ULONG_MAX ) {
    2927           0 :       fd_sched_block_t * child = block_pool_ele( sched, child_idx );
    2928           0 :       if( FD_LIKELY( child->staged && child->staging_lane==block->staging_lane ) ) {
    2929             :         /* There is a child block down the same staging lane ... */
    2930           0 :         if( FD_LIKELY( !child->dying ) ) {
    2931             :           /* ... and the child isn't dead */
    2932           0 :           if( FD_UNLIKELY( !block_is_activatable( child ) ) ) {
    2933             :             /* ... but the child is not activatable, likely because
    2934             :                there are no transactions available yet. */
    2935           0 :             sched->metrics->deactivate_no_txn_cnt++;
    2936           0 :             try_activate_block( sched );
    2937           0 :             return;
    2938           0 :           }
    2939             :           /* ... and it's immediately dispatchable, so switch the active
    2940             :              block to it, and have the child inherit the head status of
    2941             :              the lane.  This is the common case. */
    2942           0 :           FD_LOG_DEBUG(( "activating block %lu:%lu: child inheritance on lane %lu", child->slot, child_idx, child->staging_lane ));
    2943           0 :           sched->active_bank_idx = child_idx;
    2944           0 :           sched->staged_head_bank_idx[ block->staging_lane ] = child_idx;
    2945           0 :           if( FD_UNLIKELY( !fd_ulong_extract_bit( sched->staged_bitset, (int)block->staging_lane ) ) ) {
    2946           0 :             FD_LOG_CRIT(( "invariant violation: staged_bitset 0x%lx bit %lu is not set, slot %lu, parent slot %lu, child slot %lu, parent slot %lu",
    2947           0 :                           sched->staged_bitset, block->staging_lane, block->slot, block->parent_slot, child->slot, child->parent_slot ));
    2948           0 :           }
    2949           0 :           return;
    2950           0 :         } else {
    2951             :           /* ... but the child block is considered dead, likely because
    2952             :              the parser considers it invalid. */
    2953           0 :           FD_LOG_INFO(( "child block %lu is already dead", child->slot ));
    2954           0 :           subtree_abandon( sched, child );
    2955           0 :           break;
    2956           0 :         }
    2957           0 :       }
    2958           0 :       child_idx = child->sibling_idx;
    2959           0 :     }
    2960             :     /* There isn't a child block down the same staging lane.  This is
    2961             :        the last block in the staging lane.  Release the staging lane. */
    2962           0 :     sched->staged_bitset = fd_ulong_clear_bit( sched->staged_bitset, (int)block->staging_lane );
    2963           0 :     sched->staged_head_bank_idx[ block->staging_lane ] = ULONG_MAX;
    2964           0 :     sched->metrics->deactivate_no_child_cnt++;
    2965           0 :     try_activate_block( sched );
    2966          27 :   } else if( block_should_deactivate( block ) ) {
    2967             :     /* We exhausted the active block, but it's not fully done yet.  We
    2968             :        are just not getting FEC sets for it fast enough.  This could
    2969             :        happen when the network path is congested, or when the leader
    2970             :        simply went down.  Reset the active block. */
    2971          15 :     sched->last_active_bank_idx = sched->active_bank_idx;
    2972          15 :     sched->active_bank_idx = ULONG_MAX;
    2973          15 :     sched->metrics->deactivate_no_txn_cnt++;
    2974          15 :     try_activate_block( sched );
    2975          15 :   }
    2976          27 : }
    2977             : 
    2978             : FD_FN_UNUSED static ulong
    2979           0 : find_and_stage_longest_unstaged_fork( fd_sched_t * sched, int lane_idx ) {
    2980           0 :   ulong root_idx = sched->root_idx;
    2981           0 : 
    2982           0 :   if( FD_UNLIKELY( root_idx==ULONG_MAX ) ) {
    2983           0 :     FD_LOG_CRIT(( "invariant violation: root_idx==ULONG_MAX indicating fd_sched is uninitialized" ));
    2984           0 :   }
    2985           0 : 
    2986           0 :   /* First pass: compute the longest unstaged fork depth for each node
    2987           0 :      in the fork tree. */
    2988           0 :   ulong depth = compute_longest_unstaged_fork( sched, root_idx );
    2989           0 : 
    2990           0 :   /* Second pass: stage blocks on the longest unstaged fork. */
    2991           0 :   ulong head_bank_idx = stage_longest_unstaged_fork( sched, root_idx, lane_idx );
    2992           0 : 
    2993           0 :   if( FD_UNLIKELY( (depth>0UL && head_bank_idx==ULONG_MAX) || (depth==0UL && head_bank_idx!=ULONG_MAX) ) ) {
    2994           0 :     FD_LOG_CRIT(( "invariant violation: depth %lu, head_bank_idx %lu",
    2995           0 :                   depth, head_bank_idx ));
    2996           0 :   }
    2997           0 : 
    2998           0 :   return head_bank_idx;
    2999           0 : }
    3000             : 
    3001             : /* Returns length of the longest stageable unstaged fork, if there is
    3002             :    one, and 0 otherwise. */
    3003             : static ulong
    3004          96 : compute_longest_unstaged_fork( fd_sched_t * sched, ulong bank_idx ) {
    3005          96 :   if( FD_UNLIKELY( bank_idx==ULONG_MAX ) ) {
    3006           0 :     FD_LOG_CRIT(( "invariant violation: bank_idx==ULONG_MAX" ));
    3007           0 :   }
    3008             : 
    3009          96 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3010             : 
    3011          96 :   ulong max_child_depth = 0UL;
    3012          96 :   ulong child_idx       = block->child_idx;
    3013         165 :   while( child_idx!=ULONG_MAX ) {
    3014          69 :     ulong child_depth = compute_longest_unstaged_fork( sched, child_idx );
    3015          69 :     if( child_depth > max_child_depth ) {
    3016           3 :       max_child_depth = child_depth;
    3017           3 :     }
    3018          69 :     fd_sched_block_t * child = block_pool_ele( sched, child_idx );
    3019          69 :     child_idx = child->sibling_idx;
    3020          69 :   }
    3021             : 
    3022          96 :   block->luf_depth = max_child_depth + fd_ulong_if( block_is_promotable( block ), 1UL, 0UL );
    3023          96 :   return block->luf_depth;
    3024          96 : }
    3025             : 
    3026             : static ulong
    3027           6 : stage_longest_unstaged_fork_helper( fd_sched_t * sched, ulong bank_idx, int lane_idx ) {
    3028           6 :   if( FD_UNLIKELY( bank_idx==ULONG_MAX ) ) {
    3029           0 :     FD_LOG_CRIT(( "invariant violation: bank_idx==ULONG_MAX" ));
    3030           0 :   }
    3031             : 
    3032           6 :   fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3033             : 
    3034           6 :   int   stage_it = fd_int_if( block_is_promotable( block ), 1, 0 );
    3035           6 :   ulong rv       = fd_ulong_if( stage_it, bank_idx, ULONG_MAX );
    3036           6 :   if( FD_LIKELY( stage_it ) ) {
    3037           3 :     block->staged = 1;
    3038           3 :     block->staging_lane = (ulong)lane_idx;
    3039           3 :     fd_rdisp_promote_block( sched->rdisp, bank_idx, block->staging_lane );
    3040           3 :     sched->metrics->block_promoted_cnt++;
    3041           3 :     FD_LOG_DEBUG(( "block %lu:%lu entered lane %lu: promote", block->slot, bank_idx, block->staging_lane ));
    3042           3 :   }
    3043             : 
    3044             :   /* Base case: leaf node. */
    3045           6 :   if( block->child_idx==ULONG_MAX ) return rv;
    3046             : 
    3047           3 :   ulong max_depth      = 0UL;
    3048           3 :   ulong best_child_idx = ULONG_MAX;
    3049           3 :   ulong child_idx      = block->child_idx;
    3050          18 :   while( child_idx!=ULONG_MAX ) {
    3051          15 :     fd_sched_block_t * child = block_pool_ele( sched, child_idx );
    3052          15 :     if( child->luf_depth>max_depth ) {
    3053           3 :       max_depth      = child->luf_depth;
    3054           3 :       best_child_idx = child_idx;
    3055           3 :     }
    3056          15 :     child_idx = child->sibling_idx;
    3057          15 :   }
    3058             : 
    3059             :   /* Recursively stage descendants. */
    3060           3 :   if( best_child_idx!=ULONG_MAX ) {
    3061           3 :     ulong head_bank_idx = stage_longest_unstaged_fork_helper( sched, best_child_idx, lane_idx );
    3062           3 :     rv = fd_ulong_if( rv!=ULONG_MAX, rv, head_bank_idx );
    3063           3 :   }
    3064             : 
    3065           3 :   return rv;
    3066           6 : }
    3067             : 
    3068             : /* Returns idx of head block of staged lane on success, idx_null
    3069             :    otherwise. */
    3070             : static ulong
    3071           3 : stage_longest_unstaged_fork( fd_sched_t * sched, ulong bank_idx, int lane_idx ) {
    3072           3 :   ulong head_bank_idx = stage_longest_unstaged_fork_helper( sched, bank_idx, lane_idx );
    3073           3 :   if( FD_LIKELY( head_bank_idx!=ULONG_MAX ) ) {
    3074           3 :     sched->metrics->lane_promoted_cnt++;
    3075           3 :     sched->staged_bitset = fd_ulong_set_bit( sched->staged_bitset, lane_idx );
    3076             :     /* No need to update staged_popcnt_wmk because the fact that there
    3077             :        are unstaged blocks implies we already maxed out lanes at one
    3078             :        point. */
    3079           3 :     sched->staged_head_bank_idx[ lane_idx ] = head_bank_idx;
    3080           3 :   }
    3081           3 :   return head_bank_idx;
    3082           3 : }
    3083             : 
    3084             : /* Check if an entire staging lane can be demoted.  Returns 1 if all
    3085             :    blocks in the lane are demotable, 0 otherwise. */
    3086             : static int
    3087          12 : lane_is_demotable( fd_sched_t * sched, int lane_idx ) {
    3088          12 :   ulong bank_idx = sched->staged_head_bank_idx[ lane_idx ];
    3089             : 
    3090          24 :   while( bank_idx!=ULONG_MAX ) {
    3091          12 :     fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3092          12 :     FD_TEST( block->staged );
    3093          12 :     FD_TEST( block->staging_lane==(ulong)lane_idx );
    3094             : 
    3095          12 :     if( FD_UNLIKELY( !block_is_demotable( block ) ) ) {
    3096             :       /* Found a non-demotable block.  Early exit. */
    3097           0 :       return 0;
    3098           0 :     }
    3099             : 
    3100             :     /* Find the child in the same staging lane. */
    3101          12 :     ulong child_idx = block->child_idx;
    3102          12 :     ulong next_bank_idx = ULONG_MAX;
    3103          12 :     while( child_idx!=ULONG_MAX ) {
    3104           0 :       fd_sched_block_t * child = block_pool_ele( sched, child_idx );
    3105           0 :       if( child->staged && child->staging_lane==(ulong)lane_idx ) {
    3106           0 :         next_bank_idx = child_idx;
    3107           0 :         break;
    3108           0 :       }
    3109           0 :       child_idx = child->sibling_idx;
    3110           0 :     }
    3111          12 :     bank_idx = next_bank_idx;
    3112          12 :   }
    3113             : 
    3114          12 :   return 1;
    3115          12 : }
    3116             : 
    3117             : /* Demote all blocks in a staging lane.  Assumes that all blocks in the
    3118             :    lane are demotable.  Returns the number of blocks demoted. */
    3119             : static ulong
    3120          12 : demote_lane( fd_sched_t * sched, int lane_idx ) {
    3121          12 :   ulong bank_idx = sched->staged_head_bank_idx[ lane_idx ];
    3122          12 :   uint  demoted_cnt = 0U;
    3123             : 
    3124          24 :   while( bank_idx!=ULONG_MAX ) {
    3125          12 :     fd_sched_block_t * block = block_pool_ele( sched, bank_idx );
    3126          12 :     FD_TEST( block->staged );
    3127          12 :     FD_TEST( block->staging_lane==(ulong)lane_idx );
    3128             : 
    3129          12 :     int ret = fd_rdisp_demote_block( sched->rdisp, bank_idx );
    3130          12 :     if( FD_UNLIKELY( ret!=0 ) ) {
    3131           0 :       FD_LOG_CRIT(( "fd_rdisp_demote_block failed for block %lu:%lu, lane %d", block->slot, bank_idx, lane_idx ));
    3132           0 :     }
    3133          12 :     FD_LOG_DEBUG(( "block %lu:%lu exited lane %lu: demote", block->slot, bank_idx, block->staging_lane ));
    3134          12 :     block->staged = 0;
    3135          12 :     demoted_cnt++;
    3136             : 
    3137             :     /* Find the child in the same staging lane. */
    3138          12 :     ulong child_idx = block->child_idx;
    3139          12 :     ulong next_bank_idx = ULONG_MAX;
    3140          12 :     while( child_idx!=ULONG_MAX ) {
    3141           0 :       fd_sched_block_t * child = block_pool_ele( sched, child_idx );
    3142           0 :       if( child->staged && child->staging_lane==(ulong)lane_idx ) {
    3143           0 :         next_bank_idx = child_idx;
    3144           0 :         break;
    3145           0 :       }
    3146           0 :       child_idx = child->sibling_idx;
    3147           0 :     }
    3148          12 :     bank_idx = next_bank_idx;
    3149          12 :   }
    3150             : 
    3151             :   /* Clear the lane. */
    3152          12 :   sched->staged_bitset = fd_ulong_clear_bit( sched->staged_bitset, lane_idx );
    3153          12 :   sched->staged_head_bank_idx[ lane_idx ] = ULONG_MAX;
    3154             : 
    3155          12 :   sched->metrics->block_demoted_cnt += demoted_cnt;
    3156          12 :   FD_LOG_DEBUG(( "demoted %u blocks in lane %d", demoted_cnt, lane_idx ));
    3157          12 :   return demoted_cnt;
    3158          12 : }

Generated by: LCOV version 1.14