LCOV - code coverage report
Current view: top level - discof/poh - fd_poh.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 315 0.0 %
Date: 2025-12-06 04:45:29 Functions: 0 16 0.0 %

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

Generated by: LCOV version 1.14