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