Line data Source code
1 : #include "fd_poh_tile.h"
2 :
3 : #include "../metrics/fd_metrics.h"
4 :
5 : ulong
6 0 : fd_poh_tile_align( void ) {
7 0 : return 128UL;
8 0 : }
9 :
10 : ulong
11 0 : fd_poh_tile_footprint( void ) {
12 0 : ulong l = FD_LAYOUT_INIT;
13 0 : l = FD_LAYOUT_APPEND( l, alignof( fd_poh_tile_ctx_t ), sizeof( fd_poh_tile_ctx_t ) );
14 0 : l = FD_LAYOUT_APPEND( l, fd_stake_ci_align(), fd_stake_ci_footprint() );
15 0 : l = FD_LAYOUT_APPEND( l, FD_SHA256_ALIGN, FD_SHA256_FOOTPRINT );
16 0 : return FD_LAYOUT_FINI( l, fd_poh_tile_align() );
17 0 : }
18 :
19 : void
20 : fd_poh_tile_initialize( fd_poh_tile_ctx_t * ctx,
21 : ulong tick_duration_ns, /* See clock comments above, will be 500ns for mainnet-beta. */
22 : ulong hashcnt_per_tick, /* See clock comments above, will be 12,500 for mainnet-beta. */
23 : ulong ticks_per_slot, /* See clock comments above, will almost always be 64. */
24 : ulong tick_height, /* The counter (height) of the tick to start hashing on top of. */
25 0 : uchar const * last_entry_hash /* Points to start of a 32 byte region of memory, the hash itself at the tick height. */ ) {
26 0 : FD_LOG_WARNING(( "tick_duration_ns: %lu",tick_duration_ns ));
27 0 : ctx->slot = (tick_height/ticks_per_slot)+1UL;
28 0 : ctx->hashcnt = 0UL;
29 0 : ctx->last_slot = ctx->slot;
30 0 : ctx->last_hashcnt = 0UL;
31 0 : ctx->reset_slot = ctx->slot;
32 0 : ctx->reset_slot_start_ns = fd_log_wallclock(); /* safe to call from Rust */
33 :
34 0 : memcpy( ctx->hash, last_entry_hash, 32UL );
35 :
36 : /* Static configuration about the clock. */
37 0 : ctx->tick_duration_ns = tick_duration_ns;
38 0 : ctx->hashcnt_per_tick = hashcnt_per_tick;
39 0 : ctx->ticks_per_slot = ticks_per_slot;
40 :
41 : /* Recompute derived information about the clock. */
42 0 : ctx->slot_duration_ns = (double)ticks_per_slot*(double)tick_duration_ns;
43 0 : ctx->hashcnt_duration_ns = (double)tick_duration_ns/(double)hashcnt_per_tick;
44 0 : ctx->hashcnt_per_slot = ticks_per_slot*hashcnt_per_tick;
45 :
46 : /* Can be derived from other information, but we precompute it
47 : since it is used frequently. */
48 0 : ctx->hashcnt_per_slot = ticks_per_slot*hashcnt_per_tick;
49 :
50 0 : if( FD_UNLIKELY( ctx->hashcnt_per_tick==1UL ) ) {
51 : /* Low power producer, maximum of one microblock per tick in the slot */
52 0 : ctx->max_microblocks_per_slot = ctx->ticks_per_slot;
53 0 : } else {
54 : /* See the long comment in after_credit for this limit */
55 0 : ctx->max_microblocks_per_slot = fd_ulong_min( MAX_MICROBLOCKS_PER_SLOT, ctx->ticks_per_slot*(ctx->hashcnt_per_tick-1UL) );
56 0 : }
57 0 : }
58 :
59 : /* fd_poh_tile_reached_leader_slot returns 1 if we have reached a slot
60 : where we are leader. This is used by the replay stage to determine
61 : if it should create a new leader bank descendant of the prior reset
62 : slot block.
63 :
64 : Sometimes, even when we reach our slot we do not return 1, as we are
65 : giving a grace period to the prior leader to finish publishing their
66 : block.
67 :
68 : out_leader_slot is the slot height of the leader slot we reached, and
69 : reset_slot is the slot height of the last good (unskipped) slot we
70 : are building on top of. */
71 :
72 : int
73 : fd_poh_tile_reached_leader_slot( fd_poh_tile_ctx_t * ctx,
74 : ulong * out_leader_slot,
75 0 : ulong * out_reset_slot ) {
76 0 : *out_leader_slot = ctx->next_leader_slot;
77 0 : *out_reset_slot = ctx->reset_slot;
78 :
79 0 : if( FD_UNLIKELY( ctx->next_leader_slot==ULONG_MAX ||
80 0 : ctx->slot<ctx->next_leader_slot ) ) {
81 : /* Didn't reach our leader slot yet. */
82 0 : return 0;
83 0 : }
84 :
85 0 : if( FD_LIKELY( ctx->reset_slot==ctx->next_leader_slot ) ) {
86 : /* We were reset onto our leader slot, because the prior leader
87 : completed theirs, so we should start immediately, no need for a
88 : grace period. */
89 0 : return 1;
90 0 : }
91 :
92 : // if( FD_LIKELY( ctx->next_leader_slot>=1UL ) ) {
93 : // fd_epoch_leaders_t * leaders = fd_stake_ci_get_lsched_for_slot( ctx->stake_ci, ctx->next_leader_slot-1UL ); /* Safe to call from Rust */
94 : // if( FD_LIKELY( leaders ) ) {
95 : // fd_pubkey_t const * leader = fd_epoch_leaders_get( leaders, ctx->next_leader_slot-1UL ); /* Safe to call from Rust */
96 : // if( FD_LIKELY( leader ) ) {
97 : // if( FD_UNLIKELY( !memcmp( leader->uc, ctx->identity_key.uc, 32UL ) ) ) {
98 : // /* We were the leader in the previous slot, so also no need for
99 : // a grace period. We wouldn't get here if we were still
100 : // processing the prior slot so begin new one immediately. */
101 : // return 1;
102 : // }
103 : // }
104 : // }
105 : // }
106 :
107 0 : if( FD_UNLIKELY( ctx->next_leader_slot-ctx->reset_slot>=4UL ) ) {
108 : /* The prior leader has not completed any slot successfully during
109 : their 4 leader slots, so they are probably inactive and no need
110 : to give a grace period. */
111 0 : return 1;
112 0 : }
113 :
114 0 : if( FD_LIKELY( ctx->slot-ctx->next_leader_slot<GRACE_SLOTS ) ) {
115 : /* The prior leader hasn't finished their last slot, and they are
116 : likely still publishing, and within their grace period of two
117 : slots so we will keep waiting. */
118 0 : return 0;
119 0 : }
120 :
121 0 : return 1;
122 0 : }
123 :
124 : void
125 : fd_poh_tile_publish_became_leader( fd_poh_tile_ctx_t * ctx,
126 : void const * current_leader_data,
127 0 : ulong slot ) {
128 0 : double tick_per_ns = fd_tempo_tick_per_ns( NULL );
129 0 : fd_histf_sample( ctx->begin_leader_delay, (ulong)((double)(fd_log_wallclock()-ctx->reset_slot_start_ns)/tick_per_ns) );
130 :
131 0 : long slot_start_ns = ctx->reset_slot_start_ns + (long)((double)(slot-ctx->reset_slot)*ctx->slot_duration_ns);
132 :
133 : /* No need to check flow control, there are always credits became when we
134 : are leader, we will not "become" leader again until we are done, so at
135 : most one frag in flight at a time. */
136 :
137 0 : uchar * dst = ctx->get_pack_buffer_func( ctx->arg );
138 :
139 0 : fd_became_leader_t * leader = (fd_became_leader_t *)dst;
140 0 : leader->slot_start_ns = slot_start_ns;
141 0 : leader->slot_end_ns = (long)((double)slot_start_ns + ctx->slot_duration_ns);
142 0 : leader->bank = current_leader_data;
143 0 : leader->max_microblocks_in_slot = ctx->max_microblocks_per_slot;
144 0 : leader->ticks_per_slot = ctx->ticks_per_slot;
145 0 : leader->total_skipped_ticks = ctx->ticks_per_slot*(slot-ctx->reset_slot);
146 :
147 0 : if( FD_UNLIKELY( leader->ticks_per_slot+leader->total_skipped_ticks>=MAX_SKIPPED_TICKS ) )
148 0 : FD_LOG_ERR(( "Too many skipped ticks %lu for slot %lu, chain must halt", leader->ticks_per_slot+leader->total_skipped_ticks, slot ));
149 :
150 0 : FD_LOG_INFO(( "became_leader(slot=%lu)", slot ));
151 :
152 0 : ulong sig = fd_disco_poh_sig( slot, POH_PKT_TYPE_BECAME_LEADER, 0UL );
153 0 : ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
154 0 : ctx->publish_pack_func( ctx->arg, tspub, sig, sizeof(fd_became_leader_t) );
155 0 : }
156 :
157 : /* The PoH tile knows when it should become leader by waiting for its
158 : leader slot (with the operating system clock). This function is so
159 : that when it becomes the leader, it can be told what the leader bank
160 : is by the replay stage. See the notes in the long comment above for
161 : more on how this works. */
162 :
163 : void
164 : fd_poh_tile_begin_leader( fd_poh_tile_ctx_t * ctx,
165 : ulong slot,
166 0 : ulong hashcnt_per_tick ) {
167 0 : FD_TEST( ctx->current_leader_slot == FD_SLOT_NULL );
168 :
169 0 : if( FD_UNLIKELY( slot!=ctx->slot ) ) FD_LOG_ERR(( "Trying to begin leader slot %lu but we are now on slot %lu", slot, ctx->slot ));
170 0 : if( FD_UNLIKELY( slot!=ctx->next_leader_slot ) ) FD_LOG_ERR(( "Trying to begin leader slot %lu but we are now on slot %lu", slot, ctx->next_leader_slot ));
171 :
172 0 : if( FD_UNLIKELY( ctx->hashcnt_per_tick!=hashcnt_per_tick ) ) {
173 0 : FD_LOG_WARNING(( "hashes per tick changed from %lu to %lu", ctx->hashcnt_per_tick, hashcnt_per_tick ));
174 :
175 : /* Recompute derived information about the clock. */
176 0 : ctx->hashcnt_duration_ns = (double)ctx->tick_duration_ns/(double)hashcnt_per_tick;
177 0 : ctx->hashcnt_per_slot = ctx->ticks_per_slot*hashcnt_per_tick;
178 0 : ctx->hashcnt_per_tick = hashcnt_per_tick;
179 :
180 0 : if( FD_UNLIKELY( ctx->hashcnt_per_tick==1UL ) ) {
181 : /* Low power producer, maximum of one microblock per tick in the slot */
182 0 : ctx->max_microblocks_per_slot = ctx->ticks_per_slot;
183 0 : } else {
184 : /* See the long comment in after_credit for this limit */
185 0 : ctx->max_microblocks_per_slot = fd_ulong_min( MAX_MICROBLOCKS_PER_SLOT, ctx->ticks_per_slot*(ctx->hashcnt_per_tick-1UL) );
186 0 : }
187 :
188 : /* Discard any ticks we might have done in the interim. They will
189 : have the wrong number of hashes per tick. We can just catch back
190 : up quickly if not too many slots were skipped and hopefully
191 : publish on time. Note that tick production and verification of
192 : skipped slots is done for the eventual bank that publishes a
193 : slot, for example:
194 :
195 : Reset Slot: 998
196 : Epoch Transition Slot: 1000
197 : Leader Slot: 1002
198 :
199 : In this case, if a feature changing the hashcnt_per_tick is
200 : activated in slot 1000, and we are publishing empty ticks for
201 : slots 998, 999, 1000, and 1001, they should all have the new
202 : hashes_per_tick number of hashes, rather than the older one, or
203 : some combination. */
204 :
205 0 : FD_TEST( ctx->last_slot==ctx->reset_slot );
206 0 : FD_TEST( !ctx->last_hashcnt );
207 0 : ctx->slot = ctx->reset_slot;
208 0 : FD_LOG_WARNING(("BEGIN SLOT: %lu", ctx->slot));
209 0 : ctx->hashcnt = 0UL;
210 0 : }
211 :
212 0 : ctx->current_leader_slot = slot;
213 0 : ctx->microblocks_lower_bound = 0UL;
214 :
215 : /* We are about to start publishing to the shred tile for this slot
216 : so update the highwater mark so we never republish in this slot
217 : again. Also check that the leader slot is greater than the
218 : highwater, which should have been ensured earlier. */
219 :
220 0 : FD_TEST( ctx->highwater_leader_slot==ULONG_MAX || slot>=ctx->highwater_leader_slot );
221 0 : ctx->highwater_leader_slot = fd_ulong_max( fd_ulong_if( ctx->highwater_leader_slot==ULONG_MAX, 0UL, ctx->highwater_leader_slot ), slot );
222 :
223 0 : fd_poh_tile_publish_became_leader( ctx, (void *)(ctx->reset_slot-1UL), slot );
224 0 : }
225 :
226 : /* Determine what the next slot is in the leader schedule is that we are
227 : leader. Includes the current slot. If we are not leader in what
228 : remains of the current and next epoch, return ULONG_MAX. */
229 :
230 : ulong
231 0 : fd_poh_tile_next_leader_slot( fd_poh_tile_ctx_t * ctx ) {
232 : /* If we have published anything in a particular slot, then we
233 : should never become leader for that slot again. */
234 0 : ulong min_leader_slot = fd_ulong_max( ctx->slot, fd_ulong_if( ctx->highwater_leader_slot==ULONG_MAX, 0UL, ctx->highwater_leader_slot ) );
235 :
236 0 : for(;;) {
237 0 : fd_epoch_leaders_t * leaders = fd_stake_ci_get_lsched_for_slot( ctx->stake_ci, min_leader_slot ); /* Safe to call from Rust */
238 0 : if( FD_UNLIKELY( !leaders ) ) break;
239 :
240 0 : while( min_leader_slot<(leaders->slot0+leaders->slot_cnt) ) {
241 0 : fd_pubkey_t const * leader = fd_epoch_leaders_get( leaders, min_leader_slot ); /* Safe to call from Rust */
242 0 : if( FD_UNLIKELY( !memcmp( leader->key, ctx->identity_key.key, 32UL ) ) ) return min_leader_slot;
243 0 : min_leader_slot++;
244 0 : }
245 0 : }
246 :
247 0 : return ULONG_MAX;
248 0 : }
249 :
250 : void
251 0 : fd_poh_tile_no_longer_leader( fd_poh_tile_ctx_t * ctx ) {
252 : /* If we stop being leader in a slot, we can never become leader in
253 : that slot again, and all in-flight microblocks for that slot
254 : should be dropped. */
255 0 : ctx->highwater_leader_slot = fd_ulong_max( fd_ulong_if( ctx->highwater_leader_slot==ULONG_MAX, 0UL, ctx->highwater_leader_slot ), ctx->slot );
256 0 : ctx->current_leader_slot = FD_SLOT_NULL;
257 0 : ctx->next_leader_slot = fd_poh_tile_next_leader_slot( ctx );
258 :
259 0 : FD_COMPILER_MFENCE();
260 0 : ctx->signal_leader_change_func( ctx->arg );
261 0 : FD_LOG_INFO(( "fd_poh_tile_no_longer_leader(next_leader_slot=%lu)", ctx->next_leader_slot ));
262 0 : }
263 :
264 : void
265 : fd_poh_tile_reset( fd_poh_tile_ctx_t * ctx,
266 : ulong completed_bank_slot, /* The slot that successfully produced a block */
267 : uchar const * reset_blockhash, /* The hash of the last tick in the produced block */
268 0 : ulong hashcnt_per_tick /* The hashcnt per tick of the bank that completed */) {
269 0 : int leader_before_reset = ctx->slot>=ctx->next_leader_slot;
270 0 : if( FD_UNLIKELY( leader_before_reset && ctx->current_leader_slot!=FD_SLOT_NULL ) ) {
271 : /* If we were in the middle of a leader slot that we notified pack
272 : pack to start packing for we can never publish into that slot
273 : again, mark all in-flight microblocks to be dropped. */
274 0 : ctx->highwater_leader_slot = fd_ulong_max( fd_ulong_if( ctx->highwater_leader_slot==ULONG_MAX, 0UL, ctx->highwater_leader_slot ), 1UL+ctx->slot );
275 0 : }
276 :
277 0 : if( FD_UNLIKELY( ctx->expect_sequential_leader_slot==(completed_bank_slot+1UL) ) ) {
278 : /* If we are being reset onto a slot, it means some block was fully
279 : processed, so we reset to build on top of it. Typically we want
280 : to update the reset_slot_start_ns to the current time, because
281 : the network will give the next leader 400ms to publish,
282 : regardless of how long the prior leader took.
283 :
284 : But: if we were leader in the prior slot, and the block was our
285 : own we can do better. We know that the next slot should start
286 : exactly 400ms after the prior one started, so we can use that as
287 : the reset slot start time instead. */
288 0 : ctx->reset_slot_start_ns = ctx->reset_slot_start_ns + (long)((double)((completed_bank_slot+1UL)-ctx->reset_slot)*ctx->slot_duration_ns);
289 0 : } else {
290 0 : ctx->reset_slot_start_ns = fd_log_wallclock(); /* safe to call from Rust */
291 0 : }
292 0 : ctx->expect_sequential_leader_slot = ULONG_MAX;
293 :
294 0 : memcpy( ctx->hash, reset_blockhash, 32UL );
295 0 : ctx->slot = completed_bank_slot+1UL;
296 0 : FD_LOG_WARNING(("RESET SLOT: %lu", ctx->slot));
297 0 : ctx->hashcnt = 0UL;
298 0 : ctx->last_slot = ctx->slot;
299 0 : ctx->last_hashcnt = 0UL;
300 0 : ctx->reset_slot = ctx->slot;
301 :
302 0 : if( FD_UNLIKELY( ctx->hashcnt_per_tick!=hashcnt_per_tick ) ) {
303 0 : FD_LOG_WARNING(( "hashes per tick changed from %lu to %lu", ctx->hashcnt_per_tick, hashcnt_per_tick ));
304 :
305 : /* Recompute derived information about the clock. */
306 0 : ctx->hashcnt_duration_ns = (double)ctx->tick_duration_ns/(double)hashcnt_per_tick;
307 0 : ctx->hashcnt_per_slot = ctx->ticks_per_slot*hashcnt_per_tick;
308 0 : ctx->hashcnt_per_tick = hashcnt_per_tick;
309 :
310 0 : if( FD_UNLIKELY( ctx->hashcnt_per_tick==1UL ) ) {
311 : /* Low power producer, maximum of one microblock per tick in the slot */
312 0 : ctx->max_microblocks_per_slot = ctx->ticks_per_slot;
313 0 : } else {
314 : /* See the long comment in after_credit for this limit */
315 0 : ctx->max_microblocks_per_slot = fd_ulong_min( MAX_MICROBLOCKS_PER_SLOT, ctx->ticks_per_slot*(ctx->hashcnt_per_tick-1UL) );
316 0 : }
317 0 : }
318 :
319 0 : if( FD_UNLIKELY( leader_before_reset ) ) {
320 : /* No longer have a leader bank if we are reset. Replay stage will
321 : call back again to give us a new one if we should become leader
322 : for the reset slot.
323 :
324 : The order is important here, ctx->hashcnt must be updated before
325 : calling no_longer_leader. */
326 0 : fd_poh_tile_no_longer_leader( ctx );
327 0 : }
328 0 : ctx->next_leader_slot = fd_poh_tile_next_leader_slot( ctx );
329 0 : FD_LOG_INFO(( "fd_poh_tile_reset(slot=%lu,next_leader_slot=%lu) slots_until_leader=%lu", ctx->reset_slot, ctx->next_leader_slot, ctx->next_leader_slot-ctx->reset_slot ));
330 0 : }
331 :
332 : int
333 : fd_poh_tile_get_leader_after_n_slots( fd_poh_tile_ctx_t * ctx,
334 : ulong n,
335 0 : uchar out_pubkey[ static 32 ] ) {
336 0 : ulong slot = ctx->slot + n;
337 0 : fd_epoch_leaders_t * leaders = fd_stake_ci_get_lsched_for_slot( ctx->stake_ci, slot ); /* Safe to call from Rust */
338 :
339 0 : int copied = 0;
340 0 : if( FD_LIKELY( leaders ) ) {
341 0 : fd_pubkey_t const * leader = fd_epoch_leaders_get( leaders, slot ); /* Safe to call from Rust */
342 0 : if( FD_LIKELY( leader ) ) {
343 0 : memcpy( out_pubkey, leader, 32UL );
344 0 : copied = 1;
345 0 : }
346 0 : }
347 0 : return copied;
348 0 : }
349 :
350 : void
351 : fd_poh_tile_publish_tick( fd_poh_tile_ctx_t * ctx,
352 : uchar hash[ static 32 ],
353 0 : int is_skipped ) {
354 0 : ulong hashcnt = ctx->hashcnt_per_tick*(1UL+(ctx->last_hashcnt/ctx->hashcnt_per_tick));
355 :
356 0 : uchar * dst = ctx->get_microblock_buffer_func( ctx->arg );
357 :
358 0 : FD_TEST( ctx->last_slot>=ctx->reset_slot );
359 0 : fd_entry_batch_meta_t * meta = (fd_entry_batch_meta_t *)dst;
360 0 : if( is_skipped ) {
361 0 : meta->reference_tick = 0UL;
362 0 : meta->block_complete = 0;
363 0 : } else {
364 0 : meta->reference_tick = hashcnt/ctx->hashcnt_per_tick;
365 0 : meta->block_complete = hashcnt==ctx->hashcnt_per_slot;
366 0 : }
367 0 : ulong slot = fd_ulong_if( meta->block_complete, ctx->slot-1UL, ctx->slot );
368 0 : meta->parent_offset = 1UL+slot-ctx->reset_slot;
369 :
370 0 : FD_TEST( hashcnt>ctx->last_hashcnt );
371 0 : ulong hash_delta = hashcnt-ctx->last_hashcnt;
372 :
373 0 : dst += sizeof(fd_entry_batch_meta_t);
374 0 : fd_entry_batch_header_t * tick = (fd_entry_batch_header_t *)dst;
375 0 : tick->hashcnt_delta = hash_delta;
376 0 : fd_memcpy( tick->hash, hash, 32UL );
377 0 : tick->txn_cnt = 0UL;
378 :
379 : // FD_LOG_WARNING(("PUB TICK(%d): %lu %lu %lu %lu", is_skipped, slot, hash_delta, hashcnt, ctx->hashcnt_per_slot ));
380 0 : ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
381 0 : ulong sz = sizeof(fd_entry_batch_meta_t)+sizeof(fd_entry_batch_header_t);
382 0 : ulong sig = fd_disco_poh_sig( slot, POH_PKT_TYPE_MICROBLOCK, 0UL );
383 0 : ctx->publish_microblock_func( ctx->arg, tspub, sig, sz );
384 :
385 0 : if( FD_UNLIKELY( hashcnt==ctx->hashcnt_per_slot ) ) {
386 0 : ctx->last_slot++;
387 0 : ctx->last_hashcnt = 0UL;
388 0 : } else {
389 0 : ctx->last_hashcnt = hashcnt;
390 0 : }
391 0 : }
392 :
393 : int
394 : fd_poh_tile_after_credit( fd_poh_tile_ctx_t * ctx,
395 0 : int * opt_poll_in ) {
396 0 : int is_leader = ctx->next_leader_slot!=ULONG_MAX && ctx->slot>=ctx->next_leader_slot;
397 0 : if( FD_UNLIKELY( is_leader && ctx->current_leader_slot==FD_SLOT_NULL ) ) {
398 : /* If we are the leader, but we didn't yet learn what the leader
399 : bank object is from the replay stage, do not do any hashing.
400 :
401 : This is not ideal, but greatly simplifies the control flow. */
402 0 : return 0;
403 0 : }
404 :
405 : /* If we have skipped ticks pending because we skipped some slots to
406 : become leader, register them now one at a time. */
407 0 : if( FD_UNLIKELY( is_leader && ctx->last_slot<ctx->slot ) ) {
408 0 : ulong publish_hashcnt = ctx->last_hashcnt+ctx->hashcnt_per_tick;
409 0 : ulong tick_idx = (ctx->last_slot*ctx->ticks_per_slot+publish_hashcnt/ctx->hashcnt_per_tick)%MAX_SKIPPED_TICKS;
410 :
411 0 : ctx->register_tick_func( ctx->arg, ctx->current_leader_slot, ctx->skipped_tick_hashes[ tick_idx ] );
412 0 : fd_poh_tile_publish_tick( ctx, ctx->skipped_tick_hashes[ tick_idx ], 1 );
413 :
414 : /* If we are catching up now and publishing a bunch of skipped
415 : ticks, we do not want to process any incoming microblocks until
416 : all the skipped ticks have been published out; otherwise we would
417 : intersperse skipped tick messages with microblocks. */
418 0 : *opt_poll_in = 0;
419 0 : return 1;
420 0 : }
421 :
422 :
423 0 : int low_power_mode = ctx->hashcnt_per_tick==1UL;
424 :
425 : /* If we are the leader, always leave enough capacity in the slot so
426 : that we can mixin any potential microblocks still coming from the
427 : pack tile for this slot. */
428 0 : ulong max_remaining_microblocks = ctx->max_microblocks_per_slot - ctx->microblocks_lower_bound;
429 : /* With hashcnt_per_tick hashes per tick, we actually get
430 : hashcnt_per_tick-1 chances to mixin a microblock. For each tick
431 : span that we need to reserve, we also need to reserve the hashcnt
432 : for the tick, hence the +
433 : max_remaining_microblocks/(hashcnt_per_tick-1) rounded up.
434 :
435 : However, if hashcnt_per_tick is 1 because we're in low power mode,
436 : this should probably just be max_remaining_microblocks. */
437 0 : ulong max_remaining_ticks_or_microblocks = max_remaining_microblocks;
438 0 : if( FD_LIKELY( !low_power_mode ) ) max_remaining_ticks_or_microblocks += (max_remaining_microblocks+ctx->hashcnt_per_tick-2UL)/(ctx->hashcnt_per_tick-1UL);
439 :
440 0 : ulong restricted_hashcnt = fd_ulong_if( ctx->hashcnt_per_slot>=max_remaining_ticks_or_microblocks, ctx->hashcnt_per_slot-max_remaining_ticks_or_microblocks, 0UL );
441 :
442 0 : ulong min_hashcnt = ctx->hashcnt;
443 :
444 0 : if( FD_LIKELY( !low_power_mode ) ) {
445 : /* Recall that there are two kinds of events that will get published
446 : to the shredder,
447 :
448 : (a) Ticks. These occur every 62,500 (hashcnt_per_tick) hashcnts,
449 : and there will be 64 (ticks_per_slot) of them in each slot.
450 :
451 : Ticks must not have any transactions mixed into the hash.
452 : This is not strictly needed in theory, but is required by the
453 : current consensus protocol. They get published here in
454 : after_credit.
455 :
456 : (b) Microblocks. These can occur at any other hashcnt, as long
457 : as it is not a tick. Microblocks cannot be empty, and must
458 : have at least one transactions mixed in. These get
459 : published in after_frag.
460 :
461 : If hashcnt_per_tick is 1, then we are in low power mode and the
462 : following does not apply, since we can mix in transactions at any
463 : time.
464 :
465 : In the normal, non-low-power mode, though, we have to be careful
466 : to make sure that we do not publish microblocks on tick
467 : boundaries. To do that, we need to obey two rules:
468 : (i) after_credit must not leave hashcnt one before a tick
469 : boundary
470 : (ii) if after_credit begins one before a tick boundary, it must
471 : advance hashcnt and publish the tick
472 :
473 : There's some interplay between min_hashcnt and restricted_hashcnt
474 : here, and we need to show that there's always a value of
475 : target_hashcnt we can pick such that
476 : min_hashcnt <= target_hashcnt <= restricted_hashcnt.
477 : We'll prove this by induction for current_slot==0 and
478 : is_leader==true, since all other slots should be the same.
479 :
480 : Let m_j and r_j be the min_hashcnt and restricted_hashcnt
481 : (respectively) for the jth call to after_credit in a slot. We
482 : want to show that for all values of j, it's possible to pick a
483 : value h_j, the value of target_hashcnt for the jth call to
484 : after_credit (which is also the value of hashcnt after
485 : after_credit has completed) such that m_j<=h_j<=r_j.
486 :
487 : Additionally, let T be hashcnt_per_tick and N be ticks_per_slot.
488 :
489 : Starting with the base case, j==0. m_j=0, and
490 : r_0 = N*T - max_microblocks_per_slot
491 : - ceil(max_microblocks_per_slot/(T-1)).
492 :
493 : This is monotonic decreasing in max_microblocks_per_slot, so it
494 : achieves its minimum when max_microblocks_per_slot is its
495 : maximum.
496 : r_0 >= N*T - N*(T-1) - ceil( (N*(T-1))/(T-1))
497 : = N*T - N*(T-1)-N = 0.
498 : Thus, m_0 <= r_0, as desired.
499 :
500 :
501 :
502 : Then, for the inductive step, assume there exists h_j such that
503 : m_j<=h_j<=r_j, and we want to show that there exists h_{j+1},
504 : which is the same as showing m_{j+1}<=r_{j+1}.
505 :
506 : Let a_j be 1 if we had a microblock immediately following the jth
507 : call to after_credit, and 0 otherwise. Then hashcnt at the start
508 : of the (j+1)th call to after_frag is h_j+a_j.
509 : Also, set b_{j+1}=1 if we are in the case covered by rule (ii)
510 : above during the (j+1)th call to after_credit, i.e. if
511 : (h_j+a_j)%T==T-1. Thus, m_{j+1} = h_j + a_j + b_{j+1}.
512 :
513 : If we received an additional microblock, then
514 : max_remaining_microblocks goes down by 1, and
515 : max_remaining_ticks_or_microblocks goes down by either 1 or 2,
516 : which means restricted_hashcnt goes up by either 1 or 2. In
517 : particular, it goes up by 2 if the new value of
518 : max_remaining_microblocks (at the start of the (j+1)th call to
519 : after_credit) is congruent to 0 mod T-1. Let b'_{j+1} be 1 if
520 : this condition is met and 0 otherwise. If we receive a
521 : done_packing message, restricted_hashcnt can go up by more, but
522 : we can ignore that case, since it is less restrictive.
523 : Thus, r_{j+1}=r_j+a_j+b'_{j+1}.
524 :
525 : If h_j < r_j (strictly less), then h_j+a_j < r_j+a_j. And thus,
526 : since b_{j+1}<=b'_{j+1}+1, just by virtue of them both being
527 : binary,
528 : h_j + a_j + b_{j+1} < r_j + a_j + b'_{j+1} + 1,
529 : which is the same (for integers) as
530 : h_j + a_j + b_{j+1} <= r_j + a_j + b'_{j+1},
531 : m_{j+1} <= r_{j+1}
532 :
533 : On the other hand, if h_j==r_j, this is easy unless b_{j+1}==1,
534 : which can also only happen if a_j==1. Then (h_j+a_j)%T==T-1,
535 : which means there's an integer k such that
536 :
537 : h_j+a_j==(ticks_per_slot-k)*T-1
538 : h_j ==ticks_per_slot*T - k*(T-1)-1 - k-1
539 : ==ticks_per_slot*T - (k*(T-1)+1) - ceil( (k*(T-1)+1)/(T-1) )
540 :
541 : Since h_j==r_j in this case, and
542 : r_j==(ticks_per_slot*T) - max_remaining_microblocks_j - ceil(max_remaining_microblocks_j/(T-1)),
543 : we can see that the value of max_remaining_microblocks at the
544 : start of the jth call to after_credit is k*(T-1)+1. Again, since
545 : a_j==1, then the value of max_remaining_microblocks at the start
546 : of the j+1th call to after_credit decreases by 1 to k*(T-1),
547 : which means b'_{j+1}=1.
548 :
549 : Thus, h_j + a_j + b_{j+1} == r_j + a_j + b'_{j+1}, so, in
550 : particular, h_{j+1}<=r_{j+1} as desired. */
551 0 : min_hashcnt += (ulong)(min_hashcnt%ctx->hashcnt_per_tick == (ctx->hashcnt_per_tick-1UL)); /* add b_{j+1}, enforcing rule (ii) */
552 0 : }
553 : /* Now figure out how many hashes are needed to "catch up" the hash
554 : count to the current system clock, and clamp it to the allowed
555 : range. */
556 0 : long now = fd_log_wallclock();
557 0 : ulong target_hashcnt = (ulong)((double)(now - ctx->reset_slot_start_ns) / ctx->hashcnt_duration_ns) - (ctx->slot-ctx->reset_slot)*ctx->hashcnt_per_slot;
558 : /* Clamp to [min_hashcnt, restricted_hashcnt] as above */
559 0 : target_hashcnt = fd_ulong_max( fd_ulong_min( target_hashcnt, restricted_hashcnt ), min_hashcnt );
560 :
561 : /* The above proof showed that it was always possible to pick a value
562 : of target_hashcnt, but we still have a lot of freedom in how to
563 : pick it. It simplifies the code a lot if we don't keep going after
564 : a tick in this function. In particular, we want to publish at most
565 : 1 tick in this call, since otherwise we could consume infinite
566 : credits to publish here. The credits are set so that we should
567 : only ever publish one tick during this loop. Also, all the extra
568 : stuff (leader transitions, publishing ticks, etc.) we have to do
569 : happens at tick boundaries, so this lets us consolidate all those
570 : cases.
571 :
572 : Mathematically, since the current value of hashcnt is h_j+a_j, the
573 : next tick (advancing a full tick if we're currently at a tick) is
574 : t_{j+1} = T*(floor( (h_j+a_j)/T )+1). We need to show that if we set
575 : h'_{j+1} = min( h_{j+1}, t_{j+1} ), it is still valid.
576 :
577 : First, h'_{j+1} <= h_{j+1} <= r_{j+1}, so we're okay in that
578 : direction.
579 :
580 : Next, observe that t_{j+1}>=h_j + a_j + 1, and recall that b_{j+1}
581 : is 0 or 1. So then,
582 : t_{j+1} >= h_j+a_j+b_{j+1} = m_{j+1}.
583 :
584 : We know h_{j+1) >= m_{j+1} from before, so then h'_{j+1} >=
585 : m_{j+1}, as desired. */
586 :
587 0 : ulong next_tick_hashcnt = ctx->hashcnt_per_tick * (1UL+(ctx->hashcnt/ctx->hashcnt_per_tick));
588 0 : target_hashcnt = fd_ulong_min( target_hashcnt, next_tick_hashcnt );
589 :
590 : /* We still need to enforce rule (i). We know that min_hashcnt%T !=
591 : T-1 because of rule (ii). That means that if target_hashcnt%T ==
592 : T-1 at this point, target_hashcnt > min_hashcnt (notice the
593 : strict), so target_hashcnt-1 >= min_hashcnt and is thus still a
594 : valid choice for target_hashcnt. */
595 0 : target_hashcnt -= (ulong)( (!low_power_mode) & ((target_hashcnt%ctx->hashcnt_per_tick)==(ctx->hashcnt_per_tick-1UL)) );
596 :
597 0 : FD_TEST( target_hashcnt >= ctx->hashcnt );
598 0 : FD_TEST( target_hashcnt >= min_hashcnt );
599 0 : FD_TEST( target_hashcnt <= restricted_hashcnt );
600 :
601 0 : if( FD_UNLIKELY( ctx->hashcnt==target_hashcnt ) ) return 0; /* Nothing to do, don't publish a tick twice */
602 :
603 0 : while( ctx->hashcnt<target_hashcnt ) {
604 0 : fd_sha256_hash( ctx->hash, 32UL, ctx->hash );
605 0 : ctx->hashcnt++;
606 0 : }
607 :
608 0 : if( FD_UNLIKELY( ctx->hashcnt==ctx->hashcnt_per_slot ) ) {
609 0 : ctx->slot++;
610 0 : ctx->hashcnt = 0UL;
611 0 : }
612 :
613 0 : if( FD_UNLIKELY( !is_leader && !(ctx->hashcnt%ctx->hashcnt_per_tick ) ) ) {
614 : /* We finished a tick while not leader... save the current hash so
615 : it can be played back into the bank when we become the leader. */
616 0 : ulong tick_idx = (ctx->slot*ctx->ticks_per_slot+ctx->hashcnt/ctx->hashcnt_per_tick)%MAX_SKIPPED_TICKS;
617 0 : fd_memcpy( ctx->skipped_tick_hashes[ tick_idx ], ctx->hash, 32UL );
618 :
619 0 : ulong initial_tick_idx = (ctx->last_slot*ctx->ticks_per_slot+ctx->last_hashcnt/ctx->hashcnt_per_tick)%MAX_SKIPPED_TICKS;
620 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", ctx->last_slot, ctx->slot ));
621 0 : }
622 :
623 0 : if( FD_UNLIKELY( is_leader && !(ctx->hashcnt%ctx->hashcnt_per_tick) ) ) {
624 : /* We ticked while leader... tell the leader bank. */
625 0 : ctx->register_tick_func( ctx->arg, ctx->current_leader_slot, ctx->hash );
626 : /* And send an empty microblock (a tick) to the shred tile. */
627 0 : fd_poh_tile_publish_tick( ctx, ctx->hash, 0 );
628 0 : }
629 :
630 0 : if( FD_UNLIKELY( is_leader && ctx->slot>ctx->next_leader_slot ) ) {
631 : /* We ticked while leader and are no longer leader... transition
632 : the state machine. */
633 0 : FD_TEST( !max_remaining_microblocks );
634 0 : fd_poh_tile_no_longer_leader( ctx );
635 0 : ctx->expect_sequential_leader_slot = ctx->slot;
636 :
637 0 : double tick_per_ns = fd_tempo_tick_per_ns( NULL );
638 0 : fd_histf_sample( ctx->slot_done_delay, (ulong)((double)(fd_log_wallclock()-ctx->reset_slot_start_ns)/tick_per_ns) );
639 0 : }
640 :
641 0 : return 1;
642 0 : }
643 :
644 : void
645 0 : fd_poh_tile_init_stakes( fd_poh_tile_ctx_t * ctx, uchar const * stakes_msg ) {
646 0 : fd_stake_ci_stake_msg_init( ctx->stake_ci, stakes_msg );
647 0 : }
648 :
649 : void
650 0 : fd_poh_tile_fini_stakes( fd_poh_tile_ctx_t * ctx ) {
651 0 : fd_stake_ci_stake_msg_fini( ctx->stake_ci );
652 :
653 : /* It might seem like we do not need to do state transitions in and
654 : out of being the leader here, since leader schedule updates are
655 : always one epoch in advance (whether we are leader or not would
656 : never change for the currently executing slot) but this is not
657 : true for new ledgers when the validator first boots. We will
658 : likely be the leader in slot 1, and get notified of the leader
659 : schedule for that slot while we are still in it.
660 :
661 : For safety we just handle both transitions, in and out, although
662 : the only one possible should be into leader. */
663 0 : ulong next_leader_slot_after_frag = fd_poh_tile_next_leader_slot( ctx );
664 :
665 0 : int currently_leader = ctx->slot>=ctx->next_leader_slot;
666 0 : int leader_after_frag = ctx->slot>=next_leader_slot_after_frag;
667 :
668 0 : FD_LOG_INFO(( "stake_update(before_leader=%lu,after_leader=%lu)",
669 0 : ctx->next_leader_slot,
670 0 : next_leader_slot_after_frag ));
671 :
672 0 : ctx->next_leader_slot = next_leader_slot_after_frag;
673 0 : if( FD_UNLIKELY( currently_leader && !leader_after_frag ) ) {
674 : /* Shouldn't ever happen, otherwise we need to do a state
675 : transition out of being leader. */
676 0 : FD_LOG_ERR(( "stake update caused us to no longer be leader in an active slot" ));
677 0 : }
678 :
679 : /* Nothing to do if we transition into being leader, since it
680 : will just get picked up by the regular tick loop. */
681 0 : return;
682 0 : }
683 :
684 : void
685 0 : fd_poh_tile_done_packing( fd_poh_tile_ctx_t * ctx, ulong microblocks_in_slot ) {
686 0 : FD_TEST( ctx->microblocks_lower_bound<=ctx->max_microblocks_per_slot );
687 0 : FD_LOG_INFO(( "done_packing(slot=%lu,seen_microblocks=%lu,microblocks_in_slot=%lu)",
688 0 : ctx->slot,
689 0 : ctx->microblocks_lower_bound,
690 0 : microblocks_in_slot ));
691 0 : ctx->microblocks_lower_bound += ctx->max_microblocks_per_slot - microblocks_in_slot;
692 0 : }
693 :
694 : void
695 : fd_poh_tile_publish_microblock( fd_poh_tile_ctx_t * ctx,
696 : ulong sig,
697 : ulong slot,
698 : ulong hashcnt_delta,
699 : fd_txn_p_t * txns,
700 0 : ulong txn_cnt ) {
701 0 : uchar * dst = (uchar *)ctx->get_microblock_buffer_func( ctx->arg );
702 0 : FD_TEST( slot>=ctx->reset_slot );
703 0 : fd_entry_batch_meta_t * meta = (fd_entry_batch_meta_t *)dst;
704 0 : meta->parent_offset = 1UL+slot-ctx->reset_slot;
705 0 : meta->reference_tick = (ctx->hashcnt/ctx->hashcnt_per_tick) % ctx->ticks_per_slot;
706 0 : meta->block_complete = !ctx->hashcnt;
707 :
708 0 : dst += sizeof(fd_entry_batch_meta_t);
709 0 : fd_entry_batch_header_t * header = (fd_entry_batch_header_t *)dst;
710 0 : header->hashcnt_delta = hashcnt_delta;
711 0 : fd_memcpy( header->hash, ctx->hash, 32UL );
712 :
713 0 : dst += sizeof(fd_entry_batch_header_t);
714 0 : ulong payload_sz = 0UL;
715 0 : ulong included_txn_cnt = 0UL;
716 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
717 0 : fd_txn_p_t * txn = &txns[ i ];
718 0 : if( FD_UNLIKELY( !(txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS) ) ) continue;
719 :
720 0 : fd_memcpy( dst, txn->payload, txn->payload_sz );
721 0 : payload_sz += txn->payload_sz;
722 0 : dst += txn->payload_sz;
723 0 : included_txn_cnt++;
724 0 : }
725 0 : header->txn_cnt = included_txn_cnt;
726 :
727 : /* We always have credits to publish here, because we have a burst
728 : value of 3 credits, and at most we will publish_tick() once and
729 : then publish_became_leader() once, leaving one credit here to
730 : publish the microblock. */
731 0 : ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() );
732 0 : ulong sz = sizeof(fd_entry_batch_meta_t)+sizeof(fd_entry_batch_header_t)+payload_sz;
733 0 : ctx->publish_microblock_func( ctx->arg, tspub, sig, sz );
734 0 : }
735 :
736 : void
737 : fd_poh_tile_process_packed_microblock( fd_poh_tile_ctx_t * ctx,
738 : ulong target_slot,
739 : ulong sig,
740 : fd_txn_p_t * txns,
741 : ulong txn_cnt,
742 0 : uchar mixin_hash[ static 32 ] ) {
743 0 : if( FD_UNLIKELY( !ctx->microblocks_lower_bound ) ) {
744 0 : double tick_per_ns = fd_tempo_tick_per_ns( NULL );
745 0 : fd_histf_sample( ctx->first_microblock_delay, (ulong)((double)(fd_log_wallclock()-ctx->reset_slot_start_ns)/tick_per_ns) );
746 0 : }
747 :
748 0 : if( FD_UNLIKELY( target_slot!=ctx->next_leader_slot || target_slot!=ctx->slot ) ) {
749 0 : FD_LOG_ERR(( "packed too early or late target_slot=%lu, current_slot=%lu. highwater_leader_slot=%lu",
750 0 : target_slot, ctx->slot, ctx->highwater_leader_slot ));
751 0 : }
752 :
753 0 : FD_TEST( ctx->current_leader_slot!=FD_SLOT_NULL );
754 0 : FD_TEST( ctx->microblocks_lower_bound<ctx->max_microblocks_per_slot );
755 0 : ctx->microblocks_lower_bound += 1UL;
756 :
757 0 : ulong executed_txn_cnt = 0UL;
758 0 : for( ulong i=0; i<txn_cnt; i++ ) { executed_txn_cnt += !!(txns[ i ].flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS); }
759 :
760 : /* We don't publish transactions that fail to execute. If all the
761 : transctions failed to execute, the microblock would be empty, causing
762 : solana labs to think it's a tick and complain. Instead we just skip
763 : the microblock and don't hash or update the hashcnt. */
764 0 : if( FD_UNLIKELY( !executed_txn_cnt ) ) return;
765 :
766 0 : FD_LOG_DEBUG(( "rx packed mblk - target_slot: %lu, txn_cnt: %lu", target_slot, executed_txn_cnt ));
767 :
768 0 : uchar data[ 64 ];
769 0 : fd_memcpy( data, ctx->hash, 32UL );
770 0 : fd_memcpy( data+32UL, mixin_hash, 32UL );
771 0 : fd_sha256_hash( data, 64UL, ctx->hash );
772 :
773 0 : ctx->hashcnt++;
774 0 : FD_TEST( ctx->hashcnt>ctx->last_hashcnt );
775 0 : ulong hashcnt_delta = ctx->hashcnt - ctx->last_hashcnt;
776 :
777 : /* The hashing loop above will never leave us exactly one away from
778 : crossing a tick boundary, so this increment will never cause the
779 : current tick (or the slot) to change, except in low power mode
780 : for development, in which case we do need to register the tick
781 : with the leader bank. We don't need to publish the tick since
782 : sending the microblock below is the publishing action. */
783 0 : if( FD_UNLIKELY( !(ctx->hashcnt%ctx->hashcnt_per_slot ) ) ) {
784 0 : ctx->slot++;
785 0 : FD_LOG_WARNING(("SLOT INC: %lu", ctx->slot));
786 0 : ctx->hashcnt = 0UL;
787 0 : }
788 :
789 0 : ctx->last_slot = ctx->slot;
790 0 : ctx->last_hashcnt = ctx->hashcnt;
791 :
792 0 : if( FD_UNLIKELY( !(ctx->hashcnt%ctx->hashcnt_per_tick ) ) ) {
793 0 : ctx->register_tick_func( ctx->arg, ctx->current_leader_slot, ctx->hash );
794 0 : if( FD_UNLIKELY( ctx->slot>ctx->next_leader_slot ) ) {
795 : /* We ticked while leader and are no longer leader... transition
796 : the state machine. */
797 0 : fd_poh_tile_no_longer_leader( ctx );
798 0 : }
799 0 : }
800 :
801 0 : fd_poh_tile_publish_microblock( ctx, sig, target_slot, hashcnt_delta, txns, txn_cnt );
802 0 : }
803 :
804 : void
805 0 : fd_poh_tile_during_housekeeping( fd_poh_tile_ctx_t * ctx ) {
806 0 : FD_MHIST_COPY( POH_TILE, BEGIN_LEADER_DELAY_SECONDS, ctx->begin_leader_delay );
807 0 : FD_MHIST_COPY( POH_TILE, FIRST_MICROBLOCK_DELAY_SECONDS, ctx->first_microblock_delay );
808 0 : FD_MHIST_COPY( POH_TILE, SLOT_DONE_DELAY_SECONDS, ctx->slot_done_delay );
809 0 : }
810 :
811 : fd_poh_tile_ctx_t *
812 : fd_poh_tile_new( void * scratch,
813 : void * arg,
814 : fd_poh_tile_get_micoblock_buffer_func_t get_microblock_buffer_func,
815 : fd_poh_tile_publish_microblock_func_t publish_microblock_func,
816 : fd_poh_tile_get_pack_buffer_func_t get_pack_buffer_func,
817 : fd_poh_tile_publish_pack_func_t publish_pack_func,
818 : fd_poh_tile_register_tick_func_t register_tick_func,
819 0 : fd_poh_tile_signal_leader_change_func_t signal_leader_change_func ) {
820 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
821 0 : fd_poh_tile_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_poh_tile_ctx_t ), sizeof( fd_poh_tile_ctx_t ) );
822 0 : void * stake_ci = FD_SCRATCH_ALLOC_APPEND( l, fd_stake_ci_align(), fd_stake_ci_footprint() );
823 0 : void * sha256 = FD_SCRATCH_ALLOC_APPEND( l, FD_SHA256_ALIGN, FD_SHA256_FOOTPRINT );
824 :
825 0 : #define NONNULL( x ) (__extension__({ \
826 0 : __typeof__((x)) __x = (x); \
827 0 : if( FD_UNLIKELY( !__x ) ) FD_LOG_ERR(( #x " was unexpectedly NULL" )); \
828 0 : __x; }))
829 :
830 0 : ctx->stake_ci = NONNULL( fd_stake_ci_join( fd_stake_ci_new( stake_ci, &ctx->identity_key ) ) );
831 0 : ctx->sha256 = NONNULL( fd_sha256_join( fd_sha256_new( sha256 ) ) );
832 0 : ctx->current_leader_slot = FD_SLOT_NULL;
833 :
834 0 : ctx->slot = 0UL;
835 0 : ctx->hashcnt = 0UL;
836 0 : ctx->last_hashcnt = 0UL;
837 0 : ctx->highwater_leader_slot = ULONG_MAX;
838 0 : ctx->next_leader_slot = ULONG_MAX;
839 0 : ctx->reset_slot = ULONG_MAX;
840 :
841 0 : ctx->expect_sequential_leader_slot = ULONG_MAX;
842 :
843 0 : ctx->microblocks_lower_bound = 0UL;
844 :
845 0 : fd_histf_join( fd_histf_new( ctx->begin_leader_delay, FD_MHIST_SECONDS_MIN( POH_TILE, BEGIN_LEADER_DELAY_SECONDS ),
846 0 : FD_MHIST_SECONDS_MAX( POH_TILE, BEGIN_LEADER_DELAY_SECONDS ) ) );
847 0 : fd_histf_join( fd_histf_new( ctx->first_microblock_delay, FD_MHIST_SECONDS_MIN( POH_TILE, FIRST_MICROBLOCK_DELAY_SECONDS ),
848 0 : FD_MHIST_SECONDS_MAX( POH_TILE, FIRST_MICROBLOCK_DELAY_SECONDS ) ) );
849 0 : fd_histf_join( fd_histf_new( ctx->slot_done_delay, FD_MHIST_SECONDS_MIN( POH_TILE, SLOT_DONE_DELAY_SECONDS ),
850 0 : FD_MHIST_SECONDS_MAX( POH_TILE, SLOT_DONE_DELAY_SECONDS ) ) );
851 :
852 0 : ctx->arg = arg;
853 0 : ctx->get_microblock_buffer_func = get_microblock_buffer_func;
854 0 : ctx->publish_microblock_func = publish_microblock_func;
855 0 : ctx->get_pack_buffer_func = get_pack_buffer_func;
856 0 : ctx->publish_pack_func = publish_pack_func;
857 0 : ctx->register_tick_func = register_tick_func;
858 0 : ctx->signal_leader_change_func = signal_leader_change_func;
859 :
860 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, 1UL );
861 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + fd_poh_tile_footprint() ) )
862 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - fd_poh_tile_footprint(), scratch_top, (ulong)scratch + fd_poh_tile_footprint() ));
863 :
864 0 : return ctx;
865 0 : }
|