LCOV - code coverage report
Current view: top level - discof/poh - fd_poh.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 308 0.0 %
Date: 2025-09-19 04:41:14 Functions: 0 14 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             :               ulong               hashcnt_per_tick,        /* The hashcnt per tick of the bank that completed */
     180             :               ulong               ticks_per_slot,
     181             :               ulong               tick_duration_ns,
     182             :               ulong               completed_slot,          /* The slot that successfully produced a block */
     183             :               uchar const *       completed_blockhash,     /* The hash of the last tick in the produced block */
     184             :               ulong               next_leader_slot,        /* The next slot where this node will be leader */
     185             :               ulong               max_microblocks_in_slot, /* The maximum number of microblocks that may appear in a slot */
     186           0 :               uchar const *       completed_block_id       /* The block id of the completed block */)  {
     187           0 :   memcpy( poh->reset_hash, completed_blockhash, 32UL );
     188           0 :   memcpy( poh->hash, completed_blockhash, 32UL );
     189           0 :   memcpy( poh->completed_block_id, completed_block_id, 32UL );
     190           0 :   poh->slot                     = completed_slot+1UL;
     191           0 :   poh->hashcnt                  = 0UL;
     192           0 :   poh->last_slot                = poh->slot;
     193           0 :   poh->last_hashcnt             = 0UL;
     194           0 :   poh->reset_slot               = poh->slot;
     195           0 :   poh->next_leader_slot         = next_leader_slot;
     196           0 :   poh->max_microblocks_per_slot = max_microblocks_in_slot;
     197             : 
     198           0 :   if( FD_UNLIKELY( poh->state==STATE_UNINIT ) ) {
     199           0 :     poh->tick_duration_ns = tick_duration_ns;
     200           0 :     poh->ticks_per_slot   = ticks_per_slot;
     201           0 :     poh->state = STATE_FOLLOWER;
     202           0 :   } else {
     203           0 :     FD_TEST( tick_duration_ns==poh->tick_duration_ns );
     204           0 :     FD_TEST( ticks_per_slot==poh->ticks_per_slot );
     205           0 :   }
     206           0 :   update_hashes_per_tick( poh, hashcnt_per_tick );
     207             : 
     208             :   /* When we reset, we need to allow PoH to tick freely again rather
     209             :      than being constrained.  If we are leader after the reset, this
     210             :      is OK because we won't tick until we get a bank, and the lower
     211             :      bound will be reset with the value from the bank. */
     212           0 :   poh->microblocks_lower_bound = poh->max_microblocks_per_slot;
     213             : 
     214           0 :   if( FD_UNLIKELY( poh->state!=STATE_FOLLOWER ) ) transition_to_follower( poh, stem, 0 );
     215           0 :   if( FD_UNLIKELY( poh->slot==poh->next_leader_slot ) ) poh->state = STATE_WAITING_FOR_BANK;
     216           0 : }
     217             : 
     218             : void
     219             : fd_poh_begin_leader( fd_poh_t * poh,
     220             :                      ulong      slot,
     221             :                      ulong      hashcnt_per_tick,
     222             :                      ulong      ticks_per_slot,
     223             :                      ulong      tick_duration_ns,
     224           0 :                      ulong      max_microblocks_in_slot ) {
     225           0 :   FD_TEST( poh->state==STATE_FOLLOWER || poh->state==STATE_WAITING_FOR_BANK );
     226           0 :   FD_TEST( slot==poh->next_leader_slot );
     227             : 
     228           0 :   poh->max_microblocks_per_slot = max_microblocks_in_slot;
     229             : 
     230           0 :   FD_TEST( tick_duration_ns==poh->tick_duration_ns );
     231           0 :   FD_TEST( ticks_per_slot==poh->ticks_per_slot );
     232           0 :   update_hashes_per_tick( poh, hashcnt_per_tick );
     233             : 
     234           0 :   if( FD_LIKELY( poh->state==STATE_FOLLOWER ) ) poh->state = STATE_WAITING_FOR_SLOT;
     235           0 :   else                                          poh->state = STATE_LEADER;
     236             : 
     237           0 :   poh->slot_done               = 0;
     238           0 :   poh->microblocks_lower_bound = 0UL;
     239             : 
     240           0 :   FD_LOG_INFO(( "begin_leader(slot=%lu, last_slot=%lu, last_hashcnt=%lu)", slot, poh->last_slot, poh->last_hashcnt ));
     241           0 : }
     242             : 
     243             : int
     244           0 : fd_poh_have_leader_bank( fd_poh_t const * poh ) {
     245           0 :   return poh->state==STATE_WAITING_FOR_SLOT || poh->state==STATE_LEADER;
     246           0 : }
     247             : 
     248             : void
     249             : fd_poh_done_packing( fd_poh_t * poh,
     250           0 :                      ulong      microblocks_in_slot ) {
     251           0 :   FD_TEST( poh->state==STATE_LEADER );
     252           0 :   FD_LOG_INFO(( "done_packing(slot=%lu,seen_microblocks=%lu,microblocks_in_slot=%lu)",
     253           0 :                 poh->slot,
     254           0 :                 poh->microblocks_lower_bound,
     255           0 :                 microblocks_in_slot ));
     256           0 :   FD_TEST( poh->microblocks_lower_bound==microblocks_in_slot );
     257           0 :   FD_TEST( poh->microblocks_lower_bound<=poh->max_microblocks_per_slot );
     258           0 :   poh->slot_done = 1;
     259           0 :   poh->microblocks_lower_bound += poh->max_microblocks_per_slot - microblocks_in_slot;
     260           0 :   FD_TEST( poh->microblocks_lower_bound==poh->max_microblocks_per_slot );
     261           0 : }
     262             : 
     263             : static void
     264             : publish_tick( fd_poh_t *          poh,
     265             :               fd_stem_context_t * stem,
     266             :               uchar               hash[ static 32 ],
     267           0 :               int                 is_skipped ) {
     268           0 :   ulong hashcnt = poh->hashcnt_per_tick*(1UL+(poh->last_hashcnt/poh->hashcnt_per_tick));
     269             : 
     270           0 :   uchar * dst = (uchar *)fd_chunk_to_laddr( poh->shred_out->mem, poh->shred_out->chunk );
     271             : 
     272           0 :   FD_TEST( poh->last_slot>=poh->reset_slot );
     273           0 :   fd_entry_batch_meta_t * meta = (fd_entry_batch_meta_t *)dst;
     274           0 :   if( FD_UNLIKELY( is_skipped ) ) {
     275             :     /* We are publishing ticks for a skipped slot, the reference tick
     276             :        and block complete flags should always be zero. */
     277           0 :     meta->reference_tick = 0UL;
     278           0 :     meta->block_complete = 0;
     279           0 :   } else {
     280           0 :     meta->reference_tick = hashcnt/poh->hashcnt_per_tick;
     281           0 :     meta->block_complete = hashcnt==poh->hashcnt_per_slot;
     282           0 :   }
     283             : 
     284           0 :   meta->parent_block_id_valid = 1;
     285           0 :   fd_memcpy( meta->parent_block_id, poh->completed_block_id, 32UL );
     286             : 
     287           0 :   ulong slot = fd_ulong_if( meta->block_complete, poh->slot-1UL, poh->slot );
     288           0 :   meta->parent_offset = 1UL+slot-poh->reset_slot;
     289             : 
     290           0 :   FD_TEST( hashcnt>poh->last_hashcnt );
     291           0 :   ulong hash_delta = hashcnt-poh->last_hashcnt;
     292             : 
     293           0 :   dst += sizeof(fd_entry_batch_meta_t);
     294           0 :   fd_entry_batch_header_t * tick = (fd_entry_batch_header_t *)dst;
     295           0 :   tick->hashcnt_delta = hash_delta;
     296           0 :   fd_memcpy( tick->hash, hash, 32UL );
     297           0 :   tick->txn_cnt = 0UL;
     298             : 
     299           0 :   ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
     300           0 :   ulong sz = sizeof(fd_entry_batch_meta_t)+sizeof(fd_entry_batch_header_t);
     301           0 :   ulong sig = fd_disco_poh_sig( slot, POH_PKT_TYPE_MICROBLOCK, 0UL );
     302           0 :   fd_stem_publish( stem, poh->shred_out->idx, sig, poh->shred_out->chunk, sz, 0UL, 0UL, tspub );
     303           0 :   poh->shred_out->chunk = fd_dcache_compact_next( poh->shred_out->chunk, sz, poh->shred_out->chunk0, poh->shred_out->wmark );
     304             : 
     305           0 :   if( FD_UNLIKELY( hashcnt==poh->hashcnt_per_slot ) ) {
     306           0 :     poh->last_slot++;
     307           0 :     poh->last_hashcnt = 0UL;
     308           0 :   } else {
     309           0 :     poh->last_hashcnt = hashcnt;
     310           0 :   }
     311           0 : }
     312             : 
     313             : void
     314             : fd_poh_advance( fd_poh_t *          poh,
     315             :                 fd_stem_context_t * stem,
     316             :                 int *               opt_poll_in,
     317           0 :                 int *               charge_busy ) {
     318           0 :   if( FD_UNLIKELY( poh->state==STATE_UNINIT || poh->state==STATE_WAITING_FOR_RESET ) ) return;
     319           0 :   if( FD_UNLIKELY( poh->state==STATE_WAITING_FOR_BANK ) ) {
     320             :     /* If we are the leader, but we didn't yet learn what the leader
     321             :        bank object is from the replay tile, do not do any hashing. */
     322           0 :     return;
     323           0 :   }
     324             : 
     325             :   /* If we have skipped ticks pending because we skipped some slots to
     326             :      become leader, register them now one at a time. */
     327           0 :   if( FD_UNLIKELY( poh->state==STATE_LEADER && poh->last_slot<poh->slot ) ) {
     328           0 :     ulong publish_hashcnt = poh->last_hashcnt+poh->hashcnt_per_tick;
     329           0 :     ulong tick_idx = (poh->last_slot*poh->ticks_per_slot+publish_hashcnt/poh->hashcnt_per_tick)%MAX_SKIPPED_TICKS;
     330             : 
     331           0 :     publish_tick( poh, stem, poh->skipped_tick_hashes[ tick_idx ], 1 );
     332             : 
     333             :     /* If we are catching up now and publishing a bunch of skipped
     334             :        ticks, we do not want to process any incoming microblocks until
     335             :        all the skipped ticks have been published out; otherwise we would
     336             :        intersperse skipped tick messages with microblocks. */
     337           0 :     *opt_poll_in = 0;
     338           0 :     *charge_busy = 1;
     339           0 :     return;
     340           0 :   }
     341             : 
     342           0 :   int low_power_mode = poh->hashcnt_per_tick==1UL;
     343             : 
     344             :   /* If we are the leader, always leave enough capacity in the slot so
     345             :      that we can mixin any potential microblocks still coming from the
     346             :      pack tile for this slot. */
     347           0 :   ulong max_remaining_microblocks = poh->max_microblocks_per_slot - poh->microblocks_lower_bound;
     348             : 
     349             :   /* We don't want to tick over (finish) the slot until pack tell us
     350             :      it's done.  If we're waiting on pack, them we clamp to [0, 1]. */
     351           0 :   if( FD_LIKELY( !poh->slot_done && poh->state==STATE_LEADER ) ) max_remaining_microblocks = fd_ulong_max( fd_ulong_min( 1UL, max_remaining_microblocks ), max_remaining_microblocks );
     352             : 
     353             :   /* With hashcnt_per_tick hashes per tick, we actually get
     354             :      hashcnt_per_tick-1 chances to mixin a microblock.  For each tick
     355             :      span that we need to reserve, we also need to reserve the hashcnt
     356             :      for the tick, hence the +
     357             :      max_remaining_microblocks/(hashcnt_per_tick-1) rounded up.
     358             : 
     359             :      However, if hashcnt_per_tick is 1 because we're in low power mode,
     360             :      this should probably just be max_remaining_microblocks. */
     361           0 :   ulong max_remaining_ticks_or_microblocks = max_remaining_microblocks;
     362           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);
     363             : 
     364           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 );
     365             : 
     366           0 :   ulong min_hashcnt = poh->hashcnt;
     367             : 
     368           0 :   if( FD_LIKELY( !low_power_mode ) ) {
     369             :     /* Recall that there are two kinds of events that will get published
     370             :        to the shredder,
     371             : 
     372             :          (a) Ticks. These occur every 62,500 (hashcnt_per_tick) hashcnts,
     373             :              and there will be 64 (ticks_per_slot) of them in each slot.
     374             : 
     375             :              Ticks must not have any transactions mixed into the hash.
     376             :              This is not strictly needed in theory, but is required by the
     377             :              current consensus protocol.  They get published here in
     378             :              after_credit.
     379             : 
     380             :          (b) Microblocks.  These can occur at any other hashcnt, as long
     381             :              as it is not a tick.  Microblocks cannot be empty, and must
     382             :              have at least one transactions mixed in.  These get
     383             :              published in after_frag.
     384             : 
     385             :        If hashcnt_per_tick is 1, then we are in low power mode and the
     386             :        following does not apply, since we can mix in transactions at any
     387             :        time.
     388             : 
     389             :        In the normal, non-low-power mode, though, we have to be careful
     390             :        to make sure that we do not publish microblocks on tick
     391             :        boundaries.  To do that, we need to obey two rules:
     392             :          (i)  after_credit must not leave hashcnt one before a tick
     393             :               boundary
     394             :          (ii) if after_credit begins one before a tick boundary, it must
     395             :               advance hashcnt and publish the tick
     396             : 
     397             :        There's some interplay between min_hashcnt and restricted_hashcnt
     398             :        here, and we need to show that there's always a value of
     399             :        target_hashcnt we can pick such that
     400             :            min_hashcnt <= target_hashcnt <= restricted_hashcnt.
     401             :        We'll prove this by induction for current_slot==0 and
     402             :        is_leader==true, since all other slots should be the same.
     403             : 
     404             :        Let m_j and r_j be the min_hashcnt and restricted_hashcnt
     405             :        (respectively) for the jth call to after_credit in a slot.  We
     406             :        want to show that for all values of j, it's possible to pick a
     407             :        value h_j, the value of target_hashcnt for the jth call to
     408             :        after_credit (which is also the value of hashcnt after
     409             :        after_credit has completed) such that m_j<=h_j<=r_j.
     410             : 
     411             :        Additionally, let T be hashcnt_per_tick and N be ticks_per_slot.
     412             : 
     413             :        Starting with the base case, j==0.  m_j=0, and
     414             :          r_0 = N*T - max_microblocks_per_slot
     415             :                    - ceil(max_microblocks_per_slot/(T-1)).
     416             : 
     417             :        This is monotonic decreasing in max_microblocks_per_slot, so it
     418             :        achieves its minimum when max_microblocks_per_slot is its
     419             :        maximum.
     420             :            r_0 >= N*T - N*(T-1) - ceil( (N*(T-1))/(T-1))
     421             :                 = N*T - N*(T-1)-N = 0.
     422             :        Thus, m_0 <= r_0, as desired.
     423             : 
     424             : 
     425             : 
     426             :        Then, for the inductive step, assume there exists h_j such that
     427             :        m_j<=h_j<=r_j, and we want to show that there exists h_{j+1},
     428             :        which is the same as showing m_{j+1}<=r_{j+1}.
     429             : 
     430             :        Let a_j be 1 if we had a microblock immediately following the jth
     431             :        call to after_credit, and 0 otherwise.  Then hashcnt at the start
     432             :        of the (j+1)th call to after_frag is h_j+a_j.
     433             :        Also, set b_{j+1}=1 if we are in the case covered by rule (ii)
     434             :        above during the (j+1)th call to after_credit, i.e. if
     435             :        (h_j+a_j)%T==T-1.  Thus, m_{j+1} = h_j + a_j + b_{j+1}.
     436             : 
     437             :        If we received an additional microblock, then
     438             :        max_remaining_microblocks goes down by 1, and
     439             :        max_remaining_ticks_or_microblocks goes down by either 1 or 2,
     440             :        which means restricted_hashcnt goes up by either 1 or 2.  In
     441             :        particular, it goes up by 2 if the new value of
     442             :        max_remaining_microblocks (at the start of the (j+1)th call to
     443             :        after_credit) is congruent to 0 mod T-1.  Let b'_{j+1} be 1 if
     444             :        this condition is met and 0 otherwise.  If we receive a
     445             :        done_packing message, restricted_hashcnt can go up by more, but
     446             :        we can ignore that case, since it is less restrictive.
     447             :        Thus, r_{j+1}=r_j+a_j+b'_{j+1}.
     448             : 
     449             :        If h_j < r_j (strictly less), then h_j+a_j < r_j+a_j.  And thus,
     450             :        since b_{j+1}<=b'_{j+1}+1, just by virtue of them both being
     451             :        binary,
     452             :              h_j + a_j + b_{j+1} <  r_j + a_j + b'_{j+1} + 1,
     453             :        which is the same (for integers) as
     454             :              h_j + a_j + b_{j+1} <= r_j + a_j + b'_{j+1},
     455             :                  m_{j+1}         <= r_{j+1}
     456             : 
     457             :        On the other hand, if h_j==r_j, this is easy unless b_{j+1}==1,
     458             :        which can also only happen if a_j==1.  Then (h_j+a_j)%T==T-1,
     459             :        which means there's an integer k such that
     460             : 
     461             :              h_j+a_j==(ticks_per_slot-k)*T-1
     462             :              h_j    ==ticks_per_slot*T -  k*(T-1)-1  - k-1
     463             :                     ==ticks_per_slot*T - (k*(T-1)+1) - ceil( (k*(T-1)+1)/(T-1) )
     464             : 
     465             :        Since h_j==r_j in this case, and
     466             :        r_j==(ticks_per_slot*T) - max_remaining_microblocks_j - ceil(max_remaining_microblocks_j/(T-1)),
     467             :        we can see that the value of max_remaining_microblocks at the
     468             :        start of the jth call to after_credit is k*(T-1)+1.  Again, since
     469             :        a_j==1, then the value of max_remaining_microblocks at the start
     470             :        of the j+1th call to after_credit decreases by 1 to k*(T-1),
     471             :        which means b'_{j+1}=1.
     472             : 
     473             :        Thus, h_j + a_j + b_{j+1} == r_j + a_j + b'_{j+1}, so, in
     474             :        particular, h_{j+1}<=r_{j+1} as desired. */
     475           0 :      min_hashcnt += (ulong)(min_hashcnt%poh->hashcnt_per_tick == (poh->hashcnt_per_tick-1UL)); /* add b_{j+1}, enforcing rule (ii) */
     476           0 :   }
     477             :   /* Now figure out how many hashes are needed to "catch up" the hash
     478             :      count to the current system clock, and clamp it to the allowed
     479             :      range. */
     480           0 :   long now = fd_log_wallclock();
     481           0 :   ulong target_hashcnt;
     482           0 :   if( FD_LIKELY( poh->state==STATE_FOLLOWER ||poh->state==STATE_WAITING_FOR_SLOT ) ) {
     483           0 :     target_hashcnt = (ulong)((double)(now - poh->reset_slot_start_ns) / poh->hashcnt_duration_ns) - (poh->slot-poh->reset_slot)*poh->hashcnt_per_slot;
     484           0 :   } else {
     485           0 :     FD_TEST( poh->state==STATE_LEADER );
     486           0 :     target_hashcnt = (ulong)((double)(now - poh->leader_slot_start_ns) / poh->hashcnt_duration_ns);
     487           0 :   }
     488             :   /* Clamp to [min_hashcnt, restricted_hashcnt] as above */
     489           0 :   target_hashcnt = fd_ulong_max( fd_ulong_min( target_hashcnt, restricted_hashcnt ), min_hashcnt );
     490             : 
     491             :   /* The above proof showed that it was always possible to pick a value
     492             :      of target_hashcnt, but we still have a lot of freedom in how to
     493             :      pick it.  It simplifies the code a lot if we don't keep going after
     494             :      a tick in this function.  In particular, we want to publish at most
     495             :      1 tick in this call, since otherwise we could consume infinite
     496             :      credits to publish here.  The credits are set so that we should
     497             :      only ever publish one tick during this loop.  Also, all the extra
     498             :      stuff (leader transitions, publishing ticks, etc.) we have to do
     499             :      happens at tick boundaries, so this lets us consolidate all those
     500             :      cases.
     501             : 
     502             :      Mathematically, since the current value of hashcnt is h_j+a_j, the
     503             :      next tick (advancing a full tick if we're currently at a tick) is
     504             :      t_{j+1} = T*(floor( (h_j+a_j)/T )+1).  We need to show that if we set
     505             :      h'_{j+1} = min( h_{j+1}, t_{j+1} ), it is still valid.
     506             : 
     507             :      First, h'_{j+1} <= h_{j+1} <= r_{j+1}, so we're okay in that
     508             :      direction.
     509             : 
     510             :      Next, observe that t_{j+1}>=h_j + a_j + 1, and recall that b_{j+1}
     511             :      is 0 or 1. So then,
     512             :                     t_{j+1} >= h_j+a_j+b_{j+1} = m_{j+1}.
     513             : 
     514             :      We know h_{j+1) >= m_{j+1} from before, so then h'_{j+1} >=
     515             :      m_{j+1}, as desired. */
     516             : 
     517           0 :   ulong next_tick_hashcnt = poh->hashcnt_per_tick * (1UL+(poh->hashcnt/poh->hashcnt_per_tick));
     518           0 :   target_hashcnt = fd_ulong_min( target_hashcnt, next_tick_hashcnt );
     519             : 
     520             :   /* We still need to enforce rule (i). We know that min_hashcnt%T !=
     521             :      T-1 because of rule (ii).  That means that if target_hashcnt%T ==
     522             :      T-1 at this point, target_hashcnt > min_hashcnt (notice the
     523             :      strict), so target_hashcnt-1 >= min_hashcnt and is thus still a
     524             :      valid choice for target_hashcnt. */
     525           0 :   target_hashcnt -= (ulong)( (!low_power_mode) & ((target_hashcnt%poh->hashcnt_per_tick)==(poh->hashcnt_per_tick-1UL)) );
     526             : 
     527           0 :   FD_TEST( target_hashcnt >= poh->hashcnt       );
     528           0 :   FD_TEST( target_hashcnt >= min_hashcnt        );
     529           0 :   FD_TEST( target_hashcnt <= restricted_hashcnt );
     530             : 
     531           0 :   if( FD_UNLIKELY( poh->hashcnt==target_hashcnt ) ) return; /* Nothing to do, don't publish a tick twice */
     532             : 
     533           0 :   *charge_busy = 1;
     534             : 
     535           0 :   if( FD_LIKELY( poh->hashcnt<target_hashcnt ) ) {
     536           0 :     fd_sha256_hash_32_repeated( poh->hash, poh->hash, target_hashcnt-poh->hashcnt );
     537           0 :     poh->hashcnt = target_hashcnt;
     538           0 :   }
     539             : 
     540           0 :   if( FD_UNLIKELY( poh->hashcnt==poh->hashcnt_per_slot ) ) {
     541           0 :     poh->slot++;
     542           0 :     poh->hashcnt = 0UL;
     543           0 :   }
     544             : 
     545           0 :   switch( poh->state ) {
     546           0 :     case STATE_LEADER: {
     547           0 :       if( FD_UNLIKELY( !(poh->hashcnt%poh->hashcnt_per_tick) ) ) {
     548             :         /* We ticked while leader... send an empty microblock (a tick)
     549             :            to the shred tile. */
     550           0 :         publish_tick( poh, stem, poh->hash, 0 );
     551           0 :       }
     552           0 :       if( FD_UNLIKELY( poh->slot>poh->next_leader_slot ) ) {
     553             :         /* We ticked while leader and are no longer leader... transition
     554             :            the state machine. */
     555           0 :         FD_TEST( !max_remaining_microblocks );
     556           0 :         transition_to_follower( poh, stem, 1 );
     557           0 :         poh->state = STATE_WAITING_FOR_RESET;
     558           0 :       }
     559           0 :       break;
     560           0 :     }
     561           0 :     case STATE_WAITING_FOR_SLOT:
     562           0 :     case STATE_FOLLOWER: {
     563           0 :       if( FD_UNLIKELY( !(poh->hashcnt%poh->hashcnt_per_tick ) ) ) {
     564             :         /* We finished a tick while not leader... save the current hash
     565             :            so it can be played back into the bank when we become the
     566             :            leader. */
     567           0 :         ulong tick_idx = (poh->slot*poh->ticks_per_slot+poh->hashcnt/poh->hashcnt_per_tick)%MAX_SKIPPED_TICKS;
     568           0 :         fd_memcpy( poh->skipped_tick_hashes[ tick_idx ], poh->hash, 32UL );
     569             : 
     570           0 :         ulong initial_tick_idx = (poh->last_slot*poh->ticks_per_slot+poh->last_hashcnt/poh->hashcnt_per_tick)%MAX_SKIPPED_TICKS;
     571           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 ));
     572           0 :       }
     573             : 
     574           0 :       FD_TEST( poh->slot<=poh->next_leader_slot );
     575           0 :       if( FD_UNLIKELY( poh->slot==poh->next_leader_slot ) ) {
     576             :         /* We ticked while not leader and are now leader... transition
     577             :            the state machine. */
     578           0 :         if( FD_LIKELY( poh->state==STATE_FOLLOWER ) ) poh->state = STATE_WAITING_FOR_BANK;
     579           0 :         else                                          poh->state = STATE_LEADER;
     580           0 :       }
     581           0 :       break;
     582           0 :     }
     583           0 :     default: {
     584           0 :       break;
     585           0 :     }
     586           0 :   }
     587           0 : }
     588             : 
     589             : static void
     590             : publish_microblock( fd_poh_t *          poh,
     591             :                     fd_stem_context_t * stem,
     592             :                     ulong               slot,
     593             :                     ulong               hashcnt_delta,
     594             :                     ulong               txn_cnt,
     595           0 :                     fd_txn_p_t const *  txns ) {
     596           0 :   uchar * dst = (uchar *)fd_chunk_to_laddr( poh->shred_out->mem, poh->shred_out->chunk );
     597           0 :   FD_TEST( slot>=poh->reset_slot );
     598           0 :   fd_entry_batch_meta_t * meta = (fd_entry_batch_meta_t *)dst;
     599           0 :   meta->parent_offset = 1UL+slot-poh->reset_slot;
     600           0 :   meta->reference_tick = (poh->hashcnt/poh->hashcnt_per_tick) % poh->ticks_per_slot;
     601           0 :   meta->block_complete = !poh->hashcnt;
     602             : 
     603           0 :   meta->parent_block_id_valid = 1;
     604           0 :   fd_memcpy( meta->parent_block_id, poh->completed_block_id, 32UL );
     605             : 
     606           0 :   dst += sizeof(fd_entry_batch_meta_t);
     607           0 :   fd_entry_batch_header_t * header = (fd_entry_batch_header_t *)dst;
     608           0 :   header->hashcnt_delta = hashcnt_delta;
     609           0 :   fd_memcpy( header->hash, poh->hash, 32UL );
     610             : 
     611           0 :   dst += sizeof(fd_entry_batch_header_t);
     612           0 :   ulong payload_sz = 0UL;
     613           0 :   ulong included_txn_cnt = 0UL;
     614           0 :   for( ulong i=0UL; i<txn_cnt; i++ ) {
     615           0 :     fd_txn_p_t const * txn = txns + i;
     616           0 :     if( FD_UNLIKELY( !(txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS) ) ) continue;
     617             : 
     618           0 :     fd_memcpy( dst, txn->payload, txn->payload_sz );
     619           0 :     payload_sz += txn->payload_sz;
     620           0 :     dst        += txn->payload_sz;
     621           0 :     included_txn_cnt++;
     622           0 :   }
     623           0 :   header->txn_cnt = included_txn_cnt;
     624             : 
     625             :   /* We always have credits to publish here, because we have a burst
     626             :      value of 3 credits, and at most we will publish_tick() once and
     627             :      then publish_became_leader() once, leaving one credit here to
     628             :      publish the microblock. */
     629           0 :   ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
     630           0 :   ulong sz = sizeof(fd_entry_batch_meta_t)+sizeof(fd_entry_batch_header_t)+payload_sz;
     631           0 :   ulong new_sig = fd_disco_poh_sig( slot, POH_PKT_TYPE_MICROBLOCK, 0UL );
     632           0 :   fd_stem_publish( stem, poh->shred_out->idx, new_sig, poh->shred_out->chunk, sz, 0UL, 0UL, tspub );
     633           0 :   poh->shred_out->chunk = fd_dcache_compact_next( poh->shred_out->chunk, sz, poh->shred_out->chunk0, poh->shred_out->wmark );
     634           0 : }
     635             : 
     636             : void
     637             : fd_poh1_mixin( fd_poh_t *          poh,
     638             :                fd_stem_context_t * stem,
     639             :                ulong               slot,
     640             :                uchar const *       hash,
     641             :                ulong               txn_cnt,
     642           0 :                fd_txn_p_t const *  txns ) {
     643           0 :   if( FD_UNLIKELY( slot!=poh->next_leader_slot || slot!=poh->slot ) ) {
     644           0 :     FD_LOG_ERR(( "packed too early or late slot=%lu, current_slot=%lu", slot, poh->slot ));
     645           0 :   }
     646             : 
     647           0 :   FD_TEST( poh->state==STATE_LEADER );
     648           0 :   FD_TEST( poh->microblocks_lower_bound<poh->max_microblocks_per_slot );
     649           0 :   poh->microblocks_lower_bound += 1UL;
     650             : 
     651           0 :   ulong executed_txn_cnt = 0UL;
     652           0 :   for( ulong i=0UL; i<txn_cnt; i++ ) {
     653             :     /* It's important that we check if a transaction is included in the
     654             :        block with FD_TXN_P_FLAGS_EXECUTE_SUCCESS since
     655             :        actual_consumed_cus may have a nonzero value for excluded
     656             :        transactions used for monitoring purposes */
     657           0 :     if( FD_LIKELY( txns[ i ].flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS ) ) {
     658           0 :       executed_txn_cnt++;
     659           0 :     }
     660           0 :   }
     661             : 
     662             :   /* We don't publish transactions that fail to execute.  If all the
     663             :      transactions failed to execute, the microblock would be empty,
     664             :      causing agave to think it's a tick and complain.  Instead, we just
     665             :      skip the microblock and don't hash or update the hashcnt. */
     666           0 :   if( FD_UNLIKELY( !executed_txn_cnt ) ) return;
     667             : 
     668           0 :   uchar data[ 64 ];
     669           0 :   fd_memcpy( data, poh->hash, 32UL );
     670           0 :   fd_memcpy( data+32UL, hash, 32UL );
     671           0 :   fd_sha256_hash( data, 64UL, poh->hash );
     672             : 
     673           0 :   poh->hashcnt++;
     674           0 :   FD_TEST( poh->hashcnt>poh->last_hashcnt );
     675           0 :   ulong hashcnt_delta = poh->hashcnt - poh->last_hashcnt;
     676             : 
     677             :   /* The hashing loop above will never leave us exactly one away from
     678             :      crossing a tick boundary, so this increment will never cause the
     679             :      current tick (or the slot) to change, except in low power mode
     680             :      for development, in which case we do need to register the tick
     681             :      with the leader bank.  We don't need to publish the tick since
     682             :      sending the microblock below is the publishing action. */
     683           0 :   if( FD_UNLIKELY( !(poh->hashcnt%poh->hashcnt_per_slot ) ) ) {
     684           0 :     poh->slot++;
     685           0 :     poh->hashcnt = 0UL;
     686           0 :   }
     687             : 
     688           0 :   poh->last_slot    = poh->slot;
     689           0 :   poh->last_hashcnt = poh->hashcnt;
     690             : 
     691           0 :   if( FD_UNLIKELY( !(poh->hashcnt%poh->hashcnt_per_tick ) ) ) {
     692           0 :     if( FD_UNLIKELY( poh->slot>poh->next_leader_slot ) ) {
     693             :       /* We ticked while leader and are no longer leader... transition
     694             :          the state machine. */
     695           0 :       transition_to_follower( poh, stem, 1 );
     696           0 :       poh->state = STATE_WAITING_FOR_RESET;
     697           0 :     }
     698           0 :   }
     699             : 
     700           0 :   publish_microblock( poh, stem, slot, hashcnt_delta, txn_cnt, txns );
     701           0 : }

Generated by: LCOV version 1.14