LCOV - code coverage report
Current view: top level - discof/poh - fd_poh.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 392 0.0 %
Date: 2026-09-17 04:28:31 Functions: 0 19 0.0 %

          Line data    Source code
       1             : #include "fd_poh.h"
       2             : #include "../../disco/tiles.h"
       3             : 
       4             : /* The PoH implementation is at its core a state machine ...
       5             : 
       6             :                            +--------+
       7             :                            | UNINIT |
       8             :                            +--------+
       9             :                                 |
      10             :                   +---------+   |    +---------+
      11             :                   |         v   v    v         |
      12             :  +-------------------+     +----------+      +------------------+
      13             :  |  WAITING_FOR_SLOT |<----| FOLLOWER |----->| WAITING_FOR_BANK |
      14             :  +-------------------+     +----------+      +------------------+
      15             :                   |             ^              |
      16             :                   |             |              |
      17             :                   |       +----------+         |
      18             :                   |------>|  LEADER  |<--------+
      19             :                           +----------+
      20             : 
      21             :    The state machine starts UNINIT, but once a snapshot is loaded it
      22             :    will transition to follower.
      23             : 
      24             :    The state machine is in a resting the state when FOLLOWER, in this
      25             :    state it knows a `next_leader_slot` and will continually hash to
      26             :    advance towards that slot.  When it reaches the `next_leader_slot`
      27             :    it will transition to the WAITING_FOR_BANK state, where it waits for
      28             :    the replay stage to tell it some information relevant to that leader
      29             :    slot, so that it can start doing mixins and hashing towards the end
      30             :    of the block.  When the block ends, the state transitions back to
      31             :    follower, even if the next slot is the leader, as we need the replay
      32             :    stage to tell us about the new leader slot.
      33             : 
      34             :    Sometimes it might happen that we have received the bank from replay
      35             :    stage before we have reached the `next_leader_slot`, in which case
      36             :    we transition to the WAITING_FOR_SLOT state, where we wait for the
      37             :    hash count to reach the leader slot.
      38             : 
      39             :    At any time, during any state except UNINIT, we can be suddenly
      40             :    "reset" by the replay tile.  Such reset actions may move the reset
      41             :    slot backwards or forwards, or set it back to something we have
      42             :    already seen before.  BUT, the `next_leader_slot` must always
      43             :    advance forward.
      44             : 
      45             :    If the PoH machine successfully completes a leader slot, by hashing
      46             :    it until the end, then the a completion message is sent back to
      47             :    replay with the final blockhash, after which the state machine enters
      48             :    the follower state once again, and waits for further instructions
      49             :    from replay. */
      50             : 
      51           0 : #define STATE_UNINIT            (0)
      52           0 : #define STATE_FOLLOWER          (1)
      53           0 : #define STATE_WAITING_FOR_BANK  (2)
      54           0 : #define STATE_WAITING_FOR_SLOT  (3)
      55           0 : #define STATE_LEADER            (4)
      56           0 : #define STATE_WAITING_FOR_RESET (5)
      57             : 
      58             : FD_FN_CONST ulong
      59           0 : fd_poh_align( void ) {
      60           0 :   return FD_POH_ALIGN;
      61           0 : }
      62             : 
      63             : FD_FN_CONST ulong
      64           0 : fd_poh_footprint( void ) {
      65           0 :   return sizeof(fd_poh_t);
      66           0 : }
      67             : 
      68             : void *
      69           0 : fd_poh_new( void * shmem ) {
      70           0 :   fd_poh_t * poh = (fd_poh_t *)shmem;
      71             : 
      72           0 :   if( FD_UNLIKELY( !poh ) ) {
      73           0 :     FD_LOG_WARNING(( "NULL shmem" ));
      74           0 :     return NULL;
      75           0 :   }
      76             : 
      77           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)poh, fd_poh_align() ) ) ) {
      78           0 :     FD_LOG_WARNING(( "misaligned shmem" ));
      79           0 :     return NULL;
      80           0 :   }
      81             : 
      82           0 :   poh->hashcnt_per_tick = ULONG_MAX;
      83           0 :   poh->state = STATE_UNINIT;
      84           0 :   poh->wfs_paused = 0;
      85             : 
      86           0 :   FD_COMPILER_MFENCE();
      87           0 :   FD_VOLATILE( poh->magic ) = FD_POH_MAGIC;
      88           0 :   FD_COMPILER_MFENCE();
      89             : 
      90           0 :   return (void *)poh;
      91           0 : }
      92             : 
      93             : fd_poh_t *
      94             : fd_poh_join( void *                         shpoh,
      95             :              fd_poh_out_t *                 shred_out,
      96             :              fd_poh_out_t *                 replay_out,
      97             :              fd_leader_txn_timing_table_t * timing_tables,
      98           0 :              ulong                          timing_table_max ) {
      99           0 :   if( FD_UNLIKELY( !shpoh ) ) {
     100           0 :     FD_LOG_WARNING(( "NULL shpoh" ));
     101           0 :     return NULL;
     102           0 :   }
     103             : 
     104           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shpoh, fd_poh_align() ) ) ) {
     105           0 :     FD_LOG_WARNING(( "misaligned shpoh" ));
     106           0 :     return NULL;
     107           0 :   }
     108             : 
     109           0 :   fd_poh_t * poh = (fd_poh_t *)shpoh;
     110             : 
     111           0 :   if( FD_UNLIKELY( poh->magic!=FD_POH_MAGIC ) ) {
     112           0 :     FD_LOG_WARNING(( "bad magic" ));
     113           0 :     return NULL;
     114           0 :   }
     115             : 
     116           0 :   *poh->shred_out = *shred_out;
     117           0 :   *poh->replay_out = *replay_out;
     118             : 
     119           0 :   poh->timing_tables    = timing_tables;
     120           0 :   poh->timing_table_idx = 0UL;
     121           0 :   poh->timing_table_max = timing_table_max;
     122             : 
     123           0 :   return poh;
     124           0 : }
     125             : 
     126             : static void
     127             : transition_to_follower( fd_poh_t *          poh,
     128             :                         fd_stem_context_t * stem,
     129           0 :                         int                 completed_leader_slot ) {
     130           0 :   FD_TEST( poh->state==STATE_LEADER || poh->state==STATE_WAITING_FOR_BANK || poh->state==STATE_WAITING_FOR_SLOT || poh->state==STATE_WAITING_FOR_RESET );
     131             : 
     132           0 :   if( FD_LIKELY( completed_leader_slot ) ) FD_TEST( poh->state==STATE_LEADER );
     133             : 
     134           0 :   if( FD_LIKELY( poh->state==STATE_LEADER || poh->state==STATE_WAITING_FOR_SLOT ) ) {
     135           0 :     fd_poh_leader_slot_ended_t * dst = fd_chunk_to_laddr( poh->replay_out->mem, poh->replay_out->chunk );
     136           0 :     dst->completed = completed_leader_slot;
     137           0 :     dst->slot      = fd_ulong_if( completed_leader_slot, poh->slot-1UL, poh->slot );
     138           0 :     fd_memcpy( dst->blockhash, poh->hash, 32UL );
     139             : 
     140           0 :     dst->microblock_count = poh->pack_microblock_count;
     141           0 :     dst->pack_block_cost  = poh->pack_block_cost;
     142           0 :     dst->pack_vote_cost   = poh->pack_vote_cost;
     143           0 :     dst->pack_data_bytes  = poh->pack_data_bytes;
     144           0 :     dst->bundle_txn_count = poh->pack_bundle_txn_count;
     145           0 :     dst->pack_end_reason  = poh->pack_end_reason;
     146           0 :     dst->pack_start_ns    = poh->pack_start_ns;
     147           0 :     dst->pack_end_ns      = poh->pack_end_ns;
     148           0 :     dst->timing_table_idx = poh->timing_tables ? poh->timing_table_idx : ULONG_MAX;
     149             : 
     150           0 :     ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
     151           0 :     fd_stem_publish( stem, poh->replay_out->idx, 0UL, poh->replay_out->chunk, sizeof(fd_poh_leader_slot_ended_t), 0UL, 0UL, tspub );
     152           0 :     poh->replay_out->chunk = fd_dcache_compact_next( poh->replay_out->chunk, sizeof(fd_poh_leader_slot_ended_t), poh->replay_out->chunk0, poh->replay_out->wmark );
     153           0 :   }
     154             : 
     155           0 :   poh->state = STATE_FOLLOWER;
     156           0 : }
     157             : 
     158             : static void
     159             : update_hashes_per_tick( fd_poh_t * poh,
     160           0 :                         ulong      hashcnt_per_tick ) {
     161           0 :   if( FD_UNLIKELY( poh->hashcnt_per_tick!=hashcnt_per_tick ) ) {
     162           0 :     if( FD_UNLIKELY( poh->hashcnt_per_tick!=ULONG_MAX ) ) {
     163           0 :       FD_LOG_WARNING(( "hashes per tick changed from %lu to %lu", poh->hashcnt_per_tick, hashcnt_per_tick ));
     164           0 :     }
     165             : 
     166             :     /* Recompute derived information about the clock. */
     167           0 :     poh->hashcnt_duration_ns = (double)poh->tick_duration_ns/(double)hashcnt_per_tick;
     168           0 :     poh->hashcnt_per_slot = poh->ticks_per_slot*hashcnt_per_tick;
     169           0 :     poh->hashcnt_per_tick = hashcnt_per_tick;
     170             : 
     171             :     /* Discard any ticks we might have done in the interim.  They will
     172             :        have the wrong number of hashes per tick.  We can just catch back
     173             :        up quickly if not too many slots were skipped and hopefully
     174             :        publish on time.  Note that tick production and verification of
     175             :        skipped slots is done for the eventual bank that publishes a
     176             :        slot, for example:
     177             : 
     178             :         Reset Slot:            998
     179             :         Epoch Transition Slot: 1000
     180             :         Leader Slot:           1002
     181             : 
     182             :        In this case, if a feature changing the hashcnt_per_tick is
     183             :        activated in slot 1000, and we are publishing empty ticks for
     184             :        slots 998, 999, 1000, and 1001, they should all have the new
     185             :        hashes_per_tick number of hashes, rather than the older one, or
     186             :        some combination. */
     187             : 
     188           0 :     FD_TEST( poh->last_slot==poh->reset_slot );
     189           0 :     FD_TEST( !poh->last_hashcnt );
     190           0 :     poh->slot = poh->reset_slot;
     191           0 :     poh->hashcnt = 0UL;
     192           0 :     fd_memcpy( poh->hash, poh->reset_hash, 32UL );
     193           0 :   }
     194           0 : }
     195             : 
     196             : void
     197             : fd_poh_reset( fd_poh_t *          poh,
     198             :               fd_stem_context_t * stem,
     199             :               long                timestamp,               /* The local timestamp when the reset is occurring */
     200             :               ulong               hashcnt_per_tick,        /* The hashcnt per tick of the bank that completed */
     201             :               ulong               ticks_per_slot,
     202             :               ulong               tick_duration_ns,
     203             :               ulong               completed_slot,          /* The slot that successfully produced a block */
     204             :               uchar const *       completed_blockhash,     /* The hash of the last tick in the produced block */
     205             :               ulong               next_leader_slot,        /* The next slot where this node will be leader */
     206             :               ulong               max_microblocks_in_slot, /* The maximum number of microblocks that may appear in a slot */
     207           0 :               uchar const *       completed_block_id       /* The block id of the completed block */)  {
     208           0 :   FD_TEST( memcmp( poh->completed_block_id, completed_block_id, 32UL ) );
     209             : 
     210           0 :   memcpy( poh->reset_hash, completed_blockhash, 32UL );
     211           0 :   memcpy( poh->hash, completed_blockhash, 32UL );
     212           0 :   memcpy( poh->completed_block_id, completed_block_id, 32UL );
     213           0 :   poh->slot                     = completed_slot+1UL;
     214           0 :   poh->hashcnt                  = 0UL;
     215           0 :   poh->last_slot                = poh->slot;
     216           0 :   poh->last_hashcnt             = 0UL;
     217           0 :   poh->reset_slot               = poh->slot;
     218           0 :   poh->next_leader_slot         = next_leader_slot;
     219           0 :   poh->max_microblocks_per_slot = max_microblocks_in_slot;
     220           0 :   poh->reset_slot_start_ns      = timestamp;
     221             : 
     222           0 :   if( FD_UNLIKELY( poh->state==STATE_UNINIT ) ) {
     223           0 :     poh->tick_duration_ns = tick_duration_ns;
     224           0 :     poh->ticks_per_slot   = ticks_per_slot;
     225           0 :     poh->state = STATE_FOLLOWER;
     226           0 :   } else {
     227           0 :     poh->tick_duration_ns = tick_duration_ns;
     228           0 :     FD_TEST( ticks_per_slot==poh->ticks_per_slot );
     229           0 :   }
     230           0 :   update_hashes_per_tick( poh, hashcnt_per_tick );
     231             : 
     232             :   /* When we reset, we need to allow PoH to tick freely again rather
     233             :      than being constrained.  If we are leader after the reset, this
     234             :      is OK because we won't tick until we get a bank, and the lower
     235             :      bound will be reset with the value from the bank. */
     236           0 :   poh->microblocks_lower_bound = poh->max_microblocks_per_slot;
     237             : 
     238           0 :   if( FD_UNLIKELY( poh->state!=STATE_FOLLOWER ) ) transition_to_follower( poh, stem, 0 );
     239           0 :   if( FD_UNLIKELY( poh->slot==poh->next_leader_slot ) ) poh->state = STATE_WAITING_FOR_BANK;
     240             : 
     241           0 : }
     242             : 
     243             : void
     244             : fd_poh_begin_leader( fd_poh_t * poh,
     245             :                      ulong      slot,
     246             :                      ulong      hashcnt_per_tick,
     247             :                      ulong      ticks_per_slot,
     248             :                      ulong      tick_duration_ns,
     249             :                      ulong      max_microblocks_in_slot,
     250           0 :                      long       slot_start_ns ) {
     251           0 :   FD_TEST( poh->state==STATE_FOLLOWER || poh->state==STATE_WAITING_FOR_BANK );
     252           0 :   FD_TEST( slot==poh->next_leader_slot );
     253             : 
     254             :   /* PoH ends the slot once it "ticks" through all of the hashes, but we
     255             :      only want that to happen if we have received a done packing message
     256             :      from pack, so we always reserve an empty microblock at the end so
     257             :      the tick advance will not end the slot without being told. */
     258           0 :   poh->max_microblocks_per_slot = max_microblocks_in_slot+1UL;
     259             : 
     260           0 :   poh->tick_duration_ns = tick_duration_ns;
     261           0 :   FD_TEST( ticks_per_slot==poh->ticks_per_slot );
     262           0 :   update_hashes_per_tick( poh, hashcnt_per_tick );
     263             : 
     264           0 :   FD_TEST( poh->slot<=poh->next_leader_slot );
     265           0 :   if( FD_LIKELY( poh->slot<poh->next_leader_slot ) ) poh->state = STATE_WAITING_FOR_SLOT;
     266           0 :   else                                               poh->state = STATE_LEADER;
     267             : 
     268           0 :   poh->microblocks_lower_bound = 0UL;
     269           0 :   poh->leader_slot_start_ns    = slot_start_ns;
     270             : 
     271           0 :   if( FD_LIKELY( poh->timing_tables ) ) {
     272           0 :     poh->timing_table_idx ^= 1UL;
     273           0 :     fd_leader_txn_timing_table_t * table = fd_leader_txn_timing_table( poh->timing_tables, poh->timing_table_idx, poh->timing_table_max );
     274           0 :     table->slot = slot;
     275           0 :     table->cnt  = 0UL;
     276           0 :   }
     277             : 
     278           0 :   FD_LOG_INFO(( "begin_leader(slot=%lu, last_slot=%lu, last_hashcnt=%lu)", slot, poh->last_slot, poh->last_hashcnt ));
     279           0 : }
     280             : 
     281             : int
     282           0 : fd_poh_have_leader_bank( fd_poh_t const * poh ) {
     283           0 :   return poh->state==STATE_WAITING_FOR_SLOT || poh->state==STATE_LEADER;
     284           0 : }
     285             : 
     286             : int
     287           0 : fd_poh_hashing_to_leader_slot( fd_poh_t const * poh ) {
     288           0 :   int hashing = poh->state==STATE_WAITING_FOR_SLOT || poh->state==STATE_LEADER;
     289           0 :   return hashing && poh->slot<poh->next_leader_slot;
     290           0 : }
     291             : 
     292             : int
     293           0 : fd_poh_must_tick( fd_poh_t const * poh ) {
     294           0 :   return poh->state==STATE_LEADER && (poh->hashcnt%poh->hashcnt_per_tick)==(poh->hashcnt_per_tick-1UL);
     295           0 : }
     296             : 
     297             : int
     298           0 : fd_poh_must_publish_skipped_tick( fd_poh_t const * poh ) {
     299           0 :   return poh->state==STATE_LEADER && poh->last_slot<poh->slot;
     300           0 : }
     301             : 
     302             : void
     303           0 : fd_poh_wfs_done( fd_poh_t * poh ) {
     304           0 :   poh->wfs_paused = 0;
     305           0 :   poh->reset_slot_start_ns = fd_log_wallclock();
     306           0 : }
     307             : 
     308             : void
     309             : fd_poh_update_max_microblocks( fd_poh_t * poh,
     310           0 :                                ulong      new_max ) {
     311           0 :   ulong inflated = new_max + 1UL;
     312             : 
     313             :   /* Guaranteed to be monotonically decreasing. */
     314           0 :   FD_TEST( inflated <= poh->max_microblocks_per_slot );
     315           0 :   poh->max_microblocks_per_slot = inflated;
     316           0 :   FD_TEST( poh->max_microblocks_per_slot >= poh->microblocks_lower_bound );
     317           0 : }
     318             : 
     319             : void
     320             : fd_poh_done_packing( fd_poh_t *                poh,
     321             :                      fd_stem_context_t *       stem,
     322           0 :                      fd_done_packing_t const * done_packing ) {
     323           0 :   ulong microblocks_in_slot = done_packing->microblocks_in_slot;
     324             : 
     325           0 :   FD_TEST( poh->state==STATE_LEADER );
     326           0 :   FD_LOG_INFO(( "done_packing(slot=%lu,seen_microblocks=%lu,microblocks_in_slot=%lu)",
     327           0 :                 poh->slot,
     328           0 :                 poh->microblocks_lower_bound,
     329           0 :                 microblocks_in_slot ));
     330           0 :   FD_TEST( poh->microblocks_lower_bound==microblocks_in_slot );
     331           0 :   FD_TEST( poh->microblocks_lower_bound<=poh->max_microblocks_per_slot );
     332             : 
     333           0 :   poh->pack_microblock_count = microblocks_in_slot;
     334           0 :   poh->pack_block_cost       = done_packing->limits_usage->block_cost;
     335           0 :   poh->pack_vote_cost        = done_packing->limits_usage->vote_cost;
     336           0 :   poh->pack_data_bytes       = done_packing->limits_usage->block_data_bytes;
     337           0 :   poh->pack_bundle_txn_count = done_packing->bundle_txn_count;
     338           0 :   poh->pack_end_reason       = done_packing->end_slot_reason;
     339           0 :   poh->pack_start_ns         = done_packing->pack_start_ns;
     340           0 :   poh->pack_end_ns           = done_packing->pack_end_ns;
     341             : 
     342           0 :   if( FD_UNLIKELY( done_packing->end_slot_reason==FD_PACK_END_SLOT_REASON_ABANDONED ) ) {
     343           0 :     transition_to_follower( poh, stem, 0 );
     344           0 :     return;
     345           0 :   }
     346             : 
     347           0 :   poh->microblocks_lower_bound += 1UL /* done_packing as a phantom "microblock"*/
     348           0 :                                 + (poh->max_microblocks_per_slot-1UL) /* the canonical microblock limit */
     349           0 :                                 - microblocks_in_slot /* the actual microblock count */;
     350           0 :   FD_TEST( poh->microblocks_lower_bound==poh->max_microblocks_per_slot );
     351           0 : }
     352             : 
     353             : static void
     354             : publish_tick( fd_poh_t *          poh,
     355             :               fd_stem_context_t * stem,
     356             :               uchar               hash[ static 32 ],
     357           0 :               int                 is_skipped ) {
     358           0 :   ulong hashcnt = poh->hashcnt_per_tick*(1UL+(poh->last_hashcnt/poh->hashcnt_per_tick));
     359             : 
     360           0 :   uchar * dst = (uchar *)fd_chunk_to_laddr( poh->shred_out->mem, poh->shred_out->chunk );
     361             : 
     362           0 :   FD_TEST( poh->last_slot>=poh->reset_slot );
     363           0 :   fd_entry_batch_meta_t * meta = (fd_entry_batch_meta_t *)dst;
     364           0 :   if( FD_UNLIKELY( is_skipped ) ) {
     365             :     /* We are publishing ticks for a skipped slot, the reference tick
     366             :        and block complete flags should always be zero. */
     367           0 :     meta->reference_tick = 0UL;
     368           0 :     meta->block_complete = 0;
     369           0 :   } else {
     370           0 :     meta->reference_tick = hashcnt/poh->hashcnt_per_tick;
     371           0 :     meta->block_complete = hashcnt==poh->hashcnt_per_slot;
     372           0 :   }
     373             : 
     374           0 :   meta->parent_block_id_valid = 1;
     375           0 :   fd_memcpy( meta->parent_block_id, poh->completed_block_id, 32UL );
     376             : 
     377           0 :   ulong slot = fd_ulong_if( meta->block_complete, poh->slot-1UL, poh->slot );
     378           0 :   meta->parent_offset = 1UL+slot-poh->reset_slot;
     379             : 
     380           0 :   FD_TEST( hashcnt>poh->last_hashcnt );
     381           0 :   ulong hash_delta = hashcnt-poh->last_hashcnt;
     382             : 
     383           0 :   dst += sizeof(fd_entry_batch_meta_t);
     384           0 :   fd_entry_batch_header_t * tick = (fd_entry_batch_header_t *)dst;
     385           0 :   tick->hashcnt_delta = hash_delta;
     386           0 :   fd_memcpy( tick->hash, hash, 32UL );
     387           0 :   tick->txn_cnt = 0UL;
     388             : 
     389           0 :   ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
     390           0 :   ulong sz = sizeof(fd_entry_batch_meta_t)+sizeof(fd_entry_batch_header_t);
     391           0 :   ulong sig = fd_disco_poh_sig( slot, POH_PKT_TYPE_MICROBLOCK, 0UL );
     392           0 :   fd_stem_publish( stem, poh->shred_out->idx, sig, poh->shred_out->chunk, sz, 0UL, 0UL, tspub );
     393           0 :   poh->shred_out->chunk = fd_dcache_compact_next( poh->shred_out->chunk, sz, poh->shred_out->chunk0, poh->shred_out->wmark );
     394             : 
     395           0 :   if( FD_UNLIKELY( hashcnt==poh->hashcnt_per_slot ) ) {
     396           0 :     poh->last_slot++;
     397           0 :     poh->last_hashcnt = 0UL;
     398           0 :   } else {
     399           0 :     poh->last_hashcnt = hashcnt;
     400           0 :   }
     401           0 : }
     402             : 
     403             : void
     404             : fd_poh_advance( fd_poh_t *          poh,
     405             :                 fd_stem_context_t * stem,
     406             :                 int *               opt_poll_in,
     407           0 :                 int *               charge_busy ) {
     408           0 :   if( FD_UNLIKELY( poh->state==STATE_UNINIT || poh->state==STATE_WAITING_FOR_RESET ) ) return;
     409           0 :   if( FD_UNLIKELY( poh->wfs_paused ) ) return;
     410           0 :   if( FD_UNLIKELY( poh->state==STATE_WAITING_FOR_BANK ) ) {
     411             :     /* If we are the leader, but we didn't yet learn what the leader
     412             :        bank object is from the replay tile, do not do any hashing. */
     413           0 :     return;
     414           0 :   }
     415             : 
     416             :   /* No need to hash if we are so far from a leader slot that skipping
     417             :      all the required ticks would exceed MAX_SKIPPED_TICKS, or the u16
     418             :      shred parent_offset (not possible to make the block anymore). */
     419           0 :   if( FD_LIKELY( poh->state==STATE_FOLLOWER ) ) {
     420           0 :     if( FD_LIKELY( poh->next_leader_slot==ULONG_MAX ||
     421           0 :                    poh->next_leader_slot-poh->slot>fd_ulong_min( MAX_SKIPPED_TICKS, USHORT_MAX-poh->ticks_per_slot )/poh->ticks_per_slot ) ) return;
     422           0 :   }
     423             : 
     424             :   /* If we have skipped ticks pending because we skipped some slots to
     425             :      become leader, register them now one at a time. */
     426           0 :   if( FD_UNLIKELY( fd_poh_must_publish_skipped_tick( poh ) ) ) {
     427           0 :     FD_TEST( poh->hashcnt==0UL ); /* Current hashcnt stays 0 until the math below is run at least once. */
     428           0 :     FD_TEST( !(poh->last_hashcnt%poh->hashcnt_per_tick) ); /* While skipped ticks are being published, last_hashcnt marches forward in increments of hashcnt_per_tick. */
     429           0 :     ulong publish_hashcnt = poh->last_hashcnt+poh->hashcnt_per_tick;
     430           0 :     ulong tick_idx = (poh->last_slot*poh->ticks_per_slot+publish_hashcnt/poh->hashcnt_per_tick)%MAX_SKIPPED_TICKS;
     431             : 
     432           0 :     publish_tick( poh, stem, poh->skipped_tick_hashes[ tick_idx ], 1 );
     433             : 
     434             :     /* If we are catching up now and publishing a bunch of skipped
     435             :        ticks, we do not want to process any incoming microblocks until
     436             :        all the skipped ticks have been published out; otherwise we would
     437             :        intersperse skipped tick messages with microblocks. */
     438           0 :     *opt_poll_in = 0;
     439           0 :     *charge_busy = 1;
     440           0 :     return;
     441           0 :   }
     442             : 
     443           0 :   int low_power_mode = poh->hashcnt_per_tick==1UL;
     444             : 
     445             :   /* If we are the leader, always leave enough capacity in the slot so
     446             :      that we can mixin any potential microblocks still coming from the
     447             :      pack tile for this slot.
     448             : 
     449             :      When not leading (FOLLOWER or WAITING_FOR_SLOT), no microblocks
     450             :      will be mixed in, so there is nothing to reserve so
     451             :      restricted_hashcnt does not need to be reduced from hashcnt_per_slot. */
     452           0 :   ulong max_remaining_microblocks;
     453           0 :   ulong restricted_hashcnt;
     454           0 :   if( FD_LIKELY( poh->state==STATE_LEADER ) ) {
     455           0 :     max_remaining_microblocks = poh->max_microblocks_per_slot - poh->microblocks_lower_bound;
     456             : 
     457             :     /* With hashcnt_per_tick hashes per tick, we actually get
     458             :        hashcnt_per_tick-1 chances to mixin a microblock.  For each tick
     459             :        span that we need to reserve, we also need to reserve the hashcnt
     460             :        for the tick, hence the +
     461             :        max_remaining_microblocks/(hashcnt_per_tick-1) rounded up.
     462             : 
     463             :        However, if hashcnt_per_tick is 1 because we're in low power mode,
     464             :        this should probably just be max_remaining_microblocks. */
     465           0 :     ulong max_remaining_ticks_or_microblocks = max_remaining_microblocks;
     466           0 :     if( FD_LIKELY( !low_power_mode ) ) max_remaining_ticks_or_microblocks += (max_remaining_microblocks+poh->hashcnt_per_tick-2UL)/(poh->hashcnt_per_tick-1UL);
     467             : 
     468           0 :     restricted_hashcnt = fd_ulong_if( poh->hashcnt_per_slot>=max_remaining_ticks_or_microblocks, poh->hashcnt_per_slot-max_remaining_ticks_or_microblocks, 0UL );
     469           0 :   } else {
     470           0 :     max_remaining_microblocks = 0UL;
     471           0 :     restricted_hashcnt        = poh->hashcnt_per_slot;
     472           0 :   }
     473             : 
     474           0 :   ulong min_hashcnt = poh->hashcnt;
     475             : 
     476           0 :   if( FD_LIKELY( !low_power_mode ) ) {
     477             :     /* Recall that there are two kinds of events that will get published
     478             :        to the shredder,
     479             : 
     480             :          (a) Ticks. These occur every 62,500 (hashcnt_per_tick) hashcnts,
     481             :              and there will be 64 (ticks_per_slot) of them in each slot.
     482             : 
     483             :              Ticks must not have any transactions mixed into the hash.
     484             :              This is not strictly needed in theory, but is required by the
     485             :              current consensus protocol.  They get published here in
     486             :              after_credit.
     487             : 
     488             :          (b) Microblocks.  These can occur at any other hashcnt, as long
     489             :              as it is not a tick.  Microblocks cannot be empty, and must
     490             :              have at least one transactions mixed in.  These get
     491             :              published in after_frag.
     492             : 
     493             :        If hashcnt_per_tick is 1, then we are in low power mode and the
     494             :        following does not apply, since we can mix in transactions at any
     495             :        time.
     496             : 
     497             :        In the normal, non-low-power mode, though, we have to be careful
     498             :        to make sure that we do not publish microblocks on tick
     499             :        boundaries.  To do that, we need to obey two rules:
     500             :          (i)  after_credit must not leave hashcnt one before a tick
     501             :               boundary
     502             :          (ii) if after_credit begins one before a tick boundary, it must
     503             :               advance hashcnt and publish the tick
     504             : 
     505             :        There's some interplay between min_hashcnt and restricted_hashcnt
     506             :        here, and we need to show that there's always a value of
     507             :        target_hashcnt we can pick such that
     508             :            min_hashcnt <= target_hashcnt <= restricted_hashcnt.
     509             :        We'll prove this by induction for current_slot==0 and
     510             :        is_leader==true, since all other slots should be the same.
     511             : 
     512             :        Let m_j and r_j be the min_hashcnt and restricted_hashcnt
     513             :        (respectively) for the jth call to after_credit in a slot.  We
     514             :        want to show that for all values of j, it's possible to pick a
     515             :        value h_j, the value of target_hashcnt for the jth call to
     516             :        after_credit (which is also the value of hashcnt after
     517             :        after_credit has completed) such that m_j<=h_j<=r_j.
     518             : 
     519             :        Additionally, let T be hashcnt_per_tick and N be ticks_per_slot.
     520             : 
     521             :        Starting with the base case, j==0.  m_j=0, and
     522             :          r_0 = N*T - max_microblocks_per_slot
     523             :                    - ceil(max_microblocks_per_slot/(T-1)).
     524             : 
     525             :        This is monotonic decreasing in max_microblocks_per_slot, so it
     526             :        achieves its minimum when max_microblocks_per_slot is its
     527             :        maximum.
     528             :            r_0 >= N*T - N*(T-1) - ceil( (N*(T-1))/(T-1))
     529             :                 = N*T - N*(T-1)-N = 0.
     530             :        Thus, m_0 <= r_0, as desired.
     531             : 
     532             : 
     533             : 
     534             :        Then, for the inductive step, assume there exists h_j such that
     535             :        m_j<=h_j<=r_j, and we want to show that there exists h_{j+1},
     536             :        which is the same as showing m_{j+1}<=r_{j+1}.
     537             : 
     538             :        Let a_j be 1 if we had a microblock immediately following the jth
     539             :        call to after_credit, and 0 otherwise.  Then hashcnt at the start
     540             :        of the (j+1)th call to after_frag is h_j+a_j.
     541             :        Also, set b_{j+1}=1 if we are in the case covered by rule (ii)
     542             :        above during the (j+1)th call to after_credit, i.e. if
     543             :        (h_j+a_j)%T==T-1.  Thus, m_{j+1} = h_j + a_j + b_{j+1}.
     544             : 
     545             :        If we received an additional microblock, then
     546             :        max_remaining_microblocks goes down by 1, and
     547             :        max_remaining_ticks_or_microblocks goes down by either 1 or 2,
     548             :        which means restricted_hashcnt goes up by either 1 or 2.  In
     549             :        particular, it goes up by 2 if the new value of
     550             :        max_remaining_microblocks (at the start of the (j+1)th call to
     551             :        after_credit) is congruent to 0 mod T-1.  Let b'_{j+1} be 1 if
     552             :        this condition is met and 0 otherwise.  If we receive a
     553             :        done_packing message, restricted_hashcnt can go up by more, but
     554             :        we can ignore that case, since it is less restrictive.
     555             :        Thus, r_{j+1}=r_j+a_j+b'_{j+1}.
     556             : 
     557             :        If h_j < r_j (strictly less), then h_j+a_j < r_j+a_j.  And thus,
     558             :        since b_{j+1}<=b'_{j+1}+1, just by virtue of them both being
     559             :        binary,
     560             :              h_j + a_j + b_{j+1} <  r_j + a_j + b'_{j+1} + 1,
     561             :        which is the same (for integers) as
     562             :              h_j + a_j + b_{j+1} <= r_j + a_j + b'_{j+1},
     563             :                  m_{j+1}         <= r_{j+1}
     564             : 
     565             :        On the other hand, if h_j==r_j, this is easy unless b_{j+1}==1,
     566             :        which can also only happen if a_j==1.  Then (h_j+a_j)%T==T-1,
     567             :        which means there's an integer k such that
     568             : 
     569             :              h_j+a_j==(ticks_per_slot-k)*T-1
     570             :              h_j    ==ticks_per_slot*T -  k*(T-1)-1  - k-1
     571             :                     ==ticks_per_slot*T - (k*(T-1)+1) - ceil( (k*(T-1)+1)/(T-1) )
     572             : 
     573             :        Since h_j==r_j in this case, and
     574             :        r_j==(ticks_per_slot*T) - max_remaining_microblocks_j - ceil(max_remaining_microblocks_j/(T-1)),
     575             :        we can see that the value of max_remaining_microblocks at the
     576             :        start of the jth call to after_credit is k*(T-1)+1.  Again, since
     577             :        a_j==1, then the value of max_remaining_microblocks at the start
     578             :        of the j+1th call to after_credit decreases by 1 to k*(T-1),
     579             :        which means b'_{j+1}=1.
     580             : 
     581             :        Thus, h_j + a_j + b_{j+1} == r_j + a_j + b'_{j+1}, so, in
     582             :        particular, h_{j+1}<=r_{j+1} as desired. */
     583           0 :      min_hashcnt += (ulong)(min_hashcnt%poh->hashcnt_per_tick == (poh->hashcnt_per_tick-1UL)); /* add b_{j+1}, enforcing rule (ii) */
     584           0 :   }
     585             :   /* Now figure out how many hashes are needed to "catch up" the hash
     586             :      count to the current system clock, and clamp it to the allowed
     587             :      range. */
     588           0 :   long now = fd_clock_tile_now( poh->clock );
     589           0 :   ulong target_hashcnt;
     590           0 :   if( FD_LIKELY( poh->state==STATE_FOLLOWER ||poh->state==STATE_WAITING_FOR_SLOT ) ) {
     591           0 :     target_hashcnt = (ulong)((double)(now - poh->reset_slot_start_ns) / poh->hashcnt_duration_ns) - (poh->slot-poh->reset_slot)*poh->hashcnt_per_slot;
     592           0 :   } else {
     593           0 :     FD_TEST( poh->state==STATE_LEADER );
     594           0 :     target_hashcnt = (ulong)((double)(now - poh->leader_slot_start_ns) / poh->hashcnt_duration_ns);
     595           0 :   }
     596             :   /* Clamp to [min_hashcnt, restricted_hashcnt] as above */
     597           0 :   target_hashcnt = fd_ulong_max( fd_ulong_min( target_hashcnt, restricted_hashcnt ), min_hashcnt );
     598             : 
     599             :   /* The above proof showed that it was always possible to pick a value
     600             :      of target_hashcnt, but we still have a lot of freedom in how to
     601             :      pick it.  It simplifies the code a lot if we don't keep going after
     602             :      a tick in this function.  In particular, we want to publish at most
     603             :      1 tick in this call, since otherwise we could consume infinite
     604             :      credits to publish here.  The credits are set so that we should
     605             :      only ever publish one tick during this loop.  Also, all the extra
     606             :      stuff (leader transitions, publishing ticks, etc.) we have to do
     607             :      happens at tick boundaries, so this lets us consolidate all those
     608             :      cases.
     609             : 
     610             :      Mathematically, since the current value of hashcnt is h_j+a_j, the
     611             :      next tick (advancing a full tick if we're currently at a tick) is
     612             :      t_{j+1} = T*(floor( (h_j+a_j)/T )+1).  We need to show that if we set
     613             :      h'_{j+1} = min( h_{j+1}, t_{j+1} ), it is still valid.
     614             : 
     615             :      First, h'_{j+1} <= h_{j+1} <= r_{j+1}, so we're okay in that
     616             :      direction.
     617             : 
     618             :      Next, observe that t_{j+1}>=h_j + a_j + 1, and recall that b_{j+1}
     619             :      is 0 or 1. So then,
     620             :                     t_{j+1} >= h_j+a_j+b_{j+1} = m_{j+1}.
     621             : 
     622             :      We know h_{j+1) >= m_{j+1} from before, so then h'_{j+1} >=
     623             :      m_{j+1}, as desired. */
     624             : 
     625           0 :   ulong next_tick_hashcnt = poh->hashcnt_per_tick * (1UL+(poh->hashcnt/poh->hashcnt_per_tick));
     626           0 :   target_hashcnt = fd_ulong_min( target_hashcnt, next_tick_hashcnt );
     627             : 
     628             :   /* We still need to enforce rule (i). We know that min_hashcnt%T !=
     629             :      T-1 because of rule (ii).  That means that if target_hashcnt%T ==
     630             :      T-1 at this point, target_hashcnt > min_hashcnt (notice the
     631             :      strict), so target_hashcnt-1 >= min_hashcnt and is thus still a
     632             :      valid choice for target_hashcnt. */
     633           0 :   target_hashcnt -= (ulong)( (!low_power_mode) & ((target_hashcnt%poh->hashcnt_per_tick)==(poh->hashcnt_per_tick-1UL)) );
     634             : 
     635           0 :   FD_TEST( target_hashcnt >= poh->hashcnt       );
     636           0 :   FD_TEST( target_hashcnt >= min_hashcnt        );
     637           0 :   FD_TEST( target_hashcnt <= restricted_hashcnt );
     638             : 
     639           0 :   if( FD_UNLIKELY( poh->hashcnt==target_hashcnt ) ) return; /* Nothing to do, don't publish a tick twice */
     640             : 
     641           0 :   *charge_busy = 1;
     642             : 
     643           0 :   if( FD_LIKELY( poh->hashcnt<target_hashcnt ) ) {
     644           0 :     fd_sha256_hash_32_repeated( poh->hash, poh->hash, target_hashcnt-poh->hashcnt );
     645           0 :     poh->hashcnt = target_hashcnt;
     646           0 :   }
     647             : 
     648           0 :   if( FD_UNLIKELY( poh->hashcnt==poh->hashcnt_per_slot ) ) {
     649           0 :     poh->slot++;
     650           0 :     poh->hashcnt = 0UL;
     651           0 :   }
     652             : 
     653           0 :   switch( poh->state ) {
     654           0 :     case STATE_LEADER: {
     655           0 :       if( FD_UNLIKELY( !(poh->hashcnt%poh->hashcnt_per_tick) ) ) {
     656             :         /* We ticked while leader... send an empty microblock (a tick)
     657             :            to the shred tile. */
     658           0 :         publish_tick( poh, stem, poh->hash, 0 );
     659           0 :       }
     660           0 :       if( FD_UNLIKELY( poh->slot>poh->next_leader_slot ) ) {
     661             :         /* We ticked while leader and are no longer leader... transition
     662             :            the state machine. */
     663           0 :         FD_TEST( !max_remaining_microblocks );
     664           0 :         FD_LOG_INFO(( "fd_poh_ticked_outof_leader(slot=%lu)", poh->slot-1UL ));
     665           0 :         transition_to_follower( poh, stem, 1 );
     666           0 :         poh->state = STATE_WAITING_FOR_RESET;
     667           0 :       }
     668           0 :       break;
     669           0 :     }
     670           0 :     case STATE_WAITING_FOR_SLOT:
     671           0 :     case STATE_FOLLOWER: {
     672           0 :       if( FD_UNLIKELY( !(poh->hashcnt%poh->hashcnt_per_tick ) && poh->next_leader_slot!=ULONG_MAX ) ) {
     673             :         /* We finished a tick while not leader... save the current hash
     674             :            so it can be played back into the bank when we become the
     675             :            leader.
     676             : 
     677             :            If next_leader_slot is ULONG_MAX, we have no upcoming leader
     678             :            slot and these tick hashes will never be published, so we
     679             :            skip storing them. */
     680           0 :         ulong tick_idx = (poh->slot*poh->ticks_per_slot+poh->hashcnt/poh->hashcnt_per_tick)%MAX_SKIPPED_TICKS;
     681           0 :         fd_memcpy( poh->skipped_tick_hashes[ tick_idx ], poh->hash, 32UL );
     682             : 
     683           0 :         ulong initial_tick_idx = (poh->last_slot*poh->ticks_per_slot+poh->last_hashcnt/poh->hashcnt_per_tick)%MAX_SKIPPED_TICKS;
     684           0 :         if( FD_UNLIKELY( tick_idx==initial_tick_idx ) ) FD_LOG_ERR(( "Too many skipped ticks from slot %lu to slot %lu, chain must halt", poh->last_slot, poh->slot ));
     685           0 :       }
     686             : 
     687           0 :       FD_TEST( poh->slot<=poh->next_leader_slot );
     688           0 :       if( FD_UNLIKELY( poh->slot==poh->next_leader_slot ) ) {
     689             :         /* We ticked while not leader and are now leader... transition
     690             :            the state machine. */
     691           0 :         if( FD_LIKELY( poh->state==STATE_FOLLOWER ) ) poh->state = STATE_WAITING_FOR_BANK;
     692           0 :         else                                          poh->state = STATE_LEADER;
     693           0 :       }
     694           0 :       break;
     695           0 :     }
     696           0 :     default: {
     697           0 :       break;
     698           0 :     }
     699           0 :   }
     700           0 : }
     701             : 
     702             : static void
     703             : publish_microblock( fd_poh_t *          poh,
     704             :                     fd_stem_context_t * stem,
     705             :                     ulong               slot,
     706             :                     ulong               hashcnt_delta,
     707             :                     ulong               txn_cnt,
     708           0 :                     fd_txn_p_t const *  txns ) {
     709           0 :   uchar * dst = (uchar *)fd_chunk_to_laddr( poh->shred_out->mem, poh->shred_out->chunk );
     710           0 :   FD_TEST( slot>=poh->reset_slot );
     711           0 :   fd_entry_batch_meta_t * meta = (fd_entry_batch_meta_t *)dst;
     712           0 :   meta->parent_offset = 1UL+slot-poh->reset_slot;
     713           0 :   meta->reference_tick = (poh->hashcnt/poh->hashcnt_per_tick) % poh->ticks_per_slot;
     714           0 :   meta->block_complete = !poh->hashcnt;
     715             : 
     716           0 :   meta->parent_block_id_valid = 1;
     717           0 :   fd_memcpy( meta->parent_block_id, poh->completed_block_id, 32UL );
     718             : 
     719           0 :   dst += sizeof(fd_entry_batch_meta_t);
     720           0 :   fd_entry_batch_header_t * header = (fd_entry_batch_header_t *)dst;
     721           0 :   header->hashcnt_delta = hashcnt_delta;
     722           0 :   fd_memcpy( header->hash, poh->hash, 32UL );
     723             : 
     724           0 :   dst += sizeof(fd_entry_batch_header_t);
     725           0 :   ulong payload_sz = 0UL;
     726           0 :   ulong included_txn_cnt = 0UL;
     727           0 :   for( ulong i=0UL; i<txn_cnt; i++ ) {
     728           0 :     fd_txn_p_t const * txn = txns + i;
     729           0 :     if( FD_UNLIKELY( !(txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS) ) ) continue;
     730             : 
     731           0 :     fd_memcpy( dst, txn->payload, txn->payload_sz );
     732           0 :     payload_sz += txn->payload_sz;
     733           0 :     dst        += txn->payload_sz;
     734           0 :     included_txn_cnt++;
     735           0 :   }
     736           0 :   header->txn_cnt = included_txn_cnt;
     737             : 
     738             :   /* We always have credits to publish here, because we have a burst
     739             :      value of 3 credits, and at most we will publish_tick() once and
     740             :      then publish_became_leader() once, leaving one credit here to
     741             :      publish the microblock. */
     742           0 :   ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
     743           0 :   ulong sz = sizeof(fd_entry_batch_meta_t)+sizeof(fd_entry_batch_header_t)+payload_sz;
     744           0 :   ulong new_sig = fd_disco_poh_sig( slot, POH_PKT_TYPE_MICROBLOCK, 0UL );
     745           0 :   fd_stem_publish( stem, poh->shred_out->idx, new_sig, poh->shred_out->chunk, sz, 0UL, 0UL, tspub );
     746           0 :   poh->shred_out->chunk = fd_dcache_compact_next( poh->shred_out->chunk, sz, poh->shred_out->chunk0, poh->shred_out->wmark );
     747           0 : }
     748             : 
     749             : void
     750             : fd_poh1_mixin( fd_poh_t *                         poh,
     751             :                fd_stem_context_t *                stem,
     752             :                ulong                              slot,
     753             :                uchar const *                      hash,
     754             :                ulong                              txn_cnt,
     755             :                fd_txn_p_t const *                 txns,
     756           0 :                fd_leader_txn_timing_rec_t const * timing ) {
     757           0 :   if( FD_UNLIKELY( slot!=poh->next_leader_slot || slot!=poh->slot ) ) {
     758           0 :     FD_LOG_ERR(( "packed too early or late slot=%lu, current_slot=%lu", slot, poh->slot ));
     759           0 :   }
     760           0 :   if( FD_UNLIKELY( (poh->hashcnt%poh->hashcnt_per_tick)==(poh->hashcnt_per_tick-1UL) ) ) FD_LOG_CRIT(( "a tick will be skipped due to hashcnt %lu hashcnt_per_tick %lu", poh->hashcnt, poh->hashcnt_per_tick ));
     761             : 
     762           0 :   FD_TEST( poh->state==STATE_LEADER );
     763           0 :   FD_TEST( poh->microblocks_lower_bound<poh->max_microblocks_per_slot );
     764           0 :   poh->microblocks_lower_bound += 1UL;
     765             : 
     766           0 :   ulong executed_txn_cnt = 0UL;
     767           0 :   for( ulong i=0UL; i<txn_cnt; i++ ) {
     768             :     /* It's important that we check if a transaction is included in the
     769             :        block with FD_TXN_P_FLAGS_EXECUTE_SUCCESS since
     770             :        actual_consumed_cus may have a nonzero value for excluded
     771             :        transactions used for monitoring purposes */
     772           0 :     if( FD_LIKELY( txns[ i ].flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS ) ) {
     773           0 :       executed_txn_cnt++;
     774           0 :     }
     775           0 :   }
     776             : 
     777             :   /* We don't publish transactions that fail to execute.  If all the
     778             :      transactions failed to execute, the microblock would be empty,
     779             :      causing agave to think it's a tick and complain.  Instead, we just
     780             :      skip the microblock and don't hash or update the hashcnt. */
     781           0 :   if( FD_UNLIKELY( !executed_txn_cnt ) ) return;
     782             : 
     783           0 :   if( FD_LIKELY( poh->timing_tables ) ) {
     784           0 :     fd_leader_txn_timing_table_t * table = fd_leader_txn_timing_table( poh->timing_tables, poh->timing_table_idx, poh->timing_table_max );
     785           0 :     long poh_mixed_ticks = fd_tickcount();
     786           0 :     for( ulong i=0UL; i<txn_cnt; i++ ) {
     787           0 :       if( FD_UNLIKELY( !(txns[ i ].flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS) ) ) continue;
     788           0 :       if( FD_UNLIKELY( table->cnt>=poh->timing_table_max ) ) break;
     789             : 
     790           0 :       fd_leader_txn_timing_rec_t * rec = &table->rec[ table->cnt++ ];
     791           0 :       *rec = *timing;
     792           0 :       rec->received_ns     = txns[ i ].first_seen_nanos;
     793           0 :       rec->poh_mixed_ticks = poh_mixed_ticks;
     794           0 :     }
     795           0 :   }
     796             : 
     797           0 :   uchar data[ 64 ];
     798           0 :   fd_memcpy( data, poh->hash, 32UL );
     799           0 :   fd_memcpy( data+32UL, hash, 32UL );
     800           0 :   fd_sha256_hash( data, 64UL, poh->hash );
     801             : 
     802           0 :   poh->hashcnt++;
     803           0 :   FD_TEST( poh->hashcnt>poh->last_hashcnt );
     804           0 :   ulong hashcnt_delta = poh->hashcnt - poh->last_hashcnt;
     805             : 
     806             :   /* The hashing loop above will never leave us exactly one away from
     807             :      crossing a tick boundary, so this increment will never cause the
     808             :      current tick (or the slot) to change, except in low power mode
     809             :      for development, in which case we do need to register the tick
     810             :      with the leader bank.  We don't need to publish the tick since
     811             :      sending the microblock below is the publishing action. */
     812           0 :   if( FD_UNLIKELY( !(poh->hashcnt%poh->hashcnt_per_slot ) ) ) {
     813           0 :     poh->slot++;
     814           0 :     poh->hashcnt = 0UL;
     815           0 :   }
     816             : 
     817           0 :   poh->last_slot    = poh->slot;
     818           0 :   poh->last_hashcnt = poh->hashcnt;
     819             : 
     820           0 :   if( FD_UNLIKELY( !(poh->hashcnt%poh->hashcnt_per_tick ) ) ) {
     821           0 :     if( FD_UNLIKELY( poh->slot>poh->next_leader_slot ) ) {
     822             :       /* We ticked while leader and are no longer leader... transition
     823             :          the state machine. */
     824           0 :       transition_to_follower( poh, stem, 1 );
     825           0 :       poh->state = STATE_WAITING_FOR_RESET;
     826           0 :     }
     827           0 :   }
     828             : 
     829           0 :   publish_microblock( poh, stem, slot, hashcnt_delta, txn_cnt, txns );
     830           0 : }

Generated by: LCOV version 1.14