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

Generated by: LCOV version 1.14