Line data Source code
1 : #define _GNU_SOURCE
2 : #include <linux/futex.h> /* FUTEX_WAKE */
3 : #include <sys/syscall.h> /* SYS_futex */
4 : #include <unistd.h> /* syscall(2) */
5 :
6 : #include "fd_replay_tile.h"
7 : #include "fd_replay_tile_private.h"
8 : #include "fd_sched.h"
9 : #include "fd_execrp.h"
10 : #include "generated/fd_replay_tile_seccomp.h"
11 :
12 : #include "../admin/fd_adminctl.h"
13 : #include "../genesis/fd_genesi_tile.h"
14 : #include "../poh/fd_poh.h"
15 : #include "../poh/fd_poh_tile.h"
16 : #include "../tower/fd_tower_tile.h"
17 : #include "../resolv/fd_resolv_tile.h"
18 : #include "../restore/utils/fd_ssload.h"
19 :
20 : #include "../../disco/tiles.h"
21 : #include "../../disco/fd_txn_m.h"
22 : #include "../../disco/shred/fd_fec_set.h"
23 : #include "../../disco/shred/fd_shred_tile.h"
24 : #include "../../disco/pack/fd_pack.h"
25 : #include "../backup/fd_snapmk_tile.h"
26 : #include "../reasm/fd_reasm.h"
27 : #include "../../disco/keyguard/fd_keyload.h"
28 : #include "../../disco/genesis/fd_genesis_cluster.h"
29 : #include "../../discof/genesis/genesis_hash.h"
30 : #include "../../util/pod/fd_pod.h"
31 : #include "../../flamenco/rewards/fd_rewards.h"
32 : #include "../../flamenco/leaders/fd_multi_epoch_leaders.h"
33 : #include "../../flamenco/progcache/fd_progcache_admin.h"
34 : #include "../../flamenco/rewards/fd_rewards.h"
35 : #include "../../disco/metrics/fd_metrics.h"
36 : #include "../repair/fd_repair_tile.h"
37 : #include "../repair/fd_repair_tile.h"
38 : #include "../../flamenco/runtime/fd_runtime.h"
39 : #include "../../flamenco/runtime/fd_runtime_stack.h"
40 : #include "../../flamenco/runtime/sysvar/fd_sysvar_cache.h"
41 : #include "../../flamenco/runtime/sysvar/fd_sysvar_stake_history.h"
42 : #include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h"
43 : #include "../../flamenco/runtime/sysvar/fd_sysvar_rent.h"
44 : #include "../../flamenco/runtime/program/fd_precompiles.h"
45 : #include "../../flamenco/runtime/program/vote/fd_vote_state_versioned.h"
46 : #include "../../flamenco/runtime/program/vote/fd_vote_codec.h"
47 : #include "../../flamenco/runtime/tests/fd_dump_pb.h"
48 :
49 : /* Replay concepts:
50 :
51 : - Blocks are aggregations of entries aka. microblocks which are
52 : groupings of txns and are constructed by the block producer (see
53 : fd_pack).
54 :
55 : - Entries are grouped into entry batches by the block producer (see
56 : fd_pack / fd_shredder).
57 :
58 : - Entry batches are divided into chunks known as shreds by the block
59 : producer (see fd_shredder).
60 :
61 : - Shreds are grouped into forward-error-correction sets (FEC sets) by
62 : the block producer (see fd_shredder).
63 :
64 : - Shreds are transmitted to the rest of the cluster via the Turbine
65 : protocol (see fd_shredder / fd_shred).
66 :
67 : - Once enough shreds within a FEC set are received to recover the
68 : entirety of the shred data encoded by that FEC set, the receiver
69 : can "complete" the FEC set (see fd_fec_resolver).
70 :
71 : - If shreds in the FEC set are missing such that it can't complete,
72 : the receiver can use the Repair protocol to request missing shreds
73 : in FEC set (see fd_repair).
74 :
75 : - The current Repair protocol does not support requesting coding
76 : shreds. As a result, some FEC sets might be actually complete
77 : (contain all data shreds). Repair currently hacks around this by
78 : forcing completion but the long-term solution is to add support for
79 : fec_repairing coding shreds via Repair.
80 :
81 : - FEC sets are delivered in partial-order to the Replay tile by the
82 : Repair tile. Currently Replay only supports replaying entry batches
83 : so FEC sets need to reassembled into an entry batch before they can
84 : be replayed. The new Dispatcher will change this by taking a FEC
85 : set as input instead. */
86 :
87 0 : #define IN_KIND_SNAP ( 0)
88 0 : #define IN_KIND_GENESIS ( 1)
89 0 : #define IN_KIND_IPECHO ( 2)
90 0 : #define IN_KIND_TOWER ( 3)
91 0 : #define IN_KIND_RESOLV ( 4)
92 0 : #define IN_KIND_POH ( 5)
93 0 : #define IN_KIND_EXECRP ( 6)
94 0 : #define IN_KIND_REPAIR ( 7)
95 0 : #define IN_KIND_TXSEND ( 8)
96 0 : #define IN_KIND_RPC ( 9)
97 0 : #define IN_KIND_GOSSIP_OUT (10)
98 0 : #define IN_KIND_SNAPMK (11)
99 0 : #define IN_KIND_ADMIN (12)
100 :
101 : #define DEBUG_LOGGING 0
102 :
103 : /* The first bank that the replay tile produces either for genesis
104 : or the snapshot boot will always be at bank index 0. */
105 0 : #define FD_REPLAY_BOOT_BANK_SEQ (0UL)
106 :
107 : static inline ulong
108 0 : fd_block_id_ele_get_idx( fd_block_id_ele_t * ele_arr, fd_block_id_ele_t * ele ) {
109 0 : return (ulong)(ele - ele_arr);
110 0 : }
111 :
112 : FD_FN_CONST static inline ulong
113 0 : scratch_align( void ) {
114 0 : return 128UL;
115 0 : }
116 : FD_FN_PURE static inline ulong
117 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
118 0 : ulong chain_cnt = fd_block_id_map_chain_cnt_est( tile->replay.max_live_slots );
119 :
120 0 : ulong l = FD_LAYOUT_INIT;
121 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_replay_tile_t), sizeof(fd_replay_tile_t) );
122 0 : l = FD_LAYOUT_APPEND( l, fd_runtime_stack_align(), fd_runtime_stack_footprint( FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS, FD_RUNTIME_MAX_STAKED_VOTE_ACCOUNTS, FD_RUNTIME_MAX_STAKE_ACCOUNTS ) );
123 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_block_id_ele_t), sizeof(fd_block_id_ele_t) * tile->replay.max_live_slots );
124 0 : l = FD_LAYOUT_APPEND( l, fd_block_id_map_align(), fd_block_id_map_footprint( chain_cnt ) );
125 0 : l = FD_LAYOUT_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->replay.max_live_slots ) );
126 0 : l = FD_LAYOUT_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->replay.max_live_slots ) );
127 0 : l = FD_LAYOUT_APPEND( l, fd_reasm_align(), fd_reasm_footprint( tile->replay.fec_max ) );
128 0 : l = FD_LAYOUT_APPEND( l, fd_sched_align(), fd_sched_footprint( tile->replay.sched_depth, tile->replay.max_live_slots ) );
129 0 : l = FD_LAYOUT_APPEND( l, fd_vote_tracker_align(), fd_vote_tracker_footprint() );
130 0 : l = FD_LAYOUT_APPEND( l, fd_capture_ctx_align(), fd_capture_ctx_footprint() );
131 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_dump_proto_ctx_t), sizeof(fd_dump_proto_ctx_t) );
132 :
133 0 : if( FD_UNLIKELY( tile->replay.dump_block_to_pb ) ) {
134 0 : l = FD_LAYOUT_APPEND( l, fd_block_dump_context_align(), fd_block_dump_context_footprint() );
135 0 : }
136 :
137 0 : l = FD_LAYOUT_FINI( l, scratch_align() );
138 :
139 0 : return l;
140 0 : }
141 :
142 : static inline void
143 0 : metrics_write( fd_replay_tile_t * ctx ) {
144 0 : fd_accdb_flush_metrics( ctx->accdb );
145 :
146 0 : FD_MCNT_SET ( REPLAY, STORE_QUERY_ACQUIRED, ctx->metrics.store_query_acquire );
147 0 : FD_MCNT_SET ( REPLAY, STORE_QUERY_RELEASED, ctx->metrics.store_query_release );
148 0 : FD_MHIST_COPY( REPLAY, STORE_QUERY_WAIT_SECONDS, ctx->metrics.store_query_wait );
149 0 : FD_MHIST_COPY( REPLAY, STORE_QUERY_WORK_SECONDS, ctx->metrics.store_query_work );
150 0 : FD_MCNT_SET ( REPLAY, STORE_QUERIED, ctx->metrics.store_query_cnt );
151 0 : FD_MCNT_SET ( REPLAY, STORE_QUERY_MISSING, ctx->metrics.store_query_missing_cnt );
152 0 : FD_MGAUGE_SET( REPLAY, STORE_QUERY_MERKLE_ROOT_SAMPLE, ctx->metrics.store_query_mr );
153 0 : FD_MGAUGE_SET( REPLAY, STORE_QUERY_MISSING_MERKLE_ROOT_SAMPLE, ctx->metrics.store_query_missing_mr );
154 :
155 0 : FD_MGAUGE_SET( REPLAY, ROOT_SLOT, ctx->consensus_root_slot==ULONG_MAX ? 0UL : ctx->consensus_root_slot );
156 0 : ulong leader_slot = ctx->leader_bank ? ctx->leader_bank->f.slot : 0UL;
157 :
158 0 : if( FD_LIKELY( ctx->leader_bank ) ) {
159 0 : FD_MGAUGE_SET( REPLAY, NEXT_LEADER_SLOT, leader_slot );
160 0 : FD_MGAUGE_SET( REPLAY, LEADER_SLOT, leader_slot );
161 0 : } else {
162 0 : FD_MGAUGE_SET( REPLAY, NEXT_LEADER_SLOT, ctx->next_leader_slot==ULONG_MAX ? 0UL : ctx->next_leader_slot );
163 0 : FD_MGAUGE_SET( REPLAY, LEADER_SLOT, 0UL );
164 0 : }
165 0 : FD_MGAUGE_SET( REPLAY, RESET_SLOT, ctx->reset_slot==ULONG_MAX ? 0UL : ctx->reset_slot );
166 :
167 0 : FD_MGAUGE_SET( REPLAY, BANK_LIVE, fd_banks_pool_used_cnt( ctx->banks ) );
168 :
169 0 : ulong reasm_free = fd_reasm_free( ctx->reasm );
170 0 : FD_MGAUGE_SET( REPLAY, REASSEMBLY_FREE, reasm_free );
171 :
172 0 : FD_MCNT_SET( REPLAY, SLOT_REPLAYED, ctx->metrics.slots_total );
173 0 : FD_MCNT_SET( REPLAY, TXN_PROCESSED, ctx->metrics.transactions_total );
174 :
175 0 : FD_MGAUGE_SET( REPLAY, REASSEMBLY_LATEST_SLOT, ctx->metrics.reasm_latest_slot );
176 0 : FD_MGAUGE_SET( REPLAY, REASSEMBLY_LATEST_FEC_INDEX, ctx->metrics.reasm_latest_fec_idx );
177 :
178 0 : fd_sched_metrics_write( ctx->sched );
179 :
180 0 : FD_MCNT_SET( REPLAY, FEC_SCHED_FULL, ctx->metrics.sched_full );
181 0 : FD_MCNT_SET( REPLAY, FEC_REASSEMBLY_EMPTY, ctx->metrics.reasm_empty );
182 0 : FD_MCNT_SET( REPLAY, FEC_LEADER_BID_WAIT, ctx->metrics.leader_bid_wait );
183 0 : FD_MCNT_SET( REPLAY, FEC_BANK_FULL, ctx->metrics.banks_full );
184 0 : FD_MCNT_SET( REPLAY, STORAGE_ROOT_BEHIND, ctx->metrics.storage_root_behind );
185 :
186 0 : fd_progcache_admin_metrics_t const * pcm = &fd_progcache_admin_metrics_g;
187 0 : FD_MCNT_SET( REPLAY, PROGCACHE_ROOTED, pcm->root_cnt );
188 :
189 0 : fd_wksp_mon_t * wm = fd_wksp_mon_tick( ctx->progcache_wksp_mon, fd_tickcount() );
190 0 : FD_MGAUGE_SET( REPLAY, PROGCACHE_FREE_PARTITION, wm->free_cnt );
191 0 : FD_MGAUGE_SET( REPLAY, PROGCACHE_FREE_BYTES, wm->free_sz );
192 0 : FD_MGAUGE_SET( REPLAY, PROGCACHE_SIZE_BYTES, wm->wksp->data_max );
193 0 : FD_MGAUGE_SET( REPLAY, PROGCACHE_FREE_PARTITION_MAX_BYTES, wm->free_max_sz );
194 0 : FD_MGAUGE_SET( REPLAY, PROGCACHE_USED_PARTITION_MEDIAN_BYTES, wm->part_median_sz );
195 0 : FD_MGAUGE_SET( REPLAY, PROGCACHE_USED_PARTITION_MEAN_BYTES, wm->part_mean_sz );
196 :
197 0 : FD_ACCDB_METRICS_WRITE( REPLAY, fd_accdb_metrics( ctx->accdb ) );
198 0 : }
199 :
200 : static void
201 : publish_epoch_info( fd_replay_tile_t * ctx,
202 : fd_stem_context_t * stem,
203 : fd_bank_t * bank,
204 0 : int next_epoch ) {
205 0 : fd_epoch_schedule_t const * schedule = &bank->f.epoch_schedule;
206 0 : ulong epoch = fd_slot_to_epoch( schedule, bank->f.slot, NULL ) + fd_ulong_if( next_epoch, 1UL, 0UL );
207 :
208 0 : fd_features_t const * features = &bank->f.features;
209 :
210 0 : fd_runtime_stack_t * runtime_stack = ctx->runtime_stack;
211 :
212 0 : fd_epoch_info_msg_t * epoch_info_msg = fd_chunk_to_laddr( ctx->epoch_out->mem, ctx->epoch_out->chunk );
213 :
214 0 : epoch_info_msg->staked_vote_cnt = next_epoch ? runtime_stack->epoch_weights.next_stake_weights_cnt : runtime_stack->epoch_weights.stake_weights_cnt;
215 0 : epoch_info_msg->staked_id_cnt = next_epoch ? runtime_stack->epoch_weights.next_id_weights_cnt : runtime_stack->epoch_weights.id_weights_cnt;
216 0 : epoch_info_msg->epoch_schedule = *schedule;
217 0 : epoch_info_msg->features = *features;
218 0 : epoch_info_msg->epoch = epoch;
219 0 : epoch_info_msg->start_slot = fd_epoch_slot0( schedule, epoch );
220 0 : epoch_info_msg->slot_cnt = fd_epoch_slot_cnt( schedule, epoch );
221 0 : epoch_info_msg->ns_per_slot = fd_slot_params_at_slot( bank, epoch_info_msg->start_slot ).ns_per_slot;
222 :
223 0 : fd_vote_stake_weight_t * stake_weights = fd_type_pun( epoch_info_msg + 1 );
224 0 : fd_vote_stake_weight_t * src_stake_weights = next_epoch ? runtime_stack->epoch_weights.next_stake_weights : runtime_stack->epoch_weights.stake_weights;
225 0 : memcpy( stake_weights, src_stake_weights, epoch_info_msg->staked_vote_cnt * sizeof(fd_vote_stake_weight_t) );
226 :
227 0 : fd_stake_weight_t * id_weights = fd_epoch_info_msg_id_weights( epoch_info_msg );
228 0 : fd_stake_weight_t * src_id_weights = next_epoch ? runtime_stack->epoch_weights.next_id_weights : runtime_stack->epoch_weights.id_weights;
229 0 : fd_memcpy( id_weights, src_id_weights, epoch_info_msg->staked_id_cnt * sizeof(fd_stake_weight_t) );
230 :
231 0 : ulong epoch_info_sz = fd_epoch_info_msg_sz( epoch_info_msg->staked_vote_cnt , epoch_info_msg->staked_id_cnt );
232 :
233 0 : ulong epoch_info_sig = 4UL;
234 0 : fd_stem_publish( stem, ctx->epoch_out->idx, epoch_info_sig, ctx->epoch_out->chunk, epoch_info_sz, 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
235 0 : ctx->epoch_out->chunk = fd_dcache_compact_next( ctx->epoch_out->chunk, epoch_info_sz, ctx->epoch_out->chunk0, ctx->epoch_out->wmark );
236 :
237 0 : fd_multi_epoch_leaders_epoch_msg_init( ctx->mleaders, epoch_info_msg );
238 0 : fd_multi_epoch_leaders_epoch_msg_fini( ctx->mleaders );
239 0 : }
240 :
241 : /**********************************************************************/
242 : /* Transaction execution state machine helpers */
243 : /**********************************************************************/
244 :
245 : static void
246 : replay_block_start( fd_replay_tile_t * ctx,
247 : ulong bank_idx,
248 : ulong parent_bank_idx,
249 0 : ulong slot ) {
250 0 : long before = fd_log_wallclock();
251 :
252 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, bank_idx );
253 0 : FD_CHECK_CRIT( bank, "invariant violation: bank is NULL" );
254 0 : FD_CHECK_CRIT( bank->state==FD_BANK_STATE_INIT, "invariant violation: bank is not in correct state" );
255 :
256 0 : bank->preparation_begin_nanos = before;
257 :
258 0 : fd_bank_t * parent_bank = fd_banks_bank_query( ctx->banks, parent_bank_idx );
259 0 : FD_CHECK_CRIT( parent_bank, "invariant violation: parent bank is NULL" );
260 0 : FD_CHECK_CRIT( parent_bank->state==FD_BANK_STATE_FROZEN || parent_bank->state==FD_BANK_STATE_PRUNABLE, "invariant violation: parent bank is not in correct state" );
261 :
262 : /* Clone the bank from the parent. We must special case the first
263 : slot that is executed as the snapshot does not provide a parent
264 : block id. */
265 :
266 0 : bank = fd_banks_clone_from_parent( ctx->banks, bank_idx );
267 0 : if( FD_UNLIKELY( !bank ) ) {
268 0 : FD_LOG_CRIT(( "invariant violation: bank is NULL for bank index %lu", bank_idx ));
269 0 : }
270 0 : bank->f.slot = slot;
271 0 : bank->txncache_fork_id = fd_txncache_attach_child ( ctx->txncache, parent_bank->txncache_fork_id );
272 0 : bank->progcache_fork_id = fd_progcache_attach_child( ctx->progcache, parent_bank->progcache_fork_id );
273 0 : bank->accdb_fork_id = fd_accdb_attach_child ( ctx->accdb, parent_bank->accdb_fork_id );
274 0 : bank->parent_accdb_fork_id = parent_bank->accdb_fork_id;
275 :
276 0 : ulong new_epoch = fd_slot_to_epoch( &parent_bank->f.epoch_schedule, slot, NULL );
277 0 : ulong root_epoch = fd_slot_to_epoch( &parent_bank->f.epoch_schedule, ctx->published_root_slot, NULL );
278 0 : if( FD_UNLIKELY( new_epoch>root_epoch+1UL ) ) {
279 0 : FD_LOG_CRIT(( "firedancer replay does not support replaying more than one epoch ahead of the current root" ));
280 0 : }
281 :
282 : /* Update required runtime state and handle potential boundary. */
283 :
284 0 : int is_epoch_boundary = 0;
285 0 : fd_runtime_block_execute_prepare( ctx->banks, bank, ctx->accdb, ctx->runtime_stack, ctx->capture_ctx, &is_epoch_boundary );
286 :
287 0 : ulong max_tick_height;
288 0 : if( FD_UNLIKELY( FD_RUNTIME_EXECUTE_SUCCESS!=fd_runtime_compute_max_tick_height( parent_bank->f.ticks_per_slot, slot, &max_tick_height ) ) ) {
289 0 : FD_LOG_CRIT(( "couldn't compute tick height/max tick height slot %lu ticks_per_slot %lu", slot, parent_bank->f.ticks_per_slot ));
290 0 : }
291 0 : bank->f.max_tick_height = max_tick_height;
292 0 : fd_sched_set_poh_params( ctx->sched, bank->idx, bank->f.tick_height, bank->f.max_tick_height, bank->f.slot_params.hashes_per_tick, &parent_bank->f.poh );
293 :
294 0 : FD_LOG_DEBUG(( "replay_block_start: bank_idx=%lu slot=%lu parent_bank_idx=%lu", bank_idx, slot, parent_bank_idx ));
295 0 : }
296 :
297 : static void
298 0 : cost_tracker_snap( fd_bank_t * bank, fd_replay_slot_completed_t * slot_info ) {
299 0 : if( FD_LIKELY( bank->cost_tracker_pool_idx!=ULONG_MAX ) ) {
300 0 : fd_cost_tracker_t const * cost_tracker = fd_bank_cost_tracker_query( bank );
301 0 : if( FD_UNLIKELY( cost_tracker->block_cost_limit==0UL ) ) {
302 0 : memset( &slot_info->cost_tracker, -1 /* ULONG_MAX */, sizeof(slot_info->cost_tracker) );
303 0 : } else {
304 0 : slot_info->cost_tracker.block_cost = cost_tracker->block_cost;
305 0 : slot_info->cost_tracker.vote_cost = cost_tracker->vote_cost;
306 0 : slot_info->cost_tracker.allocated_accounts_data_size = cost_tracker->allocated_accounts_data_size;
307 0 : slot_info->cost_tracker.block_cost_limit = cost_tracker->block_cost_limit;
308 0 : slot_info->cost_tracker.vote_cost_limit = cost_tracker->vote_cost_limit;
309 0 : slot_info->cost_tracker.account_cost_limit = cost_tracker->account_cost_limit;
310 0 : }
311 0 : } else {
312 0 : memset( &slot_info->cost_tracker, -1 /* ULONG_MAX */, sizeof(slot_info->cost_tracker) );
313 0 : }
314 0 : }
315 :
316 : static void
317 : publish_slot_completed( fd_replay_tile_t * ctx,
318 : fd_stem_context_t * stem,
319 : fd_bank_t * bank,
320 : int is_initial,
321 : int is_leader,
322 : ulong execution_fees_pre_settle,
323 0 : ulong priority_fees_pre_settle ) {
324 :
325 0 : ulong slot = bank->f.slot;
326 :
327 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ bank->idx ];
328 :
329 : /* HACKY: hacky way of checking if we should send a null parent block
330 : id */
331 0 : fd_hash_t parent_block_id = {0};
332 0 : if( FD_LIKELY( !is_initial ) ) {
333 0 : parent_block_id = ctx->block_id_arr[ bank->parent_idx ].latest_mr;
334 0 : }
335 :
336 0 : fd_hash_t const * bank_hash = &bank->f.bank_hash;
337 0 : fd_hash_t const * block_hash = fd_blockhashes_peek_last_hash( &bank->f.block_hash_queue );
338 0 : FD_TEST( block_hash );
339 :
340 0 : if( FD_LIKELY( !is_initial ) ) fd_txncache_finalize_fork( ctx->txncache, bank->txncache_fork_id, 0UL, block_hash->uc );
341 :
342 0 : fd_epoch_schedule_t const * epoch_schedule = &bank->f.epoch_schedule;
343 0 : ulong slot_idx;
344 0 : ulong epoch = fd_slot_to_epoch( epoch_schedule, slot, &slot_idx );
345 :
346 0 : ctx->metrics.slots_total++;
347 0 : ctx->metrics.transactions_total = bank->f.parent_txn_count + bank->f.txn_count;
348 :
349 : /* Caught up once replay completes a slot within a few slots of the
350 : cluster tip. Require the tip to have advanced a few times first so
351 : a brief view of the tip right after boot does not count. */
352 0 : if( FD_UNLIKELY( !ctx->caught_up && !is_initial &&
353 0 : ctx->catch_up_tip_advance_cnt>=12UL &&
354 0 : ctx->catch_up_max_fec_slot<slot+3UL ) ) {
355 0 : ctx->caught_up = 1;
356 0 : double boot_secs = (double)(fd_log_wallclock()-ctx->boot_timestamp_nanos)/1e9;
357 0 : FD_LOG_NOTICE(( "caught up to cluster at slot %s%lu%s %s(%.1f seconds since boot)%s",
358 0 : fd_log_style_bold(), slot, fd_log_style_normal(),
359 0 : fd_log_style_dim(), boot_secs, fd_log_style_normal() ));
360 0 : }
361 :
362 0 : fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
363 0 : slot_info->slot = slot;
364 0 : slot_info->root_slot = ctx->consensus_root_slot;
365 0 : slot_info->storage_slot = ctx->published_root_slot;
366 0 : slot_info->epoch = epoch;
367 0 : slot_info->slot_in_epoch = slot_idx;
368 0 : slot_info->slots_per_epoch = fd_epoch_slot_cnt( epoch_schedule, epoch );
369 0 : slot_info->block_height = bank->f.block_height;
370 0 : slot_info->parent_slot = bank->f.parent_slot;
371 0 : slot_info->block_id = block_id_ele->latest_mr;
372 0 : slot_info->parent_block_id = parent_block_id;
373 0 : slot_info->bank_hash = *bank_hash;
374 0 : slot_info->block_hash = *block_hash;
375 0 : slot_info->transaction_count = bank->f.parent_txn_count + bank->f.txn_count;
376 :
377 0 : fd_inflation_t inflation = bank->f.inflation;
378 0 : slot_info->inflation.foundation = inflation.foundation;
379 0 : slot_info->inflation.foundation_term = inflation.foundation_term;
380 0 : slot_info->inflation.terminal = inflation.terminal;
381 0 : slot_info->inflation.initial = inflation.initial;
382 0 : slot_info->inflation.taper = inflation.taper;
383 :
384 0 : fd_rent_t rent = bank->f.rent;
385 0 : slot_info->rent.burn_percent = rent.burn_percent;
386 0 : slot_info->rent.lamports_per_uint8_year = rent.lamports_per_uint8_year;
387 0 : slot_info->rent.exemption_threshold = rent.exemption_threshold;
388 :
389 0 : slot_info->first_fec_set_received_nanos = bank->first_fec_set_received_nanos;
390 0 : slot_info->preparation_begin_nanos = bank->preparation_begin_nanos;
391 0 : slot_info->first_transaction_scheduled_nanos = bank->first_transaction_scheduled_nanos;
392 0 : slot_info->last_transaction_finished_nanos = bank->last_transaction_finished_nanos;
393 0 : slot_info->completion_time_nanos = fd_log_wallclock();
394 0 : if( !slot_info->first_transaction_scheduled_nanos ) { /* edge case: empty slot */
395 0 : slot_info->first_transaction_scheduled_nanos = slot_info->last_transaction_finished_nanos;
396 0 : }
397 :
398 : /* refcnt should be incremented by 1 for each consumer that uses
399 : `bank_idx`. Each consumer should decrement the bank's refcnt once
400 : they are done using the bank. */
401 0 : bank->refcnt++; /* tower_tile */
402 0 : if( FD_LIKELY( ctx->rpc_enabled ) ) bank->refcnt++; /* rpc tile */
403 0 : slot_info->bank_idx = bank->idx;
404 0 : slot_info->bank_seq = bank->bank_seq;
405 0 : slot_info->accdb_fork_id = bank->accdb_fork_id;
406 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for tower, rpc", bank->idx, slot, bank->refcnt ));
407 :
408 0 : fd_bank_t * parent_bank = fd_banks_get_parent( ctx->banks, bank );
409 0 : slot_info->parent_bank_idx = parent_bank ? parent_bank->idx : ULONG_MAX;
410 0 : slot_info->parent_bank_seq = parent_bank ? parent_bank->bank_seq : ULONG_MAX;
411 0 : if( FD_LIKELY( parent_bank ) ) {
412 0 : ulong total_txn_cnt = bank->f.txn_count;
413 0 : ulong nonvote_txn_cnt = bank->f.nonvote_txn_count;
414 0 : ulong failed_txn_cnt = bank->f.failed_txn_count;
415 0 : ulong nonvote_failed_txn_cnt = bank->f.nonvote_failed_txn_count;
416 :
417 0 : slot_info->nonvote_success = nonvote_txn_cnt - nonvote_failed_txn_cnt;
418 0 : slot_info->nonvote_failed = nonvote_failed_txn_cnt;
419 0 : slot_info->vote_failed = failed_txn_cnt - nonvote_failed_txn_cnt;
420 0 : slot_info->vote_success = total_txn_cnt - nonvote_txn_cnt - slot_info->vote_failed;
421 0 : } else {
422 0 : slot_info->vote_failed = ULONG_MAX;
423 0 : slot_info->vote_success = ULONG_MAX;
424 0 : slot_info->nonvote_success = ULONG_MAX;
425 0 : slot_info->nonvote_failed = ULONG_MAX;
426 0 : }
427 :
428 0 : slot_info->is_leader = is_leader;
429 0 : slot_info->transaction_fee = execution_fees_pre_settle;
430 0 : slot_info->transaction_fee -= (slot_info->transaction_fee>>1); /* burn */
431 0 : slot_info->priority_fee = priority_fees_pre_settle;
432 0 : slot_info->tips = bank->f.tips;
433 0 : slot_info->shred_cnt = bank->f.shred_cnt;
434 :
435 0 : FD_BASE58_ENCODE_32_BYTES( ctx->block_id_arr[ bank->idx ].latest_mr.uc, block_id_b58 );
436 0 : FD_BASE58_ENCODE_32_BYTES( bank->f.bank_hash.uc, bank_hash_b58 );
437 0 : FD_BASE58_ENCODE_32_BYTES( bank->f.poh.uc, poh_hash_b58 );
438 0 : FD_LOG_DEBUG(( "finished replaying slot %lu with (block id %s, bank hash %s, PoH hash %s, transactions %lu, votes %lu, shreds %lu, CUs used %lu, fees %lu) "
439 0 : "and timings [since parent fini %ld ns, started prepare %ld ns, started dispatching transactions %ld ns, finished executing transactions %ld ns, finished block %ld ns]",
440 0 : bank->f.slot, block_id_b58,
441 0 : bank_hash_b58,
442 0 : poh_hash_b58,
443 0 : bank->f.txn_count,
444 0 : bank->f.txn_count - bank->f.nonvote_txn_count,
445 0 : bank->f.shred_cnt,
446 0 : bank->f.total_compute_units_used,
447 0 : execution_fees_pre_settle + priority_fees_pre_settle,
448 0 : !!parent_bank ? parent_bank->block_completed_nanos - bank->first_fec_set_received_nanos : LONG_MAX,
449 0 : bank->preparation_begin_nanos - bank->first_fec_set_received_nanos,
450 0 : bank->first_transaction_scheduled_nanos - bank->preparation_begin_nanos,
451 0 : bank->last_transaction_finished_nanos - bank->first_transaction_scheduled_nanos,
452 0 : bank->block_completed_nanos - bank->last_transaction_finished_nanos ));
453 :
454 0 : fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_SLOT_COMPLETED, ctx->replay_out->chunk, sizeof(fd_replay_slot_completed_t), 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
455 0 : ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(fd_replay_slot_completed_t), ctx->replay_out->chunk0, ctx->replay_out->wmark );
456 0 : }
457 :
458 : static void
459 : publish_slot_dead( fd_replay_tile_t * ctx,
460 : fd_stem_context_t * stem,
461 : ulong slot,
462 0 : fd_hash_t const * block_id ) {
463 0 : fd_replay_slot_dead_t * slot_dead = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
464 0 : slot_dead->slot = slot;
465 0 : slot_dead->block_id = *block_id;
466 0 : fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_SLOT_DEAD, ctx->replay_out->chunk, sizeof(fd_replay_slot_dead_t), 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
467 0 : ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(fd_replay_slot_dead_t), ctx->replay_out->chunk0, ctx->replay_out->wmark );
468 0 : }
469 :
470 : static void
471 : publish_txn_executed( fd_replay_tile_t * ctx,
472 : fd_stem_context_t * stem,
473 0 : ulong txn_idx ) {
474 0 : fd_sched_txn_info_t * txn_info = fd_sched_get_txn_info( ctx->sched, txn_idx );
475 0 : fd_replay_txn_executed_t * txn_executed = fd_type_pun( fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk ) );
476 0 : *txn_executed->txn = *fd_sched_get_txn( ctx->sched, txn_idx );
477 0 : txn_executed->txn_err = txn_info->txn_err;
478 0 : txn_executed->is_committable = !!(txn_info->flags&FD_SCHED_TXN_IS_COMMITTABLE);
479 0 : txn_executed->is_fees_only = !!(txn_info->flags&FD_SCHED_TXN_IS_FEES_ONLY);
480 0 : txn_executed->tick_parsed = txn_info->tick_parsed;
481 0 : txn_executed->tick_sigverify_disp = txn_info->tick_sigverify_disp;
482 0 : txn_executed->tick_sigverify_done = txn_info->tick_sigverify_done;
483 0 : txn_executed->tick_exec_disp = txn_info->tick_exec_disp;
484 0 : txn_executed->tick_exec_done = txn_info->tick_exec_done;
485 0 : fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_TXN_EXECUTED, ctx->replay_out->chunk, sizeof(*txn_executed), 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
486 0 : ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(*txn_executed), ctx->replay_out->chunk0, ctx->replay_out->wmark );
487 0 : }
488 :
489 : static void
490 : replay_block_finalize( fd_replay_tile_t * ctx,
491 : fd_stem_context_t * stem,
492 0 : fd_bank_t * bank ) {
493 0 : bank->last_transaction_finished_nanos = fd_log_wallclock();
494 :
495 : /* Set poh hash in bank. */
496 0 : fd_hash_t * poh = fd_sched_get_poh( ctx->sched, bank->idx );
497 0 : bank->f.poh = *poh;
498 :
499 : /* Set shred count in bank. */
500 0 : bank->f.shred_cnt = fd_sched_get_shred_cnt( ctx->sched, bank->idx );
501 :
502 0 : ulong execution_fees_pre_settle = bank->f.execution_fees;
503 0 : ulong priority_fees_pre_settle = bank->f.priority_fees;
504 :
505 : /* Do hashing and other end-of-block processing. */
506 0 : fd_runtime_block_execute_finalize( bank, ctx->accdb, ctx->capture_ctx );
507 :
508 : /* Copy out cost tracker fields before freezing */
509 0 : fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
510 0 : cost_tracker_snap( bank, slot_info );
511 :
512 : /* fetch identity balance infrequently and after set-identity. */
513 0 : slot_info->identity_balance = ULONG_MAX;
514 0 : if( FD_UNLIKELY( ctx->identity_dirty || bank->f.slot%4096UL==0UL ) ) {
515 0 : ctx->identity_dirty = 0;
516 0 : slot_info->identity_balance = fd_accdb_lamports( ctx->accdb, bank->accdb_fork_id, ctx->identity_pubkey->uc );
517 0 : }
518 :
519 : /* Mark the bank as frozen. */
520 0 : bank->f.block_id = ctx->block_id_arr[ bank->idx ].latest_mr;
521 0 : fd_banks_mark_bank_frozen( bank );
522 0 : bank->block_completed_nanos = fd_log_wallclock();
523 :
524 : /**********************************************************************/
525 : /* Bank hash comparison, and halt if there's a mismatch after replay */
526 : /**********************************************************************/
527 :
528 : /* Must be last so we can measure completion time correctly, even
529 : though we could technically do this before the hash cmp and vote
530 : tower stuff. */
531 0 : publish_slot_completed( ctx, stem, bank, 0, 0 /* is_leader */, execution_fees_pre_settle, priority_fees_pre_settle );
532 :
533 : /* If enabled, dump the block to a file and reset the dumping
534 : context state */
535 0 : if( FD_UNLIKELY( ctx->dump_proto_ctx && ctx->dump_proto_ctx->dump_block_to_pb ) ) {
536 0 : fd_dump_block_to_protobuf( ctx->block_dump_ctx, ctx->banks, bank, ctx->accdb, ctx->dump_proto_ctx, ctx->runtime_stack );
537 0 : fd_block_dump_context_reset( ctx->block_dump_ctx );
538 0 : }
539 0 : }
540 :
541 : /**********************************************************************/
542 : /* Leader bank management */
543 : /**********************************************************************/
544 :
545 : static fd_bank_t *
546 : prepare_leader_bank( fd_replay_tile_t * ctx,
547 : fd_bank_t * parent_bank,
548 : ulong slot,
549 0 : long now ) {
550 0 : long before = fd_log_wallclock();
551 :
552 : /* Make sure that we are not already leader. */
553 0 : FD_TEST( ctx->leader_bank==NULL );
554 :
555 0 : ctx->leader_bank = fd_banks_new_bank( ctx->banks, parent_bank->idx, now, 1 );
556 0 : if( FD_UNLIKELY( !ctx->leader_bank ) ) {
557 0 : FD_LOG_CRIT(( "invariant violation: leader bank is NULL for slot %lu", slot ));
558 0 : }
559 :
560 0 : ctx->leader_bank = fd_banks_clone_from_parent( ctx->banks, ctx->leader_bank->idx );
561 0 : if( FD_UNLIKELY( !ctx->leader_bank ) ) {
562 0 : FD_LOG_CRIT(( "invariant violation: bank is NULL for slot %lu", slot ));
563 0 : }
564 :
565 0 : ctx->leader_bank->preparation_begin_nanos = before;
566 :
567 0 : ctx->leader_bank->f.slot = slot;
568 :
569 0 : ctx->leader_bank->txncache_fork_id = fd_txncache_attach_child ( ctx->txncache, parent_bank->txncache_fork_id );
570 0 : ctx->leader_bank->progcache_fork_id = fd_progcache_attach_child( ctx->progcache, parent_bank->progcache_fork_id );
571 0 : ctx->leader_bank->accdb_fork_id = fd_accdb_attach_child ( ctx->accdb, parent_bank->accdb_fork_id );
572 0 : ctx->leader_bank->parent_accdb_fork_id = parent_bank->accdb_fork_id;
573 :
574 0 : int is_epoch_boundary = 0;
575 0 : fd_runtime_block_execute_prepare( ctx->banks, ctx->leader_bank, ctx->accdb, ctx->runtime_stack, ctx->capture_ctx, &is_epoch_boundary );
576 :
577 0 : ulong max_tick_height;
578 0 : if( FD_UNLIKELY( FD_RUNTIME_EXECUTE_SUCCESS!=fd_runtime_compute_max_tick_height( parent_bank->f.ticks_per_slot, slot, &max_tick_height ) ) ) {
579 0 : FD_LOG_CRIT(( "couldn't compute tick height/max tick height slot %lu ticks_per_slot %lu", slot, parent_bank->f.ticks_per_slot ));
580 0 : }
581 0 : ctx->leader_bank->f.max_tick_height = max_tick_height;
582 :
583 : /* Now that a bank has been created for the leader slot, increment the
584 : reference count until we are done with the leader slot. */
585 0 : ctx->leader_bank->refcnt++;
586 :
587 0 : return ctx->leader_bank;
588 0 : }
589 :
590 : static inline void
591 0 : maybe_switch_identity( fd_replay_tile_t * ctx ) {
592 :
593 0 : if( FD_LIKELY( fd_keyswitch_state_query( ctx->keyswitch )!=FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) return;
594 :
595 : /* Switch identity */
596 :
597 0 : FD_LOG_DEBUG(( "keyswitch: switching identity" ));
598 :
599 0 : memcpy( ctx->identity_pubkey, ctx->keyswitch->bytes, 32UL );
600 0 : ctx->identity_dirty = 1;
601 :
602 0 : fd_node_info_write_begin( ctx->node_info );
603 0 : ctx->node_info->info.identity = *ctx->identity_pubkey;
604 0 : fd_node_info_write_end ( ctx->node_info );
605 :
606 0 : fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
607 :
608 : /* The next leader slot will be incorrect now that the identity has
609 : switched. The next leader slot normally gets updated based on the
610 : reset slot returned by tower. */
611 0 : ulong min_leader_slot = fd_ulong_max( ctx->reset_slot+1UL, fd_ulong_if( ctx->highwater_leader_slot==ULONG_MAX, 0UL, ctx->highwater_leader_slot+1UL ) );
612 0 : ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, min_leader_slot, ctx->identity_pubkey );
613 0 : ctx->next_leader_tickcount = LONG_MAX;
614 0 : if( FD_LIKELY( ctx->next_leader_slot != ULONG_MAX && ctx->is_booted ) ) {
615 : /* If we are booted, we have a reset_bank, so use it to set
616 : next_leader_tickcount. If we are not booted, then we don't need
617 : to set next_leader_tickcount as it will be set when we boot. */
618 0 : fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &ctx->reset_block_id, NULL, ctx->block_id_arr );
619 0 : if( FD_LIKELY( block_id_ele ) ) {
620 0 : fd_bank_t * reset_bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) );
621 0 : if( FD_LIKELY( reset_bank && reset_bank->bank_seq==block_id_ele->bank_seq && reset_bank->state!=FD_BANK_STATE_PRUNABLE ) ) {
622 0 : double slot_duration_ticks = (double)reset_bank->f.slot_params.ns_per_slot_adjusted*ctx->tick_per_ns;
623 0 : ctx->next_leader_tickcount = (long)((double)(ctx->next_leader_slot-ctx->reset_slot-1UL)*slot_duration_ticks) + fd_tickcount();
624 0 : }
625 0 : }
626 0 : }
627 :
628 0 : ctx->identity_vote_rooted = 0;
629 0 : ctx->identity_idx++;
630 0 : fd_vote_tracker_reset( ctx->vote_tracker );
631 0 : }
632 :
633 : static int
634 : try_fini_leader( fd_replay_tile_t * ctx,
635 0 : fd_stem_context_t * stem ) {
636 :
637 : /* If we are leader, we can only unbecome the leader iff we have
638 : received the poh hash from the poh tile and block id from reasm.
639 : We have to do an additional check against the slot of the leader
640 : bank because we lazily remove entries from the block id arr. */
641 :
642 0 : if( FD_LIKELY( !ctx->is_leader ) ) return 0;
643 0 : if( !ctx->recv_poh ) return 0;
644 0 : if( !ctx->block_id_arr[ ctx->leader_bank->idx ].block_id_seen ) return 0;
645 0 : if( ctx->block_id_arr[ ctx->leader_bank->idx ].slot!=ctx->leader_bank->f.slot ) return 0;
646 :
647 0 : ctx->leader_bank->last_transaction_finished_nanos = fd_log_wallclock();
648 :
649 0 : ulong curr_slot = ctx->leader_bank->f.slot;
650 :
651 0 : fd_sched_block_add_done( ctx->sched, ctx->leader_bank->idx, ctx->leader_bank->parent_idx, curr_slot );
652 :
653 0 : ulong execution_fees_pre_settle = ctx->leader_bank->f.execution_fees;
654 0 : ulong priority_fees_pre_settle = ctx->leader_bank->f.priority_fees;
655 :
656 0 : fd_runtime_block_execute_finalize( ctx->leader_bank, ctx->accdb, ctx->capture_ctx );
657 :
658 0 : fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
659 0 : cost_tracker_snap( ctx->leader_bank, slot_info );
660 0 : slot_info->identity_balance = ULONG_MAX;
661 0 : if( FD_UNLIKELY( ctx->identity_dirty || curr_slot%4096UL==0UL ) ) {
662 0 : ctx->identity_dirty = 0;
663 0 : slot_info->identity_balance = fd_accdb_lamports( ctx->accdb, ctx->leader_bank->accdb_fork_id, ctx->identity_pubkey->uc );
664 0 : }
665 :
666 0 : ctx->leader_bank->f.block_id = ctx->block_id_arr[ ctx->leader_bank->idx ].latest_mr;
667 0 : fd_banks_mark_bank_frozen( ctx->leader_bank );
668 0 : ctx->leader_bank->block_completed_nanos = fd_log_wallclock();
669 :
670 0 : publish_slot_completed( ctx, stem, ctx->leader_bank, 0, 1 /* is_leader */, execution_fees_pre_settle, priority_fees_pre_settle );
671 :
672 : /* The reference on the bank is finally no longer needed. */
673 0 : ctx->leader_bank->refcnt--;
674 :
675 : /* We are no longer leader so we can clear the bank index we use for
676 : being the leader. */
677 0 : ctx->leader_bank = NULL;
678 0 : ctx->recv_poh = 0;
679 0 : ctx->is_leader = 0;
680 :
681 0 : maybe_switch_identity( ctx );
682 :
683 0 : return 1;
684 0 : }
685 :
686 : static void
687 : publish_root_advanced( fd_replay_tile_t * ctx,
688 : fd_stem_context_t * stem,
689 0 : fd_bank_t * bank ) {
690 :
691 : /* If the new consensus root is in the next epoch from the one the
692 : replay tile currently holds, send the next epoch's leader schedule.
693 : We can't use the new root's parent slot safely here. */
694 0 : if( FD_UNLIKELY( bank->f.epoch>fd_slot_to_epoch( &bank->f.epoch_schedule, ctx->notified_root_slot, NULL ) ) ) {
695 0 : fd_runtime_update_next_leaders( bank, ctx->runtime_stack );
696 0 : publish_epoch_info( ctx, stem, bank, 1 );
697 0 : }
698 :
699 0 : if( ctx->rpc_enabled ) {
700 0 : bank->refcnt++;
701 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for rpc", bank->idx, bank->f.slot, bank->refcnt ));
702 0 : }
703 :
704 : /* Increment the reference count on the consensus root bank to account
705 : for the number of resolv tiles that are waiting on it. */
706 0 : bank->refcnt += ctx->resolv_tile_cnt;
707 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for resolv", bank->idx, bank->f.slot, bank->refcnt ));
708 :
709 0 : fd_replay_root_advanced_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
710 0 : msg->bank_idx = bank->idx;
711 0 : msg->bank_seq = bank->bank_seq;
712 0 : msg->slot = bank->f.slot;
713 0 : msg->bank_hash = bank->f.bank_hash;
714 :
715 0 : fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_ROOT_ADVANCED, ctx->replay_out->chunk, sizeof(fd_replay_root_advanced_t), 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
716 0 : ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(fd_replay_root_advanced_t), ctx->replay_out->chunk0, ctx->replay_out->wmark );
717 0 : }
718 :
719 : /* Determine the default slot params to use for slots where no
720 : reduce_slot_time feature gate is in effect. This is important for
721 : the inflation calculations, which use the slot times for
722 : historical slots as input. Therefore we need the same semantics
723 : as Agave, even after the reduce_slot_time feature gates are
724 : active. */
725 : static fd_slot_params_t
726 0 : restore_default_slot_params( fd_bank_t const * bank ) {
727 :
728 : /* A reduction is effective if the effective ns_per_slot is less than
729 : the 400ms value.
730 : https://github.com/anza-xyz/agave/blob/v4.2/runtime/src/slot_params.rs#L332-L350 */
731 0 : int reduction_effective = fd_slot_params_lookup( &FD_SLOT_PARAMS_400MS,
732 0 : &bank->f.features,
733 0 : &bank->f.epoch_schedule,
734 0 : bank->f.slot ).ns_per_slot < FD_SLOT_PARAMS_400MS.ns_per_slot;
735 :
736 : /* In order to behave correctly in real networks, if a reduction is
737 : effective then we use the 400ms slot params as the default. */
738 0 : if( reduction_effective ) {
739 0 : return FD_SLOT_PARAMS_400MS;
740 0 : }
741 :
742 : /* If a reduction is not effective, then we can rely on the slot
743 : times having remained constant throughout the lifetime of the
744 : cluster, and can use the slot params from the manifest. Note that
745 : in test clusters these may differ from the 400ms values. */
746 0 : return bank->f.slot_params;
747 0 : }
748 :
749 : static void
750 : init_after_snapshot( fd_replay_tile_t * ctx,
751 0 : fd_stem_context_t * stem ) {
752 : /* snapin seeded the root stake delegations from the account stream.
753 : Refresh against the completed accdb to resolve duplicate account
754 : versions, remove stale entries, and calculate activation state. */
755 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, FD_REPLAY_BOOT_BANK_SEQ );
756 0 : if( FD_UNLIKELY( !bank ) ) {
757 0 : FD_LOG_CRIT(( "invariant violation: replay bank is NULL at bank index %lu", FD_REPLAY_BOOT_BANK_SEQ ));
758 0 : }
759 :
760 0 : char const * one_offs[ 16UL ];
761 0 : for( ulong i=0UL; i<ctx->enable_features_cnt; i++ ) one_offs[ i ] = ctx->enable_features[ i ];
762 0 : fd_features_enable_one_offs( &bank->f.features, one_offs, (uint)ctx->enable_features_cnt, 0UL );
763 :
764 : /* Set slot params based on the feature gates in the snapshot,
765 : and assert that these are consistent with the values from the
766 : manifest. These assertions match Agave:
767 : https://github.com/anza-xyz/agave/blob/v4.2/runtime/src/bank.rs#L4839-L4869 */
768 0 : fd_slot_params_t manifest_params = bank->f.slot_params;
769 0 : bank->f.slot_params_default = restore_default_slot_params( bank );
770 0 : bank->f.slot_params = fd_slot_params_at_slot( bank, bank->f.slot );
771 0 : FD_TEST( bank->f.slot_params.ns_per_slot == manifest_params.ns_per_slot );
772 0 : FD_TEST( bank->f.slot_params.slots_per_year == manifest_params.slots_per_year );
773 0 : if( FD_LIKELY( manifest_params.hashes_per_tick ) ) {
774 0 : FD_TEST( bank->f.slot_params.hashes_per_tick==manifest_params.hashes_per_tick );
775 0 : }
776 :
777 0 : fd_runtime_update_next_leaders( bank, ctx->runtime_stack );
778 0 : fd_runtime_update_leaders( bank, ctx->runtime_stack );
779 :
780 : /* Typically, when we cross an epoch boundary during normal
781 : operation, we publish the stake weights for the new epoch. But
782 : since we are starting from a snapshot, we need to publish two
783 : epochs worth of stake weights: the previous epoch (which is
784 : needed for voting on the current epoch), and the current epoch
785 : (which is needed for voting on the next epoch). */
786 0 : publish_epoch_info( ctx, stem, bank, 0 );
787 0 : publish_epoch_info( ctx, stem, bank, 1 );
788 :
789 0 : fd_progcache_reset( ctx->progcache );
790 0 : bank->progcache_fork_id = fd_progcache_fork_id_initial();
791 :
792 0 : bank->f.warmup_cooldown_rate_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, bank->f.features.reduce_stake_warmup_cooldown, NULL );
793 0 : fd_stake_delegations_t * root_delegations = fd_banks_stake_delegations_root_query( ctx->banks );
794 0 : fd_stake_history_t stake_history_[1];
795 0 : fd_stake_history_t const * stake_history = fd_sysvar_cache_stake_history_view( &bank->f.sysvar_cache, stake_history_ );
796 : /* Despite claims like https://github.com/solana-program/stake/pull/81
797 : that the stake history sysvar is contiguous, testnet has in fact
798 : had a gap at epoch 386. */
799 0 : if( FD_UNLIKELY( !fd_sysvar_stake_history_is_contiguous( stake_history ) ) ) {
800 0 : FD_LOG_INFO(( "stake history sysvar (covering epoch %lu to %lu over %lu entries) is not contiguous; some fast paths will be disabled", stake_history->entries[ 0 ].epoch, stake_history->entries[ stake_history->len-1UL ].epoch, stake_history->len ));
801 0 : }
802 0 : fd_stake_delegations_refresh(
803 0 : root_delegations,
804 0 : bank->f.epoch,
805 0 : stake_history, /* may be NULL */
806 0 : &bank->f.warmup_cooldown_rate_epoch,
807 0 : FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ),
808 0 : ctx->accdb,
809 0 : bank->accdb_fork_id );
810 0 : bank->f.total_effective_stake = root_delegations->effective_stake;
811 0 : bank->f.total_activating_stake = root_delegations->activating_stake;
812 0 : bank->f.total_deactivating_stake = root_delegations->deactivating_stake;
813 :
814 0 : fd_vote_stakes_refresh( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, ctx->accdb, bank->accdb_fork_id );
815 :
816 : /* After both snapshots have been loaded in, we can determine if we should
817 : start distributing rewards. */
818 :
819 0 : fd_rewards_recalculate_partitioned_rewards( ctx->banks, bank, ctx->accdb, ctx->runtime_stack, ctx->capture_ctx );
820 :
821 : /* Signals fd_startup_gate */
822 0 : FD_MGAUGE_SET( REPLAY, RUNTIME_STATUS, 1UL );
823 0 : }
824 :
825 : static inline int
826 : try_become_leader( fd_replay_tile_t * ctx,
827 0 : fd_stem_context_t * stem ) {
828 :
829 0 : if( FD_LIKELY( ctx->next_leader_slot==ULONG_MAX ||
830 0 : ctx->is_leader ||
831 0 : (!ctx->identity_vote_rooted && ctx->wait_for_vote_to_start_leader) ||
832 0 : ctx->replay_out->idx==ULONG_MAX ||
833 0 : !ctx->wfs_complete ) ) {
834 0 : return 0;
835 0 : }
836 :
837 : /* If we have evicted the reset bank we can't become leader it may be
838 : inactive or have been resused, we can't become leader. We may miss
839 : our leader slot if we happen to evict our reset bank. As soon as
840 : we re-replay the slot, we will be able to become leader again. */
841 0 : fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &ctx->reset_block_id, NULL, ctx->block_id_arr );
842 0 : if( FD_UNLIKELY( !block_id_ele ) ) return 0;
843 0 : fd_bank_t * reset_bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) );
844 0 : if( FD_UNLIKELY( !reset_bank || reset_bank->bank_seq!=block_id_ele->bank_seq || reset_bank->state==FD_BANK_STATE_PRUNABLE ) ) return 0;
845 :
846 0 : if( FD_UNLIKELY( !fd_banks_can_start_bank( ctx->banks ) ) ) return 0;
847 0 : if( FD_UNLIKELY( ctx->halt_leader ) ) return 0;
848 0 : if( !ctx->supports_leader ) return 0;
849 :
850 0 : FD_TEST( ctx->next_leader_slot>ctx->reset_slot );
851 0 : long now = fd_tickcount();
852 0 : if( FD_LIKELY( now<ctx->next_leader_tickcount ) ) return 0;
853 :
854 : /* If a prior leader is still in the process of publishing their slot,
855 : delay ours to let them finish ... unless they are so delayed that
856 : we risk getting skipped by the leader following us. 3*slot duration
857 : is a reasonable default here, although any value within our leader
858 : span could be considered reasonable. This is arbitrary and chosen
859 : due to intuition.
860 :
861 : If we are becoming leader for a slot at an epoch boundary where a
862 : slot time reduction is taking effect, we have a choice for the
863 : grace period: give the previous leader maximal time to complete
864 : their slot, or use a shorter grace period reflecting the new
865 : shorter slot duration that the next leader after us will use to
866 : time out our slot. We choose the latter to minimize the risk of
867 : our slot getting skipped: a grace period based on the new shorter
868 : slot duration at next_leader_slot. This only matters for epoch
869 : boundaries where a slot time reduction is taking effect, so either
870 : choice is defensible. */
871 0 : ulong ns_per_slot_adjusted = fd_slot_params_at_slot( reset_bank, ctx->next_leader_slot ).ns_per_slot_adjusted;
872 0 : double slot_duration_ticks = (double)ns_per_slot_adjusted*ctx->tick_per_ns;
873 0 : if( FD_UNLIKELY( now<ctx->next_leader_tickcount+(long)(3.0*slot_duration_ticks) ) ) {
874 : /* TODO: Make the max_active_descendant calculation more efficient
875 : by caching it in the bank structure and updating it as banks are
876 : created and completed. */
877 0 : ulong max_active_descendant = 0UL;
878 0 : ulong child_idx = reset_bank->child_idx;
879 0 : while( child_idx!=ULONG_MAX ) {
880 0 : fd_bank_t * child_bank = fd_banks_bank_query( ctx->banks, child_idx );
881 0 : max_active_descendant = fd_ulong_max( max_active_descendant, child_bank->f.slot );
882 0 : child_idx = child_bank->sibling_idx;
883 0 : }
884 :
885 : /* If the max_active_descendant is >= next_leader_slot, we waited
886 : too long and a leader after us started publishing to try and skip
887 : us. Just start our leader slot immediately, we might win ... */
888 0 : if( FD_LIKELY( max_active_descendant>=ctx->reset_slot && max_active_descendant<ctx->next_leader_slot ) ) {
889 : /* If one of the leaders between the reset slot and our leader
890 : slot is in the process of publishing (they have a descendant
891 : bank that is in progress of being replayed), then keep waiting.
892 : We probably wouldn't get a leader slot out before they
893 : finished.
894 :
895 : Unless... we are past the deadline to start our slot by more
896 : than 3*(slot duration), in which case we should probably start
897 : it to avoid getting skipped by the leader behind us. */
898 0 : return 0;
899 0 : }
900 0 : }
901 :
902 : /* If we haven't started replaying the prior block, but we have
903 : finished replaying the second to last slot of the prior
904 : leader (and that leader is not us), we should give the prior leader
905 : a little more time. */
906 0 : if( FD_UNLIKELY( ctx->next_leader_slot==ctx->reset_slot+2UL && now<ctx->next_leader_tickcount+(long)(1.0*slot_duration_ticks) ) ) {
907 :
908 0 : fd_pubkey_t const * reset_leader = fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, ctx->reset_slot );
909 0 : if( FD_UNLIKELY( reset_leader && !fd_memeq( reset_leader, ctx->identity_pubkey, 32UL ) ) ) return 0;
910 0 : }
911 :
912 0 : long now_nanos = fd_log_wallclock();
913 :
914 0 : ctx->is_leader = 1;
915 0 : ctx->recv_poh = 0;
916 :
917 0 : FD_TEST( ctx->highwater_leader_slot==ULONG_MAX || ctx->highwater_leader_slot<ctx->next_leader_slot );
918 0 : ctx->highwater_leader_slot = ctx->next_leader_slot;
919 :
920 0 : FD_LOG_INFO(( "becoming leader for slot %lu, parent slot is %lu", ctx->next_leader_slot, ctx->reset_slot ));
921 :
922 0 : fd_bank_t * bank = prepare_leader_bank( ctx, reset_bank, ctx->next_leader_slot, now_nanos );
923 :
924 0 : fd_bundle_crank_tip_payment_config_t config[1] = { 0 };
925 0 : fd_pubkey_t tip_receiver_owner = {0};
926 :
927 0 : if( FD_UNLIKELY( ctx->bundle.enabled ) ) {
928 0 : fd_acct_addr_t tip_payment_config[1];
929 0 : fd_acct_addr_t tip_receiver[1];
930 0 : fd_bundle_crank_get_addresses( ctx->bundle.gen, bank->f.epoch, tip_payment_config, tip_receiver );
931 :
932 0 : fd_acc_t tip_config_acc = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, tip_payment_config->b );
933 0 : if( FD_UNLIKELY( !tip_config_acc.lamports ) ) {
934 0 : FD_BASE58_ENCODE_32_BYTES( tip_payment_config->b, tip_config_acc_b58 );
935 0 : FD_LOG_WARNING(( "tip payment config account %s does not exist", tip_config_acc_b58 ));
936 0 : fd_accdb_unread_one( ctx->accdb, &tip_config_acc );
937 0 : } else if( FD_UNLIKELY( tip_config_acc.data_len<sizeof(fd_bundle_crank_tip_payment_config_t) ) ) {
938 0 : FD_LOG_HEXDUMP_WARNING(( "invalid tip payment config account data", tip_config_acc.data, tip_config_acc.data_len ));
939 0 : fd_accdb_unread_one( ctx->accdb, &tip_config_acc );
940 0 : } else {
941 0 : memcpy( config, tip_config_acc.data, sizeof(fd_bundle_crank_tip_payment_config_t) );
942 0 : fd_accdb_unread_one( ctx->accdb, &tip_config_acc );
943 0 : }
944 :
945 : /* It is possible that the tip receiver account does not exist yet
946 : if it is the first time in an epoch. */
947 0 : fd_acc_t tip_receiver_acc = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, tip_receiver->b );
948 0 : if( FD_LIKELY( tip_receiver_acc.lamports ) ) {
949 0 : fd_memcpy( tip_receiver_owner.uc, tip_receiver_acc.owner, 32UL );
950 0 : }
951 0 : fd_accdb_unread_one( ctx->accdb, &tip_receiver_acc );
952 0 : }
953 :
954 :
955 0 : fd_became_leader_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
956 0 : msg->slot = ctx->next_leader_slot;
957 0 : msg->slot_start_ns = now_nanos;
958 0 : msg->slot_end_ns = now_nanos+(long)bank->f.slot_params.ns_per_slot_adjusted;
959 0 : msg->bank = NULL;
960 0 : msg->bank_idx = bank->idx;
961 0 : msg->bank_seq = bank->bank_seq;
962 0 : msg->ticks_per_slot = bank->f.ticks_per_slot;
963 0 : msg->hashcnt_per_tick = bank->f.slot_params.hashes_per_tick;
964 0 : msg->tick_duration_ns = bank->f.slot_params.ns_per_slot_adjusted/msg->ticks_per_slot;
965 0 : msg->bundle->config[0] = config[0];
966 0 : memcpy( msg->bundle->last_blockhash, bank->f.poh.hash, sizeof(fd_hash_t) );
967 0 : memcpy( msg->bundle->tip_receiver_owner, tip_receiver_owner.uc, sizeof(fd_pubkey_t) );
968 :
969 0 : if( FD_UNLIKELY( msg->hashcnt_per_tick==1UL ) ) {
970 : /* Low power producer, maximum of one microblock per tick in the slot */
971 0 : msg->max_microblocks_in_slot = msg->ticks_per_slot;
972 0 : } else {
973 : /* See the long comment in after_credit for this limit */
974 0 : msg->max_microblocks_in_slot = fd_ulong_min( MAX_MICROBLOCKS_PER_SLOT, msg->ticks_per_slot*(msg->hashcnt_per_tick-1UL) );
975 0 : }
976 :
977 0 : msg->total_skipped_ticks = msg->ticks_per_slot*(ctx->next_leader_slot-ctx->reset_slot);
978 0 : msg->epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, ctx->next_leader_slot, NULL );
979 :
980 0 : fd_cost_tracker_t const * cost_tracker = fd_bank_cost_tracker_query( bank );
981 :
982 0 : msg->limits.slot_max_cost = ctx->larger_max_cost_per_block ? LARGER_MAX_COST_PER_BLOCK : cost_tracker->block_cost_limit;
983 0 : msg->limits.slot_max_vote_cost = cost_tracker->vote_cost_limit;
984 0 : msg->limits.slot_max_write_cost_per_acct = cost_tracker->account_cost_limit;
985 0 : msg->limits.slot_max_allocated_data_per_block = cost_tracker->data_size_limit;
986 0 : msg->limits.slot_max_data_shreds = bank->f.slot_params.max_shred_idx;
987 :
988 0 : if( FD_UNLIKELY( msg->ticks_per_slot+msg->total_skipped_ticks>USHORT_MAX ) ) {
989 : /* There can be at most USHORT_MAX skipped ticks, because the
990 : parent_offset field in the shred data is only 2 bytes wide. */
991 0 : FD_LOG_ERR(( "too many skipped ticks %lu for slot %lu, chain must halt", msg->ticks_per_slot+msg->total_skipped_ticks, ctx->next_leader_slot ));
992 0 : }
993 :
994 0 : fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_BECAME_LEADER, ctx->replay_out->chunk, sizeof(fd_became_leader_t), 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
995 0 : ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(fd_became_leader_t), ctx->replay_out->chunk0, ctx->replay_out->wmark );
996 :
997 0 : ctx->next_leader_slot = ULONG_MAX;
998 0 : ctx->next_leader_tickcount = LONG_MAX;
999 :
1000 0 : return 1;
1001 0 : }
1002 :
1003 : static void
1004 : process_poh_message( fd_replay_tile_t * ctx,
1005 0 : fd_poh_leader_slot_ended_t const * slot_ended ) {
1006 :
1007 0 : FD_TEST( ctx->is_booted );
1008 0 : FD_TEST( ctx->is_leader );
1009 0 : FD_TEST( ctx->leader_bank!=NULL );
1010 :
1011 0 : FD_TEST( ctx->highwater_leader_slot>=slot_ended->slot );
1012 0 : FD_TEST( ctx->next_leader_slot>ctx->highwater_leader_slot );
1013 :
1014 : /* Update the poh hash in the bank. We will want to maintain a refcnt
1015 : on the bank until we have received the block id for the block after
1016 : it has been shredded. */
1017 :
1018 0 : memcpy( &ctx->leader_bank->f.poh, slot_ended->blockhash, sizeof(fd_hash_t) );
1019 :
1020 0 : ctx->recv_poh = 1;
1021 0 : }
1022 :
1023 : static void
1024 : publish_reset( fd_replay_tile_t * ctx,
1025 : fd_stem_context_t * stem,
1026 0 : fd_bank_t * bank ) {
1027 0 : if( FD_UNLIKELY( ctx->replay_out->idx==ULONG_MAX ) ) return;
1028 :
1029 0 : fd_hash_t const * block_hash = fd_blockhashes_peek_last_hash( &bank->f.block_hash_queue );
1030 0 : FD_TEST( block_hash );
1031 :
1032 0 : fd_poh_reset_t * reset = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
1033 :
1034 0 : reset->bank_idx = bank->idx;
1035 0 : reset->timestamp = fd_log_wallclock();
1036 0 : reset->completed_slot = bank->f.slot;
1037 0 : reset->hashcnt_per_tick = bank->f.slot_params.hashes_per_tick;
1038 0 : reset->ticks_per_slot = bank->f.ticks_per_slot;
1039 0 : reset->tick_duration_ns = bank->f.slot_params.ns_per_slot_adjusted/reset->ticks_per_slot;
1040 0 : fd_memcpy( reset->completed_block_id, ctx->reset_block_id.uc, sizeof(fd_hash_t) );
1041 0 : fd_memcpy( reset->completed_blockhash, block_hash->uc, sizeof(fd_hash_t) );
1042 :
1043 0 : ulong ticks_per_slot = bank->f.ticks_per_slot;
1044 0 : if( FD_UNLIKELY( reset->hashcnt_per_tick==1UL ) ) {
1045 : /* Low power producer, maximum of one microblock per tick in the slot */
1046 0 : reset->max_microblocks_in_slot = ticks_per_slot;
1047 0 : } else {
1048 : /* See the long comment in after_credit for this limit */
1049 0 : reset->max_microblocks_in_slot = fd_ulong_min( MAX_MICROBLOCKS_PER_SLOT, ticks_per_slot*(reset->hashcnt_per_tick-1UL) );
1050 0 : }
1051 0 : reset->next_leader_slot = ctx->next_leader_slot;
1052 0 : reset->wfs_paused = !ctx->wfs_complete;
1053 :
1054 0 : fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_RESET, ctx->replay_out->chunk, sizeof(fd_poh_reset_t), 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
1055 0 : ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(fd_poh_reset_t), ctx->replay_out->chunk0, ctx->replay_out->wmark );
1056 0 : }
1057 :
1058 : static void
1059 : store_xinsert( fd_store_t * store,
1060 0 : fd_hash_t const * merkle_root ) {
1061 0 : fd_store_pool_t pool = {
1062 0 : .pool = fd_wksp_laddr_fast( fd_store_wksp( store ), store->pool_mem_gaddr ),
1063 0 : .ele = fd_wksp_laddr_fast( fd_store_wksp( store ), store->pool_ele_gaddr ),
1064 0 : .ele_max = store->fec_max
1065 0 : };
1066 0 : fd_store_fec_t * fec = fd_store_pool_acquire( &pool );
1067 0 : if( FD_UNLIKELY( !fec ) ) FD_LOG_CRIT(( "fd_store_pool_acquire failed" ));
1068 0 : fec->key.merkle_root = *merkle_root;
1069 0 : fec->key.part_idx = 0;
1070 0 : fec->next = fd_store_pool_idx_null();
1071 0 : fec->data_sz = 0UL;
1072 :
1073 0 : FD_STORE_XLOCK_BEGIN( store ) {
1074 0 : fd_store_map_ele_insert( fd_wksp_laddr_fast( fd_store_wksp( store ), store->map_gaddr ), fec, pool.ele );
1075 0 : } FD_STORE_XLOCK_END;
1076 0 : }
1077 :
1078 : static void
1079 : boot_genesis( fd_replay_tile_t * ctx,
1080 : fd_stem_context_t * stem,
1081 0 : fd_genesis_meta_t const * meta ) {
1082 : /* If we are bootstrapping, we can't wait to wait for our identity
1083 : vote to be rooted as this creates a circular dependency. */
1084 0 : ctx->identity_vote_rooted = 1;
1085 :
1086 0 : ctx->caught_up = 1;
1087 :
1088 0 : uchar const * genesis_blob = (uchar const *)( meta+1 );
1089 0 : FD_TEST( meta->bootstrap && meta->has_lthash );
1090 0 : FD_TEST( fd_genesis_parse( ctx->genesis, genesis_blob, meta->blob_sz ) );
1091 :
1092 0 : fd_bank_t * bank = fd_banks_init_bank( ctx->banks );
1093 0 : FD_TEST( bank );
1094 0 : bank->f.slot = 0UL;
1095 0 : FD_TEST( bank->idx==FD_REPLAY_BOOT_BANK_SEQ );
1096 :
1097 0 : static const fd_accdb_fork_id_t accdb_root = { .val = USHORT_MAX };
1098 0 : bank->accdb_fork_id = fd_accdb_attach_child( ctx->accdb, accdb_root );
1099 0 : bank->parent_accdb_fork_id = bank->accdb_fork_id;
1100 :
1101 0 : fd_runtime_read_genesis( ctx->banks, bank, ctx->accdb, NULL, &meta->genesis_hash, &meta->lthash, ctx->genesis, genesis_blob, ctx->runtime_stack );
1102 :
1103 0 : bank->txncache_fork_id = fd_txncache_attach_child ( ctx->txncache, (fd_txncache_fork_id_t){USHORT_MAX} );
1104 0 : bank->progcache_fork_id = fd_progcache_attach_child( ctx->progcache, fd_progcache_fork_id_initial() );
1105 :
1106 0 : fd_hash_t const * block_hash = fd_blockhashes_peek_last_hash( &bank->f.block_hash_queue );
1107 0 : fd_txncache_finalize_fork( ctx->txncache, bank->txncache_fork_id, 0UL, block_hash->uc );
1108 :
1109 : /* We call this after fd_runtime_read_genesis, which sets up the
1110 : slot_bank needed in blockstore_init. */
1111 0 : init_after_snapshot( ctx, stem );
1112 :
1113 0 : ctx->published_root_slot = 0UL;
1114 0 : fd_sched_block_add_done( ctx->sched, bank->idx, ULONG_MAX, 0UL );
1115 :
1116 0 : bank->f.block_height = 1UL;
1117 :
1118 0 : ctx->consensus_root = ctx->initial_block_id;
1119 0 : ctx->consensus_root_slot = 0UL;
1120 0 : ctx->notified_root = ctx->initial_block_id;
1121 0 : ctx->notified_root_slot = 0UL;
1122 0 : ctx->notified_root_bank = bank;
1123 0 : ctx->published_root_slot = 0UL;
1124 0 : ctx->published_root_bank_idx = 0UL;
1125 0 : if( FD_UNLIKELY( ctx->snapmk.full_interval ) ) {
1126 0 : ctx->snapmk.next_full_slot = ctx->snapmk.full_interval;
1127 0 : }
1128 0 : if( FD_UNLIKELY( ctx->snapmk.incremental_interval ) ) {
1129 0 : ctx->snapmk.next_incremental_slot = ctx->snapmk.incremental_interval;
1130 0 : }
1131 :
1132 0 : ctx->reset_slot = 0UL;
1133 0 : ctx->reset_block_id = ctx->initial_block_id;
1134 0 : ctx->reset_timestamp_nanos = fd_log_wallclock();
1135 0 : ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, 1UL, ctx->identity_pubkey );
1136 0 : if( FD_LIKELY( ctx->next_leader_slot != ULONG_MAX ) ) {
1137 0 : double slot_duration_ticks = (double)bank->f.slot_params.ns_per_slot_adjusted*ctx->tick_per_ns;
1138 0 : ctx->next_leader_tickcount = (long)((double)(ctx->next_leader_slot-ctx->reset_slot-1UL)*slot_duration_ticks) + fd_tickcount();
1139 0 : } else {
1140 0 : ctx->next_leader_tickcount = LONG_MAX;
1141 0 : }
1142 :
1143 0 : ctx->has_cluster_type = 1;
1144 :
1145 0 : ctx->is_booted = 1;
1146 0 : try_become_leader( ctx, stem );
1147 :
1148 0 : fd_hash_t initial_block_id = ctx->initial_block_id;
1149 0 : fd_reasm_fec_t * fec = fd_reasm_init( ctx->reasm, &initial_block_id, 0 /* genesis slot */ );
1150 0 : fec->bank_idx = bank->idx;
1151 0 : fec->bank_seq = bank->bank_seq;
1152 0 : store_xinsert( ctx->store, &initial_block_id );
1153 :
1154 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ 0 ];
1155 0 : block_id_ele->latest_mr = initial_block_id;
1156 0 : block_id_ele->slot = 0UL;
1157 0 : block_id_ele->bank_seq = bank->bank_seq;
1158 0 : bank->f.block_id = initial_block_id;
1159 :
1160 0 : FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, block_id_ele, ctx->block_id_arr ) );
1161 :
1162 0 : fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
1163 0 : cost_tracker_snap( bank, slot_info );
1164 :
1165 0 : slot_info->identity_balance = fd_accdb_lamports( ctx->accdb, bank->accdb_fork_id, ctx->identity_pubkey->uc );
1166 :
1167 0 : publish_slot_completed( ctx, stem, bank, 1, 0 /* is_leader */, 0, 0 );
1168 0 : publish_root_advanced( ctx, stem, bank );
1169 0 : publish_reset( ctx, stem, bank );
1170 0 : }
1171 :
1172 : static inline void
1173 0 : maybe_verify_cluster_type( fd_replay_tile_t * ctx ) {
1174 0 : if( FD_UNLIKELY( !ctx->has_cluster_type || !ctx->has_genesis_hash ) ) {
1175 0 : return;
1176 0 : }
1177 :
1178 0 : FD_BASE58_ENCODE_32_BYTES( ctx->genesis_hash->uc, hash_cstr );
1179 0 : ulong cluster = fd_genesis_cluster_identify( hash_cstr );
1180 : /* Map pyth-related clusters to unknown. */
1181 0 : switch( cluster ) {
1182 0 : case FD_CLUSTER_PYTHNET:
1183 0 : case FD_CLUSTER_PYTHTEST:
1184 0 : cluster = FD_CLUSTER_UNKNOWN;
1185 0 : }
1186 :
1187 0 : if( FD_UNLIKELY( cluster!=ctx->cluster_type ) ) {
1188 0 : FD_LOG_ERR(( "Your genesis.bin file at `%s` has a genesis hash of `%s` which means the cluster is %s "
1189 0 : "but the snapshot you loaded is for a different cluster %s. If you are trying to join the "
1190 0 : "%s cluster, you can delete the genesis.bin file and restart the node to download the correct "
1191 0 : "genesis file automatically.",
1192 0 : ctx->genesis_path,
1193 0 : hash_cstr,
1194 0 : fd_genesis_cluster_name( cluster ),
1195 0 : fd_genesis_cluster_name( ctx->cluster_type ),
1196 0 : fd_genesis_cluster_name( cluster ) ));
1197 0 : }
1198 0 : }
1199 :
1200 : static void
1201 : on_snapshot_message( fd_replay_tile_t * ctx,
1202 : fd_stem_context_t * stem,
1203 : ulong in_idx,
1204 : ulong chunk,
1205 0 : ulong sig ) {
1206 0 : ulong msg = fd_ssmsg_sig_message( sig );
1207 0 : if( FD_LIKELY( msg==FD_SSMSG_DONE ) ) {
1208 : /* An end of message notification indicates the snapshot is loaded.
1209 : Replay is able to start executing from this point onwards. */
1210 : /* TODO: replay should finish booting. Could make replay a
1211 : state machine and set the state here accordingly. */
1212 0 : ctx->is_booted = 1;
1213 :
1214 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, FD_REPLAY_BOOT_BANK_SEQ );
1215 0 : if( FD_UNLIKELY( !bank ) ) {
1216 0 : FD_LOG_CRIT(( "invariant violation: bank is NULL for bank index %lu", FD_REPLAY_BOOT_BANK_SEQ ));
1217 0 : }
1218 :
1219 0 : ulong snapshot_slot = bank->f.slot;
1220 :
1221 0 : fd_hash_t bank_hash = bank->f.bank_hash;
1222 0 : if( FD_UNLIKELY( ctx->wfs_enabled && memcmp( ctx->expected_bank_hash.uc, bank_hash.uc, sizeof(fd_hash_t) ) ) ) {
1223 0 : FD_BASE58_ENCODE_32_BYTES( ctx->expected_bank_hash.uc, expected_bank_hash_cstr );
1224 0 : FD_BASE58_ENCODE_32_BYTES( bank_hash.uc, actual_bank_hash_cstr );
1225 0 : FD_LOG_ERR(( "[consensus.wait_for_supermajority_with_bank_hash] expected_bank_hash=%s does not match snapshot slot"
1226 0 : "=%lu bank_hash=%s. If you are loading a snapshot from the network, check that the slot matches the "
1227 0 : "cluster restart slot. ", expected_bank_hash_cstr, snapshot_slot, actual_bank_hash_cstr ));
1228 0 : }
1229 0 : if( FD_UNLIKELY( ctx->wfs_enabled ) ) {
1230 0 : FD_LOG_NOTICE(( "waiting for supermajority at snapshot slot %lu", snapshot_slot ));
1231 0 : }
1232 :
1233 : /* Manifest message must arrive before DONE */
1234 0 : if( FD_UNLIKELY( !ctx->has_expected_genesis_timestamp ) ) {
1235 0 : FD_LOG_CRIT(( "snapshot DONE received before manifest" ));
1236 0 : }
1237 :
1238 : /* FIXME: This is a hack when the block id of the snapshot slot
1239 : is not provided in the snapshot (Agave versions <4.1). A
1240 : possible solution is to get the block id of the snapshot slot
1241 : from repair. */
1242 0 : fd_hash_t manifest_block_id = ctx->has_manifest_block_id ? ctx->manifest_block_id : ctx->initial_block_id;
1243 :
1244 0 : FD_TEST( fd_sysvar_cache_restore( bank, ctx->accdb ) );
1245 : /* Agave zeroes manifest rent_params; reload from sysvar account */
1246 0 : FD_TEST( fd_sysvar_rent_read( ctx->accdb, bank->accdb_fork_id, &bank->f.rent ) );
1247 :
1248 0 : ctx->consensus_root = manifest_block_id;
1249 0 : ctx->consensus_root_slot = snapshot_slot;
1250 0 : ctx->notified_root = manifest_block_id;
1251 0 : ctx->notified_root_slot = snapshot_slot;
1252 0 : ctx->notified_root_bank = bank;
1253 0 : ctx->published_root_slot = ctx->consensus_root_slot;
1254 0 : ctx->published_root_bank_idx = 0UL;
1255 0 : if( FD_UNLIKELY( ctx->snapmk.full_interval ) ) {
1256 0 : ctx->snapmk.next_full_slot = ((snapshot_slot/ctx->snapmk.full_interval)+1UL)*ctx->snapmk.full_interval;
1257 0 : }
1258 0 : if( FD_UNLIKELY( ctx->snapmk.incremental_interval ) ) {
1259 0 : ctx->snapmk.next_incremental_slot = ((snapshot_slot/ctx->snapmk.incremental_interval)+1UL)*ctx->snapmk.incremental_interval;
1260 0 : }
1261 :
1262 0 : ctx->reset_slot = snapshot_slot;
1263 0 : ctx->reset_block_id = manifest_block_id;
1264 0 : ctx->reset_timestamp_nanos = fd_log_wallclock();
1265 0 : ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, 1UL, ctx->identity_pubkey );
1266 :
1267 0 : fd_sched_block_add_done( ctx->sched, bank->idx, ULONG_MAX, snapshot_slot );
1268 0 : FD_TEST( bank->idx==0UL );
1269 :
1270 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ 0 ];
1271 0 : block_id_ele->latest_mr = manifest_block_id;
1272 0 : block_id_ele->slot = snapshot_slot;
1273 0 : block_id_ele->bank_seq = bank->bank_seq;
1274 0 : block_id_ele->block_id_seen = 1;
1275 0 : block_id_ele->latest_fec_idx = 0U;
1276 0 : bank->f.block_id = manifest_block_id;
1277 0 : FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, block_id_ele, ctx->block_id_arr ) );
1278 :
1279 : /* We call this after fd_runtime_read_genesis, which sets up the
1280 : slot_bank needed in blockstore_init. */
1281 0 : init_after_snapshot( ctx, stem );
1282 :
1283 0 : if( FD_LIKELY( ctx->next_leader_slot != ULONG_MAX ) ) {
1284 0 : double slot_duration_ticks = (double)bank->f.slot_params.ns_per_slot_adjusted*ctx->tick_per_ns;
1285 0 : ctx->next_leader_tickcount = (long)((double)(ctx->next_leader_slot-ctx->reset_slot-1UL)*slot_duration_ticks) + fd_tickcount();
1286 0 : } else {
1287 0 : ctx->next_leader_tickcount = LONG_MAX;
1288 0 : }
1289 :
1290 0 : fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
1291 0 : cost_tracker_snap( bank, slot_info );
1292 :
1293 0 : slot_info->identity_balance = fd_accdb_lamports( ctx->accdb, bank->accdb_fork_id, ctx->identity_pubkey->uc );
1294 :
1295 0 : publish_slot_completed( ctx, stem, bank, 1, 0 /* is_leader */, 0, 0 );
1296 0 : publish_root_advanced( ctx, stem, bank );
1297 :
1298 0 : fd_reasm_fec_t * fec = fd_reasm_init( ctx->reasm, &manifest_block_id, snapshot_slot );
1299 0 : fec->bank_idx = bank->idx;
1300 0 : fec->bank_seq = bank->bank_seq;
1301 0 : store_xinsert( ctx->store, &manifest_block_id );
1302 :
1303 0 : return;
1304 0 : }
1305 :
1306 0 : switch( msg ) {
1307 0 : case FD_SSMSG_MANIFEST_FULL:
1308 0 : case FD_SSMSG_MANIFEST_INCREMENTAL: {
1309 : /* We may either receive a full snapshot manifest or an
1310 : incremental snapshot manifest. Note that this external message
1311 : id is only used temporarily because replay cannot yet receive
1312 : the firedancer-internal snapshot manifest message. */
1313 0 : if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark ) )
1314 0 : FD_LOG_ERR(( "chunk %lu from in %d corrupt, not in range [%lu,%lu]", chunk, ctx->in_kind[ in_idx ], ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
1315 :
1316 : /* Malformed manifests are rejected recoverably by snapin via
1317 : fd_ssload_manifest_validate. If recover fails here, then the
1318 : bank is partially mutated, and we must abort. */
1319 0 : if( FD_UNLIKELY( fd_ssload_recover( fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ),
1320 0 : ctx->banks,
1321 0 : fd_banks_bank_query( ctx->banks, FD_REPLAY_BOOT_BANK_SEQ ),
1322 0 : ctx->blockhash_seed ) ) ) {
1323 0 : FD_LOG_ERR(( "Snapshot manifest recovery failed, aborting." ));
1324 0 : }
1325 :
1326 0 : ctx->has_cluster_type = 1;
1327 0 : ctx->cluster_type = fd_banks_bank_query( ctx->banks, FD_REPLAY_BOOT_BANK_SEQ )->f.cluster_type;
1328 :
1329 0 : fd_snapshot_manifest_t const * manifest = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
1330 : /* hard_fork_cnt already validated by fd_ssload_recover. */
1331 0 : ctx->hard_fork_cnt = manifest->hard_fork_cnt;
1332 0 : for( ulong i=0UL; i<manifest->hard_fork_cnt; i++ ) {
1333 0 : ctx->hard_forks[ i ] = manifest->hard_forks[ i ];
1334 0 : }
1335 0 : ctx->has_expected_genesis_timestamp = 1;
1336 0 : ctx->expected_genesis_timestamp = manifest->creation_time_seconds;
1337 0 : ctx->has_manifest_block_id = manifest->has_block_id;
1338 0 : if( manifest->has_block_id ) memcpy( ctx->manifest_block_id.uc, manifest->block_id, 32UL );
1339 0 : if( FD_UNLIKELY( msg==FD_SSMSG_MANIFEST_FULL ) ) {
1340 0 : ctx->snapmk.base_slot = manifest->slot;
1341 0 : }
1342 0 : break;
1343 0 : }
1344 0 : default: {
1345 0 : FD_LOG_ERR(( "Received unknown snapshot message with msg %lu", msg ));
1346 0 : return;
1347 0 : }
1348 0 : }
1349 :
1350 0 : return;
1351 0 : }
1352 :
1353 : static void
1354 : dispatch_task( fd_replay_tile_t * ctx,
1355 : fd_stem_context_t * stem,
1356 0 : fd_sched_task_t * task ) {
1357 :
1358 0 : switch( task->task_type ) {
1359 0 : case FD_SCHED_TT_TXN_EXEC: {
1360 0 : fd_txn_p_t * txn_p = fd_sched_get_txn( ctx->sched, task->txn_exec->txn_idx );
1361 :
1362 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->txn_exec->bank_idx );
1363 0 : FD_TEST( bank );
1364 :
1365 : /* Add the transaction to the block dumper if necessary. This
1366 : logic doesn't need to be fork-aware since it's only meant to
1367 : be used in backtest. */
1368 0 : if( FD_UNLIKELY( ctx->dump_proto_ctx && ctx->dump_proto_ctx->dump_block_to_pb ) ) {
1369 0 : fd_dump_block_to_protobuf_collect_tx( ctx->block_dump_ctx, txn_p );
1370 0 : }
1371 :
1372 0 : bank->refcnt++;
1373 :
1374 0 : if( FD_UNLIKELY( !bank->first_transaction_scheduled_nanos ) ) bank->first_transaction_scheduled_nanos = fd_log_wallclock();
1375 :
1376 0 : fd_replay_out_link_t * exec_out = ctx->exec_out;
1377 0 : fd_execrp_txn_exec_msg_t * exec_msg = fd_chunk_to_laddr( exec_out->mem, exec_out->chunk );
1378 0 : memcpy( exec_msg->txn, txn_p, sizeof(fd_txn_p_t) );
1379 0 : exec_msg->bank_idx = task->txn_exec->bank_idx;
1380 0 : exec_msg->txn_idx = task->txn_exec->txn_idx;
1381 0 : memcpy( exec_msg->fec_merkle_root, ctx->block_id_arr[ task->txn_exec->bank_idx ].latest_mr.uc, 32UL );
1382 0 : exec_msg->index_in_slot = fd_sched_get_txn_info( ctx->sched, task->txn_exec->txn_idx )->index_in_slot;
1383 0 : if( FD_UNLIKELY( ctx->capture_ctx ) ) {
1384 0 : exec_msg->capture_txn_idx = ctx->capture_ctx->current_txn_idx++;
1385 0 : }
1386 0 : fd_stem_publish( stem, exec_out->idx, (FD_EXECRP_TT_TXN_EXEC<<32) | task->txn_exec->exec_idx, exec_out->chunk, sizeof(*exec_msg), 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
1387 0 : exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*exec_msg), exec_out->chunk0, exec_out->wmark );
1388 0 : break;
1389 0 : }
1390 0 : case FD_SCHED_TT_TXN_SIGVERIFY: {
1391 0 : fd_txn_p_t * txn_p = fd_sched_get_txn( ctx->sched, task->txn_sigverify->txn_idx );
1392 :
1393 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->txn_sigverify->bank_idx );
1394 0 : FD_TEST( bank );
1395 0 : bank->refcnt++;
1396 :
1397 0 : fd_replay_out_link_t * exec_out = ctx->exec_out;
1398 0 : fd_execrp_txn_sigverify_msg_t * exec_msg = fd_chunk_to_laddr( exec_out->mem, exec_out->chunk );
1399 0 : memcpy( exec_msg->txn, txn_p, sizeof(fd_txn_p_t) );
1400 0 : exec_msg->bank_idx = task->txn_sigverify->bank_idx;
1401 0 : exec_msg->txn_idx = task->txn_sigverify->txn_idx;
1402 0 : fd_stem_publish( stem, exec_out->idx, (FD_EXECRP_TT_TXN_SIGVERIFY<<32) | task->txn_sigverify->exec_idx, exec_out->chunk, sizeof(*exec_msg), 0UL, 0UL, 0UL );
1403 0 : exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*exec_msg), exec_out->chunk0, exec_out->wmark );
1404 0 : break;
1405 0 : };
1406 0 : case FD_SCHED_TT_POH_HASH: {
1407 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->poh_hash->bank_idx );
1408 0 : FD_TEST( bank );
1409 0 : bank->refcnt++;
1410 :
1411 0 : fd_replay_out_link_t * exec_out = ctx->exec_out;
1412 0 : fd_execrp_poh_hash_msg_t * exec_msg = fd_chunk_to_laddr( exec_out->mem, exec_out->chunk );
1413 0 : exec_msg->bank_idx = task->poh_hash->bank_idx;
1414 0 : exec_msg->mblk_idx = task->poh_hash->mblk_idx;
1415 0 : exec_msg->hashcnt = task->poh_hash->hashcnt;
1416 0 : memcpy( exec_msg->hash, task->poh_hash->hash, sizeof(fd_hash_t) );
1417 0 : fd_stem_publish( stem, exec_out->idx, (FD_EXECRP_TT_POH_HASH<<32) | task->poh_hash->exec_idx, exec_out->chunk, sizeof(*exec_msg), 0UL, 0UL, 0UL );
1418 0 : exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*exec_msg), exec_out->chunk0, exec_out->wmark );
1419 0 : break;
1420 0 : };
1421 0 : default: {
1422 0 : FD_LOG_CRIT(( "unexpected task type %lu", task->task_type ));
1423 0 : }
1424 0 : }
1425 0 : }
1426 :
1427 : static void
1428 : mark_bank_dead( fd_replay_tile_t * ctx,
1429 : fd_stem_context_t * stem,
1430 0 : ulong bank_idx ) {
1431 0 : ulong dead_idxs[ FD_BANKS_MAX_BANKS ];
1432 0 : ulong dead_idxs_cnt = 0UL;
1433 0 : fd_banks_mark_bank_dead( ctx->banks, bank_idx, dead_idxs, &dead_idxs_cnt );
1434 :
1435 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ bank_idx ];
1436 0 : if( block_id_ele->block_id_seen ) publish_slot_dead( ctx, stem, block_id_ele->slot, &block_id_ele->latest_mr );
1437 :
1438 0 : for( ulong i=0UL; i<dead_idxs_cnt; i++ ) {
1439 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ dead_idxs[ i ] ];
1440 0 : fd_reasm_fec_t * fec = fd_reasm_query( ctx->reasm, &block_id_ele->latest_mr );
1441 0 : if( FD_LIKELY( fec ) ) fec->bank_dead = 1;
1442 0 : }
1443 0 : }
1444 :
1445 : static int
1446 : try_replay( fd_replay_tile_t * ctx,
1447 0 : fd_stem_context_t * stem ) {
1448 :
1449 0 : if( FD_UNLIKELY( !ctx->is_booted ) ) return 0;
1450 :
1451 0 : int charge_busy = 0;
1452 0 : fd_sched_task_t task[ 1 ];
1453 0 : if( FD_UNLIKELY( !fd_sched_task_next_ready( ctx->sched, task ) ) ) {
1454 0 : return charge_busy; /* Nothing to execute or do. */
1455 0 : }
1456 :
1457 0 : charge_busy = 1;
1458 :
1459 0 : switch( task->task_type ) {
1460 0 : case FD_SCHED_TT_BLOCK_START: {
1461 0 : replay_block_start( ctx, task->block_start->bank_idx, task->block_start->parent_bank_idx, task->block_start->slot );
1462 0 : fd_sched_task_done( ctx->sched, FD_SCHED_TT_BLOCK_START, ULONG_MAX, ULONG_MAX, NULL );
1463 0 : break;
1464 0 : }
1465 0 : case FD_SCHED_TT_BLOCK_END: {
1466 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->block_end->bank_idx );
1467 0 : if( FD_LIKELY( bank->state==FD_BANK_STATE_REPLAYABLE ) ) replay_block_finalize( ctx, stem, bank );
1468 0 : fd_sched_task_done( ctx->sched, FD_SCHED_TT_BLOCK_END, ULONG_MAX, ULONG_MAX, NULL );
1469 0 : break;
1470 0 : }
1471 0 : case FD_SCHED_TT_TXN_EXEC:
1472 0 : case FD_SCHED_TT_TXN_SIGVERIFY:
1473 0 : case FD_SCHED_TT_POH_HASH: {
1474 : /* Common case: we have a transaction we need to execute. */
1475 0 : dispatch_task( ctx, stem, task );
1476 0 : break;
1477 0 : }
1478 0 : case FD_SCHED_TT_MARK_DEAD: {
1479 0 : mark_bank_dead( ctx, stem, task->mark_dead->bank_idx );
1480 0 : break;
1481 0 : }
1482 0 : default: {
1483 0 : FD_LOG_CRIT(( "unexpected task type %lu", task->task_type ));
1484 0 : }
1485 0 : }
1486 :
1487 0 : return charge_busy;
1488 0 : }
1489 :
1490 : static int
1491 : can_process_fec( fd_replay_tile_t * ctx,
1492 0 : int * evict_banks_out ) {
1493 : /* We can process a FEC set if a few conditions are met:
1494 : - sched has capacity
1495 : - reasm has a FEC in its out queue ready to be processed
1496 : - banks has capacity. Evict if we don't (see below) */
1497 :
1498 0 : if( FD_UNLIKELY( fd_sched_can_ingest_cnt( ctx->sched )==0UL ) ) {
1499 0 : FD_TEST( !fd_sched_is_drained( ctx->sched ) );
1500 0 : ctx->metrics.sched_full++;
1501 0 : return 0;
1502 0 : }
1503 :
1504 0 : fd_reasm_fec_t * fec;
1505 0 : if( FD_UNLIKELY( (fec = fd_reasm_peek( ctx->reasm ))==NULL ) ) {
1506 0 : ctx->metrics.reasm_empty++;
1507 0 : return 0;
1508 0 : }
1509 :
1510 0 : fd_reasm_fec_t * parent = fd_reasm_parent( ctx->reasm, fec );
1511 0 : FD_TEST( parent ); /* FEC must be connected */
1512 :
1513 0 : ctx->metrics.reasm_latest_slot = fec->slot;
1514 0 : ctx->metrics.reasm_latest_fec_idx = fec->fec_set_idx;
1515 :
1516 : /* If the FEC we are building off of is for a prunable bank, we must
1517 : wait to process the FEC until the bank has been evicted. */
1518 0 : fd_bank_t * parent_fec_bank = parent->bank_idx==ULONG_MAX ? NULL : fd_banks_bank_query( ctx->banks, parent->bank_idx );
1519 0 : if( FD_UNLIKELY( parent_fec_bank && parent_fec_bank->bank_seq==parent->bank_seq && parent_fec_bank->state==FD_BANK_STATE_PRUNABLE ) ) {
1520 0 : FD_LOG_DEBUG(( "waiting to process FEC set (slot=%lu, fec_set_idx=%u) because parent bank is being pruned", fec->slot, fec->fec_set_idx ));
1521 0 : return 0;
1522 0 : }
1523 :
1524 0 : if( FD_UNLIKELY( ctx->is_leader && fec->fec_set_idx==0U && parent->bank_idx==ctx->leader_bank->idx ) ) {
1525 : /* This guards against a rare race where we receive the FEC set for
1526 : the slot right after our leader rotation before we freeze the
1527 : bank for the last slot in our leader rotation. Leader slot
1528 : freezing happens only after if we've received the final PoH hash
1529 : from the poh tile as well as the final FEC set for the leader
1530 : slot. So the race happens when FEC sets are delivered and
1531 : processed sooner than the PoH hash, aka when the
1532 : poh=>shred=>replay path for the block id beats the poh=>replay
1533 : path for the poh hash. To mitigate this race, we must block on
1534 : ingesting the FEC set for the ensuing slot before the leader
1535 : bank freezes, because that would violate ordering invariants in
1536 : banks and sched. */
1537 0 : FD_TEST( ctx->block_id_arr[ ctx->leader_bank->idx ].block_id_seen );
1538 0 : FD_TEST( !ctx->recv_poh );
1539 0 : ctx->metrics.leader_bid_wait++;
1540 0 : return 0;
1541 0 : }
1542 :
1543 : /* Should we evict banks if there are no more free banks? The answer
1544 : is it depends. Eviction should only happen if we can make no
1545 : forward replay progress. This can only happen if:
1546 : 1. banks are full
1547 : 2. sched is drained: pending txns could complete a block and
1548 : eventually advance the root.
1549 : AND
1550 : 3. next reasm FEC needs a new bank. A fec that chains off of a
1551 : bank that is already allocated can be processed. A FEC can
1552 : require a new bank in three ways:
1553 : - fec_set_idx==0: we don't have any free banks to provision a
1554 : new bank for this FEC.
1555 : - equivocation: a FEC may be in the middle of a block, but if
1556 : it's the first equivocating FEC detected, we need to allocate
1557 : a new bank for the version of the block.
1558 : - backfill: the parent FEC's bank was never created or has been
1559 : evicted and must be reconstructed. */
1560 :
1561 0 : int invalid_parent = !parent_fec_bank || parent_fec_bank->bank_seq!=parent->bank_seq;
1562 0 : if( FD_UNLIKELY( !fd_banks_can_start_bank( ctx->banks ) ) ) {
1563 0 : int is_new_block = fec->fec_set_idx==0U;
1564 0 : int is_eqvoc = fec->eqvoc && !parent->eqvoc;
1565 0 : if( FD_UNLIKELY( is_new_block || is_eqvoc || invalid_parent ) ) {
1566 0 : ctx->metrics.banks_full++;
1567 0 : if( FD_UNLIKELY( fd_sched_is_drained( ctx->sched ) ) ) *evict_banks_out = 1;
1568 0 : return 0;
1569 0 : }
1570 0 : }
1571 :
1572 : /* Otherwise, banks may not be full, so we can always create a new
1573 : bank if needed. Or, if banks are full, the current fec set's
1574 : ancestor (idx 0) already created a bank for this slot. */
1575 0 : return 1;
1576 0 : }
1577 :
1578 : /* Returns 0 on successful FEC ingestion, 1 if the block got marked
1579 : dead. insert_fec_set assumes that all FECs that are inserted are
1580 : directly connected to a parent FEC. Every block that is replayed
1581 : has initial fec set idx 0 up to and including a FEC with
1582 : slot_complete set. The caller is responsible for ensuring this. */
1583 : static int
1584 : insert_fec_set( fd_replay_tile_t * ctx,
1585 : fd_stem_context_t * stem,
1586 0 : fd_reasm_fec_t * reasm_fec ) {
1587 :
1588 : /* First, read FEC set from the store. If it's not there that means
1589 : that the FEC is on a minority fork which has been pruned away.
1590 : This means we shouldn't have a bank for the corresponding block so
1591 : we should just ignore and discard the FEC set. */
1592 :
1593 0 : ulong wait = (ulong)fd_log_wallclock();
1594 0 : ulong work = wait;
1595 0 : FD_STORE_SLOCK_BEGIN( ctx->store ) {
1596 0 : ctx->metrics.store_query_acquire++;
1597 0 : work = (ulong)fd_log_wallclock();
1598 0 : fd_histf_sample( ctx->metrics.store_query_wait, work - wait );
1599 :
1600 0 : fd_store_fec_t * store_fec = fd_store_query( ctx->store, &reasm_fec->key );
1601 0 : ctx->metrics.store_query_cnt++;
1602 0 : if( FD_UNLIKELY( !store_fec && !reasm_fec->is_leader ) ) {
1603 : /* The only case in which a FEC is not found in the store is either
1604 : if the FEC is from our own leader block or after repair has
1605 : notified is if the FEC was on a minority fork that has already
1606 : been published away. In this case we abandon the entire slice
1607 : because it is no longer relevant. If the FEC is from our own
1608 : leader block, process the FEC so we can unbecome leader. */
1609 0 : ctx->metrics.store_query_missing_cnt++;
1610 0 : ctx->metrics.store_query_missing_mr = reasm_fec->key.ul[0];
1611 0 : FD_BASE58_ENCODE_32_BYTES( reasm_fec->key.key, key_b58 );
1612 0 : FD_LOG_WARNING(( "store fec for slot: %lu is on minority fork already pruned by publish. abandoning slice. root: %lu. pruned merkle: %s", reasm_fec->slot, ctx->consensus_root_slot, key_b58 ));
1613 0 : return 1;
1614 0 : }
1615 :
1616 0 : long now = fd_log_wallclock();
1617 :
1618 : /* Assign parent bank idx + seq no to the FEC */
1619 0 : reasm_fec->parent_bank_idx = fd_reasm_parent( ctx->reasm, reasm_fec )->bank_idx;
1620 0 : fd_bank_t * parent_bank = fd_banks_bank_query( ctx->banks, reasm_fec->parent_bank_idx );
1621 :
1622 0 : if( FD_UNLIKELY( reasm_fec->fec_set_idx==0U ) ) {
1623 : /* Provision new bank if not leader. Assign bank idx and seq no
1624 : to the FEC. Remove stale block id map entry if any and update
1625 : pool element. */
1626 0 : fd_bank_t * bank = reasm_fec->is_leader ? ctx->leader_bank : fd_banks_new_bank( ctx->banks, reasm_fec->parent_bank_idx, now, 0 );
1627 0 : reasm_fec->bank_idx = bank->idx;
1628 0 : reasm_fec->bank_seq = bank->bank_seq;
1629 :
1630 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ reasm_fec->bank_idx ];
1631 0 : if( FD_LIKELY( fd_block_id_map_ele_query( ctx->block_id_map, &block_id_ele->latest_mr, NULL, ctx->block_id_arr )==block_id_ele ) ) {
1632 0 : FD_TEST( fd_block_id_map_ele_remove( ctx->block_id_map, &block_id_ele->latest_mr, NULL, ctx->block_id_arr ) );
1633 0 : }
1634 0 : block_id_ele->block_id_seen = 0;
1635 0 : block_id_ele->slot = reasm_fec->slot;
1636 0 : block_id_ele->bank_seq = bank->bank_seq;
1637 0 : block_id_ele->latest_fec_idx = 0U;
1638 0 : block_id_ele->latest_mr = reasm_fec->key;
1639 0 : } else { /* FEC for the middle or end of a block */
1640 : /* Assign bank idx + seqno to the FEC. Update block id pool ele. */
1641 0 : reasm_fec->bank_idx = reasm_fec->parent_bank_idx;
1642 0 : reasm_fec->bank_seq = parent_bank->bank_seq;
1643 :
1644 0 : FD_TEST( reasm_fec->bank_idx!=ULONG_MAX );
1645 :
1646 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ reasm_fec->bank_idx ];
1647 0 : block_id_ele->latest_fec_idx = reasm_fec->fec_set_idx;
1648 0 : block_id_ele->latest_mr = reasm_fec->key;
1649 0 : }
1650 :
1651 : /* If the FEC set is a slot complete, this means we have finally seen
1652 : the block id (block's last mr). */
1653 0 : if( FD_UNLIKELY( reasm_fec->slot_complete ) ) {
1654 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ reasm_fec->bank_idx ];
1655 0 : block_id_ele->block_id_seen = 1;
1656 0 : block_id_ele->latest_mr = reasm_fec->key;
1657 0 : block_id_ele->latest_fec_idx = reasm_fec->fec_set_idx;
1658 : /* If we are re-replaying a block, we want to remove the first
1659 : version of the block that we have presumably evicted. */
1660 0 : if( FD_UNLIKELY( fd_block_id_map_ele_remove( ctx->block_id_map, &block_id_ele->latest_mr, NULL, ctx->block_id_arr ) ) ) {
1661 0 : FD_LOG_DEBUG(( "finished re-replaying evicted bank (slot=%lu, bank_idx=%lu)", reasm_fec->slot, reasm_fec->bank_idx ));
1662 0 : }
1663 0 : FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, block_id_ele, ctx->block_id_arr ) );
1664 0 : }
1665 :
1666 : /* For leader FECs, don't insert the FEC into the scheduler. */
1667 0 : if( FD_UNLIKELY( reasm_fec->is_leader ) ) return 0;
1668 :
1669 : /* Forks form a partial ordering over FEC sets. The Repair tile
1670 : delivers FEC sets in-order per fork, but FEC set ordering across
1671 : forks is arbitrary */
1672 0 : fd_sched_fec_t sched_fec[ 1 ];
1673 :
1674 : # if DEBUG_LOGGING
1675 : FD_BASE58_ENCODE_32_BYTES( reasm_fec->key.key, key_b58 );
1676 : FD_BASE58_ENCODE_32_BYTES( reasm_fec->cmr.key, cmr_b58 );
1677 : FD_LOG_INFO(( "replay processing FEC set for slot %lu fec_set_idx %u, mr %s cmr %s", reasm_fec->slot, reasm_fec->fec_set_idx, key_b58, cmr_b58 ));
1678 : # endif
1679 :
1680 0 : sched_fec->shred_cnt = reasm_fec->data_cnt;
1681 0 : sched_fec->is_last_in_batch = !!reasm_fec->data_complete;
1682 0 : sched_fec->is_last_in_block = !!reasm_fec->slot_complete;
1683 0 : sched_fec->bank_idx = reasm_fec->bank_idx;
1684 0 : sched_fec->parent_bank_idx = reasm_fec->parent_bank_idx;
1685 0 : sched_fec->slot = reasm_fec->slot;
1686 0 : sched_fec->parent_slot = reasm_fec->slot - reasm_fec->parent_off;
1687 0 : sched_fec->is_first_in_block = reasm_fec->fec_set_idx==0U;
1688 0 : sched_fec->fec = store_fec;
1689 0 : sched_fec->data = fd_store_fec_data( ctx->store, store_fec );
1690 0 : sched_fec->alut_ctx->fork_id = fd_banks_bank_query( ctx->banks, ctx->published_root_bank_idx )->accdb_fork_id;
1691 0 : sched_fec->alut_ctx->accdb = ctx->accdb;
1692 0 : sched_fec->alut_ctx->els = ctx->published_root_slot;
1693 :
1694 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, sched_fec->bank_idx );
1695 0 : if( sched_fec->is_first_in_block ) {
1696 0 : bank->refcnt++;
1697 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for sched", bank->idx, sched_fec->slot, bank->refcnt ));
1698 0 : }
1699 :
1700 0 : if( FD_UNLIKELY( !fd_sched_fec_ingest( ctx->sched, sched_fec ) ) ) {
1701 0 : mark_bank_dead( ctx, stem, sched_fec->bank_idx );
1702 0 : return 1;
1703 0 : }
1704 :
1705 0 : } FD_STORE_SLOCK_END;
1706 :
1707 0 : ctx->metrics.store_query_release++;
1708 0 : fd_histf_sample( ctx->metrics.store_query_work, (ulong)fd_log_wallclock() - work );
1709 0 : return 0;
1710 0 : }
1711 :
1712 : static void
1713 : backfill_fec_sets( fd_replay_tile_t * ctx,
1714 : fd_stem_context_t * stem,
1715 0 : fd_reasm_fec_t * reasm_fec ) {
1716 0 : fd_reasm_fec_t * parent = fd_reasm_parent( ctx->reasm, reasm_fec );
1717 0 : FD_TEST( !!parent );
1718 :
1719 0 : fd_reasm_fec_t * path[ FD_FEC_BLK_MAX ];
1720 0 : ulong path_cnt = 0UL;
1721 0 : ulong path_slot = reasm_fec->slot;
1722 :
1723 : /* Walk backward from the candidate FEC until we find one with an
1724 : associated bank that we consider 'valid'. A FEC is considered
1725 : valid to backfill off of if the bank matches the seq we expect and
1726 : if its latest mr matches. We must check the latest MR in the case
1727 : of equivocation. */
1728 0 : for( fd_reasm_fec_t * curr = reasm_fec;; ) {
1729 0 : fd_bank_t * curr_bank = curr->bank_idx==ULONG_MAX ? NULL : fd_banks_bank_query( ctx->banks, curr->bank_idx );
1730 0 : fd_block_id_ele_t * block_id_ele = curr_bank ? &ctx->block_id_arr[ curr_bank->idx ] : NULL;
1731 0 : if( FD_LIKELY( curr_bank &&
1732 0 : curr_bank->bank_seq==curr->bank_seq &&
1733 0 : curr_bank->state!=FD_BANK_STATE_PRUNABLE &&
1734 0 : block_id_ele->bank_seq==curr->bank_seq &&
1735 0 : fd_hash_eq( &block_id_ele->latest_mr, &curr->key ) ) ) break;
1736 :
1737 0 : if( FD_UNLIKELY( curr->slot!=path_slot ) ) {
1738 0 : path_cnt = 0UL;
1739 0 : path_slot = curr->slot;
1740 0 : }
1741 :
1742 0 : FD_TEST( path_cnt<FD_FEC_BLK_MAX );
1743 0 : path[ path_cnt++ ] = curr;
1744 :
1745 0 : curr = fd_reasm_parent( ctx->reasm, curr );
1746 0 : FD_TEST( curr );
1747 0 : }
1748 :
1749 : /* Now that we have queued up the potential path of FECs to backfill,
1750 : ingest as much as sched can allow. */
1751 0 : fd_reasm_fec_t * first = path[ path_cnt-1UL ];
1752 0 : fd_reasm_fec_t * last = path[ 0 ];
1753 0 : FD_LOG_DEBUG(( "backfilling FEC sets for slot %lu from fec_set_idx %u to fec_set_idx %u", first->slot, first->fec_set_idx, last->fec_set_idx ));
1754 :
1755 0 : ulong sched_capacity = fd_sched_can_ingest_cnt( ctx->sched );
1756 0 : ulong path_idx_min = path_cnt - fd_ulong_min( sched_capacity, path_cnt );
1757 0 : for( ulong i=path_cnt; i>path_idx_min; i-- ) {
1758 0 : if( FD_UNLIKELY( insert_fec_set( ctx, stem, path[ i-1UL ] ) ) ) return;
1759 0 : }
1760 0 : }
1761 :
1762 : static void
1763 : process_fec_set( fd_replay_tile_t * ctx,
1764 : fd_stem_context_t * stem,
1765 0 : fd_reasm_fec_t * reasm_fec ) {
1766 :
1767 0 : fd_reasm_fec_t * parent = fd_reasm_parent( ctx->reasm, reasm_fec );
1768 0 : if( FD_UNLIKELY( parent->bank_dead ) ) {
1769 : /* Inherit the dead flag from the parent. If a dead slot is
1770 : completed, we publish the slot as dead. Don't insert FECs for
1771 : dead slots. */
1772 0 : reasm_fec->bank_dead = 1;
1773 0 : if( FD_UNLIKELY( reasm_fec->slot_complete ) ) publish_slot_dead( ctx, stem, reasm_fec->slot, &reasm_fec->key );
1774 0 : FD_LOG_DEBUG(( "dropping FEC set (slot=%lu, fec_set_idx=%u) because parent bank is marked dead", reasm_fec->slot, reasm_fec->fec_set_idx ));
1775 0 : return;
1776 0 : }
1777 :
1778 : /* An invariant from reasm is that if we receive a FEC set that is
1779 : both with eqvoc and confirmed set, we know that we must replay the
1780 : slot associated with this FEC. equivocation when fec_set_idx == 0
1781 : gets handled cleanly. */
1782 0 : int eqvoc_detected = reasm_fec->fec_set_idx!=0 && (reasm_fec->eqvoc && !parent->eqvoc);
1783 0 : if( FD_UNLIKELY( eqvoc_detected ) ) FD_TEST( reasm_fec->confirmed && parent->confirmed );
1784 :
1785 : /* We can detect if a bank has not replayed if the bank index tagged
1786 : to the FEC set is no longer valid or the bank sequence number for
1787 : the same bank is different (the bank has been recycled). This is
1788 : either due to the parent bank being evicted, or in reasm, the
1789 : parent is marked eqvoc (and not replayed), but the child gets
1790 : confirmed and delivered. */
1791 0 : fd_bank_t * parent_fec_bank = parent->bank_idx==ULONG_MAX ? NULL : fd_banks_bank_query( ctx->banks, parent->bank_idx );
1792 0 : int parent_bank_invalid = !parent_fec_bank || parent_fec_bank->bank_seq!=parent->bank_seq;
1793 :
1794 : /* If the upcoming FEC is either the start of an equivocating chain,
1795 : chains off of a bank that was evicted, OR is the child of an
1796 : equivocating chain whose parent was gated from getting replayed, we
1797 : must backfill any FECs into the scheduler. This backfill must
1798 : start from a FEC with fec_set_idx==0 with a parent FEC
1799 : corresponding to a valid bank. */
1800 0 : if( FD_LIKELY( !parent_bank_invalid && !eqvoc_detected ) ) {
1801 0 : insert_fec_set( ctx, stem, reasm_fec );
1802 0 : } else {
1803 0 : backfill_fec_sets( ctx, stem, reasm_fec );
1804 0 : }
1805 0 : }
1806 :
1807 : static int
1808 : try_notify_consensus_root( fd_replay_tile_t * ctx,
1809 0 : fd_stem_context_t * stem ) {
1810 :
1811 0 : if( FD_LIKELY( ctx->notified_root_slot==ctx->consensus_root_slot &&
1812 0 : fd_hash_eq( &ctx->notified_root, &ctx->consensus_root ) ) ) return 0;
1813 :
1814 0 : fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &ctx->consensus_root, NULL, ctx->block_id_arr );
1815 0 : if( FD_UNLIKELY( !block_id_ele ) ) return 0;
1816 :
1817 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) );
1818 0 : if( FD_UNLIKELY( !bank ||
1819 0 : bank->bank_seq!=block_id_ele->bank_seq ||
1820 0 : !fd_hash_eq( &bank->f.block_id, &ctx->consensus_root ) ||
1821 0 : bank->state==FD_BANK_STATE_PRUNABLE ) ) return 0;
1822 :
1823 0 : fd_sched_root_notify( ctx->sched, bank->idx );
1824 0 : publish_root_advanced( ctx, stem, bank );
1825 :
1826 0 : ctx->notified_root = ctx->consensus_root;
1827 0 : ctx->notified_root_slot = ctx->consensus_root_slot;
1828 0 : ctx->notified_root_bank = bank;
1829 0 : return 1;
1830 0 : }
1831 :
1832 : static ulong
1833 : snapshot_target_slot( fd_replay_tile_t * ctx,
1834 0 : int * out_incremental ) {
1835 0 : *out_incremental = 0;
1836 0 : if( FD_LIKELY( !ctx->snapmk.supported || ctx->snapmk.active ) ) return ULONG_MAX;
1837 :
1838 0 : int caught_up = ctx->caught_up; /* suspend periodic snaps until caught up */
1839 0 : ulong full = ULONG_MAX;
1840 0 : ulong interval = ctx->snapmk.full_interval;
1841 0 : if( FD_UNLIKELY( interval && caught_up ) ) {
1842 0 : if( FD_UNLIKELY( ctx->snapmk.next_full_slot==ULONG_MAX ) ) {
1843 0 : ctx->snapmk.next_full_slot = ((ctx->published_root_slot/interval)+1UL)*interval;
1844 0 : }
1845 :
1846 : /* If snapshot production fell behind by more than one interval,
1847 : skip ahead to the latest due interval. */
1848 0 : full = fd_ulong_max( ctx->snapmk.next_full_slot, (ctx->consensus_root_slot/interval)*interval );
1849 0 : }
1850 0 : full = fd_ulong_min( full, ctx->snapmk.scheduled_at );
1851 :
1852 : /* An incremental snapshot is only possible once a full snapshot
1853 : exists to serve as its base. */
1854 0 : ulong incremental = ULONG_MAX;
1855 0 : ulong incr_interval = ctx->snapmk.incremental_interval;
1856 0 : if( FD_UNLIKELY( incr_interval && caught_up && ctx->snapmk.base_slot!=ULONG_MAX ) ) {
1857 0 : if( FD_UNLIKELY( ctx->snapmk.next_incremental_slot==ULONG_MAX ) ) {
1858 0 : ctx->snapmk.next_incremental_slot = ((ctx->published_root_slot/incr_interval)+1UL)*incr_interval;
1859 0 : }
1860 0 : incremental = fd_ulong_max( ctx->snapmk.next_incremental_slot, (ctx->consensus_root_slot/incr_interval)*incr_interval );
1861 0 : }
1862 :
1863 : /* A full snapshot due at the same slot supersedes the incremental. */
1864 0 : if( FD_UNLIKELY( incremental<full ) ) {
1865 0 : *out_incremental = 1;
1866 0 : return incremental;
1867 0 : }
1868 0 : return full;
1869 0 : }
1870 :
1871 : static void
1872 : snapmk_start( fd_replay_tile_t * ctx,
1873 : fd_stem_context_t * stem,
1874 : int incremental );
1875 :
1876 : static int
1877 : try_advance_published_root( fd_replay_tile_t * ctx,
1878 0 : fd_stem_context_t * stem ) {
1879 :
1880 0 : if( FD_LIKELY( ctx->published_root_slot==ctx->consensus_root_slot ) ) return 0;
1881 :
1882 : /* accdb pauses advance_root while producing a snapshot, so submitting
1883 : one would stall the next wait_cmd until the snapshot completes. */
1884 0 : if( FD_UNLIKELY( ctx->snapmk.active ) ) return 0;
1885 :
1886 : /* If the new root is not available because the bank is/has been
1887 : evicted, we can't advance the root. Try again later. */
1888 0 : fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &ctx->consensus_root, NULL, ctx->block_id_arr );
1889 0 : if( FD_UNLIKELY( !block_id_ele ) ) return 0;
1890 0 : fd_bank_t * target_bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) );
1891 0 : if( FD_UNLIKELY( !target_bank ||
1892 0 : target_bank->bank_seq!=block_id_ele->bank_seq ||
1893 0 : !fd_hash_eq( &target_bank->f.block_id, &ctx->consensus_root ) ||
1894 0 : target_bank->state==FD_BANK_STATE_PRUNABLE ) ) return 0;
1895 :
1896 : /* If the identity vote has been seen on a bank that should be rooted,
1897 : then we are now ready to produce blocks. */
1898 0 : if( FD_UNLIKELY( !ctx->identity_vote_rooted ) ) {
1899 0 : if( target_bank->f.identity_vote_idx==ctx->identity_idx ) ctx->identity_vote_rooted = 1;
1900 0 : }
1901 :
1902 0 : ulong advanceable_root_idx = ULONG_MAX;
1903 0 : if( FD_UNLIKELY( !fd_banks_advance_root_prepare( ctx->banks, target_bank->idx, &advanceable_root_idx ) ) ) {
1904 0 : ctx->metrics.storage_root_behind++;
1905 0 : return 0;
1906 0 : }
1907 :
1908 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, advanceable_root_idx );
1909 0 : FD_TEST( bank );
1910 :
1911 0 : if( FD_UNLIKELY( advanceable_root_idx>=ctx->block_id_len ) ) {
1912 0 : FD_LOG_CRIT(( "invariant violation: advanceable root ele out of bounds [0, %lu) index %lu", ctx->block_id_len, advanceable_root_idx ));
1913 0 : }
1914 0 : fd_block_id_ele_t * advanceable_root_ele = &ctx->block_id_arr[ advanceable_root_idx ];
1915 :
1916 0 : ulong advanceable_root_slot = bank->f.slot;
1917 0 : fd_txncache_advance_root( ctx->txncache, bank->txncache_fork_id );
1918 0 : fd_progcache_advance_root( ctx->progcache, bank->progcache_fork_id );
1919 0 : fd_accdb_advance_root( ctx->accdb, bank->accdb_fork_id );
1920 0 : fd_sched_advance_root( ctx->sched, advanceable_root_idx );
1921 0 : fd_banks_advance_root( ctx->banks, advanceable_root_idx );
1922 0 : fd_reasm_publish( ctx->reasm, &advanceable_root_ele->latest_mr, ctx->store );
1923 :
1924 0 : ctx->published_root_slot = advanceable_root_slot;
1925 0 : ctx->published_root_bank_idx = advanceable_root_idx;
1926 :
1927 0 : int snap_incremental;
1928 0 : ulong snap_target_slot = snapshot_target_slot( ctx, &snap_incremental );
1929 0 : if( FD_UNLIKELY( advanceable_root_slot>=snap_target_slot ) ) {
1930 0 : snapmk_start( ctx, stem, snap_incremental );
1931 0 : if( FD_UNLIKELY( !snap_incremental ) ) {
1932 0 : if( FD_UNLIKELY( ctx->snapmk.full_interval &&
1933 0 : advanceable_root_slot>=ctx->snapmk.next_full_slot ) ) {
1934 0 : ctx->snapmk.next_full_slot = ((advanceable_root_slot/ctx->snapmk.full_interval)+1UL)*ctx->snapmk.full_interval;
1935 0 : }
1936 0 : if( FD_UNLIKELY( advanceable_root_slot>=ctx->snapmk.scheduled_at ) ) {
1937 0 : ctx->snapmk.scheduled_at = ULONG_MAX;
1938 0 : }
1939 0 : }
1940 : /* A full snapshot re-bases the incremental schedule onto itself. */
1941 0 : if( FD_UNLIKELY( ctx->snapmk.incremental_interval ) ) {
1942 0 : ctx->snapmk.next_incremental_slot = ((advanceable_root_slot/ctx->snapmk.incremental_interval)+1UL)*ctx->snapmk.incremental_interval;
1943 0 : }
1944 0 : }
1945 :
1946 0 : return 1;
1947 0 : }
1948 :
1949 : static int
1950 0 : try_prune_sched( fd_replay_tile_t * ctx ) {
1951 0 : ulong bank_idx;
1952 0 : int pruned = 0;
1953 0 : while( (bank_idx=fd_sched_pruned_block_next( ctx->sched ) )!=ULONG_MAX ) {
1954 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, bank_idx );
1955 0 : FD_TEST( bank );
1956 0 : bank->refcnt--;
1957 0 : FD_LOG_DEBUG(( "bank (idx=%lu) refcnt decremented to %lu for sched", bank->idx, bank->refcnt ));
1958 0 : pruned = 1;
1959 0 : }
1960 0 : return pruned;
1961 0 : }
1962 :
1963 : static int
1964 0 : try_prune_bank( fd_replay_tile_t * ctx ) {
1965 0 : fd_banks_prune_cancel_info_t cancel_info[ 1 ];
1966 :
1967 0 : int pruned = fd_banks_prune_one_bank( ctx->banks, cancel_info );
1968 0 : switch( pruned ) {
1969 0 : case 2: { /* pruning bank + cancellation is needed */
1970 0 : fd_txncache_cancel_fork( ctx->txncache, cancel_info->txncache_fork_id );
1971 0 : fd_progcache_cancel_fork( ctx->progcache, cancel_info->progcache_fork_id );
1972 0 : fd_accdb_purge( ctx->accdb, cancel_info->accdb_fork_id );
1973 0 : __attribute__((fallthrough));
1974 0 : }
1975 0 : case 1: { /* pruning bank + no cancellation is needed */
1976 : /* A sched block exists, and can be marked dead, for a bank as
1977 : soon as its first FEC has been ingested, which can happen
1978 : before the bank ever set itself up for actual execution (e.g. a
1979 : block that parses as bad on its very first FEC). So always
1980 : instruct sched to prune the block whenever banks prunes the
1981 : bank. The txncache/progcache/accdb forks, on the other hand,
1982 : are only created once the bank started actual execution. */
1983 0 : fd_sched_cancel( ctx->sched, cancel_info->bank_idx );
1984 0 : return 1;
1985 0 : }
1986 0 : case 0: /* no bank to prune */
1987 0 : return 0;
1988 0 : default:
1989 0 : FD_LOG_ERR(( "unreachable" ));
1990 0 : }
1991 0 : }
1992 :
1993 : static int
1994 : try_evict_reasm( fd_replay_tile_t * ctx,
1995 0 : fd_stem_context_t * stem ) {
1996 :
1997 : /* if reasm_evicted is set, publish starting from reasm_evicted down
1998 : to the leaf node to repair so repair can re-request for it.
1999 : reasm_evicted gets set when reasm tries to insert a FEC and there
2000 : is no remaining capacity. */
2001 0 : if( FD_LIKELY( !ctx->reasm_evicted ) ) return 0;
2002 :
2003 : /* Publish a notification to the repair tile that the Replay tile no
2004 : longer has the FEC that was evicted. This will make sure that the
2005 : repair tile will re-request the FEC if it eventually gets
2006 : confirmed so that Replay can still make forward progress. */
2007 0 : fd_replay_fec_evicted_t evicted = (fd_replay_fec_evicted_t){ .mr = ctx->reasm_evicted->key, .slot = ctx->reasm_evicted->slot, .fec_set_idx = ctx->reasm_evicted->fec_set_idx, .bank_idx = ctx->reasm_evicted->bank_idx };
2008 0 : fd_memcpy( fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk ), &evicted, sizeof(fd_replay_fec_evicted_t) );
2009 0 : fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_REASM_EVICTED, ctx->replay_out->chunk, sizeof(fd_replay_fec_evicted_t), 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
2010 0 : ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(fd_replay_fec_evicted_t), ctx->replay_out->chunk0, ctx->replay_out->wmark );
2011 :
2012 : /* eviction policy only evicts chains of nodes until there is a
2013 : fork, so guaranteed that the evict path is always the left-child
2014 : TODO: This should be abstracted away. */
2015 0 : fd_reasm_pool_release( ctx->reasm, ctx->reasm_evicted );
2016 0 : ctx->reasm_evicted = fd_reasm_child( ctx->reasm, ctx->reasm_evicted ); /* indexes into pool, safe to use */
2017 0 : return 1;
2018 0 : }
2019 :
2020 : static int
2021 : try_process_fec( fd_replay_tile_t * ctx,
2022 0 : fd_stem_context_t * stem ) {
2023 :
2024 : /* If the reassembler has a fec that is ready, we should process it
2025 : and pass it to the scheduler.
2026 :
2027 : We would also like to pace FEC ingestion such that we keep the exec
2028 : tiles busy. If there's a pending frag from one of the exec tiles,
2029 : we would like to know about that asap, because that could unblock
2030 : dispatching. So we ingest FEC sets only if we are sure that there
2031 : are no more exec tile notifications to process. This delays FEC
2032 : ingestion just enough so as to keep the exec tiles as busy as we
2033 : can, and prevents us from being stuck ingesting a backlog of FEC
2034 : sets, especially when there is a pending completion notification
2035 : about a single-transaction chokepoint in the replay dispatcher DAG.
2036 : Except that when we are leader or the reasm buffer is getting full,
2037 : we prioritize FEC processing. In the leader case, this is so we
2038 : can get to the leader FEC sets asap and freeze the leader bank on
2039 : time. In the reasm full case, this is so we don't prematurely
2040 : trigger eviction. */
2041 0 : int evict_banks = 0;
2042 0 : if( FD_LIKELY( (ctx->execrp_idle_cnt>=2UL*ctx->in_cnt || ctx->is_leader || fd_reasm_free( ctx->reasm )<=1UL) &&
2043 0 : can_process_fec( ctx, &evict_banks ) ) ) {
2044 0 : fd_reasm_fec_t * fec = fd_reasm_pop( ctx->reasm );
2045 0 : process_fec_set( ctx, stem, fec );
2046 0 : ctx->execrp_idle_cnt = 0UL;
2047 0 : return 1;
2048 0 : }
2049 :
2050 : /* If we need to evict banks, gather one evictable bank. The bank is
2051 : marked prunable by fd_banks_get_evictable_bank and pruned once refs
2052 : drain. */
2053 0 : if( FD_UNLIKELY( evict_banks ) ) {
2054 0 : ulong evictable_bank_idx = fd_banks_get_evictable_bank( ctx->banks, ctx->notified_root_bank );
2055 0 : if( FD_UNLIKELY( evictable_bank_idx==ULONG_MAX ) ) {
2056 0 : FD_LOG_DEBUG(( "replay has no banks to mark as prunable, it's possible that there is one bank already marked as prunable" ));
2057 0 : return 0;
2058 0 : }
2059 :
2060 0 : FD_LOG_WARNING(( "banks full, evicting bank (idx=%lu)", evictable_bank_idx ));
2061 :
2062 : /* Send a notification to other tiles to drop a reference to the
2063 : evictable bank. The RPC tile is the only tile which holds onto
2064 : non-rooted banks, non-transiently. */
2065 0 : fd_replay_drop_bank_ref_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
2066 0 : fd_sched_block_abandon( ctx->sched, evictable_bank_idx );
2067 0 : msg->bank_idx = evictable_bank_idx;
2068 0 : fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_DROP_BANK_REF, ctx->replay_out->chunk, sizeof(fd_replay_drop_bank_ref_t), 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
2069 0 : ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(fd_replay_drop_bank_ref_t), ctx->replay_out->chunk0, ctx->replay_out->wmark );
2070 :
2071 0 : return 1;
2072 0 : }
2073 :
2074 0 : return 0;
2075 0 : }
2076 :
2077 : static void
2078 : after_credit( fd_replay_tile_t * ctx,
2079 : fd_stem_context_t * stem,
2080 : int * opt_poll_in,
2081 0 : int * charge_busy ) {
2082 0 : if( FD_UNLIKELY( !ctx->is_booted || !ctx->wfs_complete ) ) return;
2083 :
2084 : /* The overall priority for the replay tile in order is:
2085 : 1. Make sure replay has room to progress:
2086 : a. evicting pending FECs from the reassembler
2087 : b. queueing up evictable banks for pruning if needed
2088 : c. clearing any pending bank eviction victims.
2089 : 2. Drain outstanding bank references from the scheduler. This
2090 : happens after a block gets completed or a fork gets pruned.
2091 : 3. Notify sched and bank consumers of a new consensus root, then
2092 : advance the storage root once old references drain.
2093 : 4. Replay. If there is work to do for replay, do it. This is
2094 : more important than ingesting more FEC sets.
2095 : 5. If replay has nothing to do, ingest more FEC sets.
2096 : WARNING: The ordering here is VERY load bearing and it should not
2097 : be changed without extreme caution. */
2098 :
2099 0 : if( FD_UNLIKELY( try_evict_reasm( ctx, stem ) ) ) {
2100 0 : *charge_busy = 1;
2101 0 : *opt_poll_in = 0;
2102 0 : return;
2103 0 : }
2104 :
2105 0 : if( FD_UNLIKELY( try_prune_sched( ctx ) ) ) {
2106 0 : *charge_busy = 1;
2107 0 : *opt_poll_in = 0;
2108 0 : return;
2109 0 : }
2110 :
2111 0 : if( FD_UNLIKELY( try_notify_consensus_root( ctx, stem ) ) ) {
2112 0 : *charge_busy = 1;
2113 0 : *opt_poll_in = 0;
2114 0 : return;
2115 0 : }
2116 :
2117 0 : if( FD_UNLIKELY( try_prune_bank( ctx ) ) ) {
2118 0 : *charge_busy = 1;
2119 0 : *opt_poll_in = 0;
2120 0 : return;
2121 0 : }
2122 :
2123 0 : if( FD_UNLIKELY( try_become_leader( ctx, stem ) ) ) {
2124 0 : *charge_busy = 1;
2125 0 : *opt_poll_in = 0;
2126 0 : return;
2127 0 : }
2128 :
2129 0 : if( FD_UNLIKELY( try_fini_leader( ctx, stem ) ) ) {
2130 0 : *charge_busy = 1;
2131 0 : *opt_poll_in = 0;
2132 0 : return;
2133 0 : }
2134 :
2135 0 : if( FD_UNLIKELY( try_advance_published_root( ctx, stem ) ) ) {
2136 0 : *charge_busy = 1;
2137 0 : *opt_poll_in = 0;
2138 0 : return;
2139 0 : }
2140 :
2141 0 : if( FD_LIKELY( try_replay( ctx, stem ) ) ) {
2142 0 : *charge_busy = 1;
2143 0 : *opt_poll_in = 0;
2144 0 : return;
2145 0 : }
2146 :
2147 0 : if( FD_LIKELY( try_process_fec( ctx, stem ) ) ) {
2148 0 : *charge_busy = 1;
2149 0 : *opt_poll_in = 0;
2150 0 : return;
2151 0 : }
2152 :
2153 0 : ctx->execrp_idle_cnt++;
2154 0 : }
2155 :
2156 : static int
2157 : before_frag( fd_replay_tile_t * ctx,
2158 : ulong in_idx,
2159 : ulong seq FD_PARAM_UNUSED,
2160 0 : ulong sig ) {
2161 :
2162 0 : if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP_OUT && sig!=FD_GOSSIP_UPDATE_TAG_WFS_DONE ) ) return 1;
2163 0 : return 0;
2164 0 : }
2165 :
2166 : static void
2167 : process_exec_task_done( fd_replay_tile_t * ctx,
2168 : fd_stem_context_t * stem,
2169 : fd_execrp_task_done_msg_t * msg,
2170 0 : ulong sig ) {
2171 :
2172 0 : ulong exec_tile_idx = sig&0xFFFFFFFFUL;
2173 :
2174 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, msg->bank_idx );
2175 0 : FD_TEST( bank );
2176 0 : bank->refcnt--;
2177 :
2178 0 : switch( sig>>32 ) {
2179 0 : case FD_EXECRP_TT_TXN_EXEC: {
2180 0 : ulong txn_idx = msg->txn_exec->txn_idx;
2181 0 : if( FD_UNLIKELY( !ctx->identity_vote_rooted ) ) {
2182 : /* Query the txn signature against our recently generated vote
2183 : txn signatures. If the query is successful, then we have
2184 : seen our own vote transaction land and this should be marked
2185 : in the bank. We go through this exercise until we've seen
2186 : our vote rooted. */
2187 0 : fd_txn_p_t * txn_p = fd_sched_get_txn( ctx->sched, txn_idx );
2188 :
2189 0 : fd_pubkey_t * identity_pubkey_out = NULL;
2190 0 : if( fd_vote_tracker_query_sig( ctx->vote_tracker, fd_type_pun_const( txn_p->payload+TXN( txn_p )->signature_off ), &identity_pubkey_out ) && fd_pubkey_eq( identity_pubkey_out, ctx->identity_pubkey ) ) {
2191 0 : bank->f.identity_vote_idx = ctx->identity_idx;
2192 0 : }
2193 0 : }
2194 0 : if( FD_UNLIKELY( !msg->txn_exec->is_committable && bank->state!=FD_BANK_STATE_DEAD) ) {
2195 : /* Every transaction in a valid block has to execute.
2196 : Otherwise, we should mark the block as dead. */
2197 0 : mark_bank_dead( ctx, stem, bank->idx );
2198 0 : fd_sched_block_abandon( ctx->sched, bank->idx );
2199 0 : }
2200 0 : int res = fd_sched_task_done( ctx->sched, FD_SCHED_TT_TXN_EXEC, txn_idx, exec_tile_idx, NULL );
2201 0 : FD_TEST( res==0 );
2202 0 : fd_sched_txn_info_t * txn_info = fd_sched_get_txn_info( ctx->sched, txn_idx );
2203 0 : txn_info->flags |= FD_SCHED_TXN_EXEC_DONE;
2204 0 : if( FD_LIKELY( !(txn_info->flags&FD_SCHED_TXN_SIGVERIFY_DONE)||!txn_info->txn_err ) ) { /* Set execution status if sigverify hasn't happened yet or if sigverify was a success. */
2205 0 : txn_info->txn_err = msg->txn_exec->txn_err;
2206 0 : txn_info->flags |= fd_ulong_if( msg->txn_exec->is_committable, FD_SCHED_TXN_IS_COMMITTABLE, 0UL );
2207 0 : txn_info->flags |= fd_ulong_if( msg->txn_exec->is_fees_only, FD_SCHED_TXN_IS_FEES_ONLY, 0UL );
2208 0 : }
2209 0 : if( FD_UNLIKELY( (txn_info->flags&FD_SCHED_TXN_REPLAY_DONE)==FD_SCHED_TXN_REPLAY_DONE ) ) { /* UNLIKELY because generally exec happens before sigverify. */
2210 0 : publish_txn_executed( ctx, stem, txn_idx );
2211 0 : }
2212 0 : break;
2213 0 : }
2214 0 : case FD_EXECRP_TT_TXN_SIGVERIFY: {
2215 0 : ulong txn_idx = msg->txn_sigverify->txn_idx;
2216 0 : fd_sched_txn_info_t * txn_info = fd_sched_get_txn_info( ctx->sched, txn_idx );
2217 0 : txn_info->flags |= FD_SCHED_TXN_SIGVERIFY_DONE;
2218 0 : if( FD_UNLIKELY( msg->txn_sigverify->err ) ) {
2219 0 : txn_info->txn_err = FD_RUNTIME_TXN_ERR_SIGNATURE_FAILURE;
2220 0 : txn_info->flags &= ~FD_SCHED_TXN_IS_COMMITTABLE;
2221 0 : txn_info->flags &= ~FD_SCHED_TXN_IS_FEES_ONLY;
2222 0 : }
2223 0 : if( FD_UNLIKELY( msg->txn_sigverify->err && bank->state!=FD_BANK_STATE_DEAD ) ) {
2224 : /* Every transaction in a valid block has to sigverify.
2225 : Otherwise, we should mark the block as dead. Also freeze the
2226 : bank if possible. */
2227 0 : mark_bank_dead( ctx, stem, bank->idx );
2228 0 : fd_sched_block_abandon( ctx->sched, bank->idx );
2229 0 : }
2230 0 : int res = fd_sched_task_done( ctx->sched, FD_SCHED_TT_TXN_SIGVERIFY, txn_idx, exec_tile_idx, NULL );
2231 0 : FD_TEST( res==0 );
2232 0 : if( FD_LIKELY( (txn_info->flags&FD_SCHED_TXN_REPLAY_DONE)==FD_SCHED_TXN_REPLAY_DONE ) ) {
2233 0 : publish_txn_executed( ctx, stem, txn_idx );
2234 0 : }
2235 0 : break;
2236 0 : }
2237 0 : case FD_EXECRP_TT_POH_HASH: {
2238 0 : int res = fd_sched_task_done( ctx->sched, FD_SCHED_TT_POH_HASH, ULONG_MAX, exec_tile_idx, msg->poh_hash );
2239 0 : if( FD_UNLIKELY( res<0 && bank->state!=FD_BANK_STATE_DEAD ) ) {
2240 0 : mark_bank_dead( ctx, stem, bank->idx );
2241 0 : }
2242 0 : break;
2243 0 : }
2244 0 : default: FD_LOG_CRIT(( "unexpected sig 0x%lx", sig ));
2245 0 : }
2246 :
2247 : /* Reference counter just decreased, and an exec tile just got freed
2248 : up. If there's a need to be more aggressively pruning, we could
2249 : check here if more slots just became publishable and publish. Not
2250 : publishing here shouldn't bloat the fork tree too much though. We
2251 : mark minority forks dead as soon as we can, and execution dispatch
2252 : stops on dead blocks. So shortly afterwards, dead blocks should be
2253 : eligible for pruning as in-flight transactions retire from the
2254 : execution pipeline. */
2255 :
2256 0 : }
2257 :
2258 : static void
2259 : process_tower_slot_done( fd_replay_tile_t * ctx,
2260 : fd_stem_context_t * stem,
2261 : fd_tower_slot_done_t const * msg,
2262 0 : ulong seq ) {
2263 :
2264 : /* This frag from tower tells us to:
2265 : - Release an outstanding refernence on the replayed bank
2266 : - Advance the consensus root if one has been supplied
2267 : - Update the reset block */
2268 :
2269 0 : fd_bank_t * replay_bank = fd_banks_bank_query( ctx->banks, msg->replay_bank_idx );
2270 0 : if( FD_UNLIKELY( !replay_bank ) ) FD_LOG_CRIT(( "invariant violation: bank not found for bank index %lu", msg->replay_bank_idx ));
2271 0 : replay_bank->refcnt--;
2272 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt decremented to %lu for tower", replay_bank->idx, msg->replay_slot, replay_bank->refcnt ));
2273 :
2274 0 : if( FD_LIKELY( msg->root_slot!=ULONG_MAX ) ) {
2275 0 : FD_TEST( msg->root_slot>=ctx->consensus_root_slot );
2276 0 : ctx->consensus_root_slot = msg->root_slot;
2277 0 : ctx->consensus_root = msg->root_block_id;
2278 0 : }
2279 :
2280 0 : fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &msg->reset_block_id, NULL, ctx->block_id_arr );
2281 0 : if( FD_UNLIKELY( !block_id_ele ) ) {
2282 0 : FD_LOG_WARNING(( "ignoring reset block update from tower because block has been evicted (slot=%lu)", msg->reset_slot ));
2283 0 : return;
2284 0 : }
2285 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) );
2286 0 : if( FD_UNLIKELY( !bank || bank->bank_seq!=block_id_ele->bank_seq || bank->state==FD_BANK_STATE_PRUNABLE ) ) {
2287 0 : FD_LOG_WARNING(( "ignoring reset block update from tower because bank has been evicted (slot=%lu)", msg->reset_slot ));
2288 0 : return;
2289 0 : }
2290 :
2291 0 : ctx->reset_block_id = msg->reset_block_id;
2292 0 : ctx->reset_slot = msg->reset_slot;
2293 0 : ctx->reset_timestamp_nanos = fd_log_wallclock();
2294 0 : if( FD_LIKELY( msg->root_slot!=ULONG_MAX ) ) FD_TEST( msg->root_slot<=msg->reset_slot );
2295 :
2296 0 : ulong min_leader_slot = fd_ulong_max( msg->reset_slot+1UL, fd_ulong_if( ctx->highwater_leader_slot==ULONG_MAX, 0UL, ctx->highwater_leader_slot+1UL ) );
2297 0 : ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, min_leader_slot, ctx->identity_pubkey );
2298 0 : if( FD_LIKELY( ctx->next_leader_slot != ULONG_MAX ) ) {
2299 0 : double slot_duration_ticks = (double)bank->f.slot_params.ns_per_slot_adjusted*ctx->tick_per_ns;
2300 0 : ctx->next_leader_tickcount = (long)((double)(ctx->next_leader_slot-ctx->reset_slot-1UL)*slot_duration_ticks) + fd_tickcount();
2301 0 : } else {
2302 0 : ctx->next_leader_tickcount = LONG_MAX;
2303 0 : }
2304 :
2305 0 : if( FD_LIKELY( ctx->replay_out->idx!=ULONG_MAX ) ) {
2306 0 : fd_poh_reset_t * reset = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
2307 :
2308 0 : reset->bank_idx = bank->idx;
2309 0 : reset->timestamp = ctx->reset_timestamp_nanos;
2310 0 : reset->completed_slot = ctx->reset_slot;
2311 0 : reset->hashcnt_per_tick = bank->f.slot_params.hashes_per_tick;
2312 0 : reset->ticks_per_slot = bank->f.ticks_per_slot;
2313 0 : reset->tick_duration_ns = bank->f.slot_params.ns_per_slot_adjusted/reset->ticks_per_slot;
2314 :
2315 0 : fd_memcpy( reset->completed_block_id, &block_id_ele->latest_mr, sizeof(fd_hash_t) );
2316 :
2317 0 : fd_blockhashes_t const * block_hash_queue = &bank->f.block_hash_queue;
2318 0 : fd_hash_t const * last_hash = fd_blockhashes_peek_last_hash( block_hash_queue );
2319 0 : FD_TEST( last_hash );
2320 0 : fd_memcpy( reset->completed_blockhash, last_hash->uc, sizeof(fd_hash_t) );
2321 :
2322 0 : ulong ticks_per_slot = bank->f.ticks_per_slot;
2323 0 : if( FD_UNLIKELY( reset->hashcnt_per_tick==1UL ) ) {
2324 : /* Low power producer, maximum of one microblock per tick in the slot */
2325 0 : reset->max_microblocks_in_slot = ticks_per_slot;
2326 0 : } else {
2327 : /* See the long comment in after_credit for this limit */
2328 0 : reset->max_microblocks_in_slot = fd_ulong_min( MAX_MICROBLOCKS_PER_SLOT, ticks_per_slot*(reset->hashcnt_per_tick-1UL) );
2329 0 : }
2330 0 : reset->next_leader_slot = ctx->next_leader_slot;
2331 0 : reset->wfs_paused = !ctx->wfs_complete;
2332 :
2333 0 : fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_RESET, ctx->replay_out->chunk, sizeof(fd_poh_reset_t), 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
2334 0 : ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(fd_poh_reset_t), ctx->replay_out->chunk0, ctx->replay_out->wmark );
2335 0 : }
2336 :
2337 0 : FD_LOG_INFO(( "tower_slot_done(reset_slot=%lu, next_leader_slot=%lu, vote_slot=%lu, replay_slot=%lu, root_slot=%lu, seqno=%lu)", msg->reset_slot, ctx->next_leader_slot, msg->vote_slot, msg->replay_slot, msg->root_slot, seq ));
2338 0 : try_become_leader( ctx, stem );
2339 :
2340 0 : ulong distance = 0UL;
2341 0 : fd_bank_t * parent = bank;
2342 0 : while( parent ) {
2343 0 : if( FD_UNLIKELY( fd_hash_eq( &parent->f.block_id, &ctx->consensus_root ) ) ) break;
2344 0 : parent = fd_banks_get_parent( ctx->banks, parent );
2345 0 : distance++;
2346 0 : }
2347 :
2348 0 : FD_MGAUGE_SET( REPLAY, ROOT_DISTANCE, distance );
2349 :
2350 0 : }
2351 :
2352 : static void
2353 : process_fec_complete( fd_replay_tile_t * ctx,
2354 : ulong sig,
2355 0 : fd_fec_complete_t * complete_msg ) {
2356 0 : fd_shred_t const * shred = &complete_msg->last_shred_hdr;
2357 :
2358 0 : fd_hash_t const * merkle_root = &complete_msg->merkle_root;
2359 0 : fd_hash_t const * chained_merkle_root = &complete_msg->chained_merkle_root;
2360 0 : int is_leader_fec = sig == REPAIR_SIG_FEC_LEADER;
2361 0 : int data_complete = !!( shred->data.flags & FD_SHRED_DATA_FLAG_DATA_COMPLETE );
2362 0 : int slot_complete = !!( shred->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE );
2363 :
2364 0 : if( FD_UNLIKELY( sig==REPAIR_SIG_FEC_INVALID ) ) {
2365 : /* FEC set detected as invalid based on duplicate confirmations.
2366 : Nothing to do except remove from store. If the FEC set is not in
2367 : reasm, we can directly remove from store. If the FEC set is in
2368 : reasm, then we let reasm_publish handle it. */
2369 0 : if( FD_LIKELY( !fd_reasm_query( ctx->reasm, merkle_root ) ) ) {
2370 0 : fd_store_remove( ctx->store, merkle_root );
2371 0 : }
2372 0 : return;
2373 0 : }
2374 :
2375 : /* Track the cluster tip: the highest slot seen in FEC sets from the
2376 : network (leader FECs are our own blocks, not evidence of the tip). */
2377 0 : if( FD_LIKELY( !is_leader_fec && ( ctx->catch_up_max_fec_slot==ULONG_MAX || shred->slot>ctx->catch_up_max_fec_slot ) ) ) {
2378 0 : ctx->catch_up_max_fec_slot = shred->slot;
2379 0 : ctx->catch_up_tip_advance_cnt++;
2380 0 : }
2381 :
2382 0 : if( FD_UNLIKELY( shred->slot - shred->data.parent_off == fd_reasm_slot0( ctx->reasm ) && shred->fec_set_idx == 0) ) {
2383 0 : chained_merkle_root = &fd_reasm_root( ctx->reasm )->key;
2384 0 : }
2385 :
2386 0 : if( FD_UNLIKELY( fd_reasm_query( ctx->reasm, merkle_root ) ) ) return;
2387 0 : fd_reasm_fec_t * fec = fd_reasm_insert( ctx->reasm, merkle_root, chained_merkle_root, shred->slot, shred->fec_set_idx, shred->data.parent_off, (ushort)(shred->idx - shred->fec_set_idx + 1), data_complete, slot_complete, is_leader_fec, ctx->store, &ctx->reasm_evicted );
2388 :
2389 0 : if( FD_UNLIKELY( !fec ) ) {
2390 : /* reasm failed to insert. We don't want to just put this back on
2391 : the returnable_frag queue because it's unclear whether this FEC
2392 : is truly something we want to process. Therefore our best option
2393 : is to punt it and "go around." Either the FEC was invalid and
2394 : was rejected or reasm_insert populates its last pool element with
2395 : the data of the failed insert, so we make sure to publish the
2396 : failed insert data to repair in after_credit. */
2397 0 : fd_store_remove( ctx->store, merkle_root );
2398 0 : return;
2399 0 : }
2400 0 : }
2401 :
2402 : static void
2403 0 : process_resolv_slot_completed( fd_replay_tile_t * ctx, ulong bank_idx ) {
2404 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, bank_idx );
2405 0 : FD_TEST( bank );
2406 0 : bank->refcnt--;
2407 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt decremented to %lu for resolv", bank->idx, bank->f.slot, bank->refcnt ));
2408 0 : }
2409 :
2410 : static void
2411 : process_vote_txn_sent( fd_replay_tile_t * ctx,
2412 0 : fd_txn_m_t * txnm ) {
2413 : /* The send tile has signed and sent a vote. Add this vote to the
2414 : vote tracker. We go through this exercise until the client has
2415 : seen a vote corresponding to the current identity rooted. */
2416 0 : if( FD_UNLIKELY( !ctx->identity_vote_rooted ) ) {
2417 0 : uchar * payload = (uchar *)txnm + sizeof(fd_txn_m_t);
2418 0 : uchar txn_mem[ FD_TXN_MAX_SZ ] __attribute__((aligned(alignof(fd_txn_t))));
2419 0 : fd_txn_t * txn = (fd_txn_t *)txn_mem;
2420 0 : if( FD_UNLIKELY( !fd_txn_parse( payload, txnm->payload_sz, txn_mem, NULL ) ) ) {
2421 0 : FD_LOG_CRIT(( "Could not parse txn from send tile" ));
2422 0 : }
2423 : /* The identity of the validator that the signed the vote will
2424 : always be the first signer in the vote transaction. */
2425 0 : fd_pubkey_t * vote_identity = fd_type_pun( payload+txn->acct_addr_off );
2426 0 : fd_vote_tracker_insert( ctx->vote_tracker, vote_identity, fd_type_pun_const( payload+txn->signature_off ) );
2427 0 : }
2428 0 : }
2429 :
2430 : static inline void
2431 0 : maybe_verify_shred_version( fd_replay_tile_t * ctx ) {
2432 0 : if( FD_LIKELY( ctx->expected_shred_version && ctx->ipecho_shred_version ) ) {
2433 0 : if( FD_UNLIKELY( ctx->expected_shred_version!=ctx->ipecho_shred_version ) ) {
2434 0 : FD_LOG_ERR(( "shred version mismatch: expected %u but got %u from ipecho", ctx->expected_shred_version, ctx->ipecho_shred_version ) );
2435 0 : }
2436 0 : }
2437 :
2438 : /* During a cluster restart, the configured shred version is the post-
2439 : restart value advertised by gossip. Defer comparing it against the
2440 : snapshot's hard fork list until wait-for-supermajority completes. */
2441 0 : if( FD_UNLIKELY( ctx->wfs_enabled && !ctx->wfs_complete && ctx->expected_shred_version ) ) return;
2442 :
2443 0 : if( FD_LIKELY( ctx->has_genesis_hash && ctx->hard_fork_cnt!=ULONG_MAX && (ctx->expected_shred_version || ctx->ipecho_shred_version) ) ) {
2444 0 : ushort expected_shred_version = ctx->expected_shred_version ? ctx->expected_shred_version : ctx->ipecho_shred_version;
2445 :
2446 0 : ushort actual_shred_version = compute_shred_version( ctx->genesis_hash->uc, ctx->hard_forks, ctx->hard_fork_cnt );
2447 :
2448 0 : if( FD_UNLIKELY( expected_shred_version!=actual_shred_version ) ) {
2449 0 : FD_BASE58_ENCODE_32_BYTES( ctx->genesis_hash->uc, genesis_hash_b58 );
2450 0 : FD_LOG_ERR(( "Your genesis.bin file at `%s` combined with the hard_forks from the loaded snapshot have produced "
2451 0 : "a shred version of %hu but the entrypoint you connected to on boot reported a shred version of %hu. "
2452 0 : "This likely means that the genesis.bin file you have is for a different cluster than the one you "
2453 0 : "are trying to connect to, you can delete it and restart the node to download the correct genesis "
2454 0 : "file automatically.", ctx->genesis_path, actual_shred_version, expected_shred_version ));
2455 0 : }
2456 0 : }
2457 0 : }
2458 :
2459 : static inline void
2460 0 : maybe_verify_genesis_timestamp( fd_replay_tile_t * ctx ) {
2461 0 : if( FD_LIKELY( !ctx->has_expected_genesis_timestamp || !ctx->has_genesis_timestamp ) ) return;
2462 0 : if( FD_LIKELY( ctx->genesis_timestamp==ctx->expected_genesis_timestamp ) ) return;
2463 :
2464 0 : FD_LOG_ERR(( "Your genesis.bin file at `%s` has a genesis timestamp of %lu but the snapshot you loaded has a genesis "
2465 0 : "timestamp of %lu. This either means that the genesis.bin file you have is for a different cluster than "
2466 0 : "the one you are trying to connect to, or you have loaded a snapshot for the wrong cluster. In either "
2467 0 : "case, you can delete the problematic file and restart the node to download the correct one automatically.",
2468 0 : ctx->genesis_path, ctx->genesis_timestamp, ctx->expected_genesis_timestamp ));
2469 0 : }
2470 :
2471 : static void
2472 : update_metric_identity_balance( fd_replay_tile_t * ctx,
2473 : fd_accdb_fork_id_t fork_id,
2474 0 : fd_pubkey_t const * identity ) {
2475 0 : ulong identity_balance = fd_accdb_lamports( ctx->accdb, fork_id, identity->uc );
2476 0 : FD_MGAUGE_SET( REPLAY, IDENTITY_BALANCE_LAMPORTS, identity_balance );
2477 0 : }
2478 :
2479 : static void
2480 : update_metric_epoch_credits( fd_replay_tile_t * ctx,
2481 : fd_bank_t const * bank,
2482 : fd_accdb_fork_id_t fork_id,
2483 0 : fd_pubkey_t const * vote_key ) {
2484 0 : ulong epoch_credits = 0UL;
2485 0 : fd_acc_t ro = fd_accdb_read_one( ctx->accdb, fork_id, vote_key->uc );
2486 0 : if( FD_LIKELY( ro.lamports ) ) {
2487 0 : fd_vote_state_versioned_t vsv[1];
2488 0 : if( FD_LIKELY( fd_vote_state_versioned_deserialize( vsv, ro.data, ro.data_len ) ) ) {
2489 0 : fd_vote_epoch_credits_t const * ec = fd_vsv_get_epoch_credits( vsv );
2490 0 : if( !deq_fd_vote_epoch_credits_t_empty( ec ) ) {
2491 0 : fd_vote_epoch_credits_t const * last_ec = deq_fd_vote_epoch_credits_t_peek_tail_const( ec );
2492 0 : if( last_ec->epoch==bank->f.epoch ) {
2493 0 : epoch_credits = last_ec->credits;
2494 0 : }
2495 0 : }
2496 0 : }
2497 0 : }
2498 0 : fd_accdb_unread_one( ctx->accdb, &ro );
2499 :
2500 0 : FD_MGAUGE_SET( REPLAY, EPOCH_CREDITS, epoch_credits );
2501 0 : }
2502 :
2503 : static void
2504 : update_metric_active_stake( fd_bank_t const * bank,
2505 0 : fd_pubkey_t const * vote_key ) {
2506 0 : ulong my_active_stake = 0UL;
2507 0 : ulong tot_active_stake = bank->f.total_epoch_stake;
2508 :
2509 0 : ulong stake = 0UL;
2510 0 : fd_vote_stakes_query_t_1( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, vote_key, NULL, &stake, NULL );
2511 0 : my_active_stake = stake;
2512 :
2513 0 : FD_MGAUGE_SET( REPLAY, ACTIVE_STAKE_LAMPORTS, my_active_stake );
2514 0 : FD_MGAUGE_SET( REPLAY, CLUSTER_ACTIVE_STAKE_LAMPORTS, tot_active_stake );
2515 0 : }
2516 :
2517 : static void
2518 : update_metric_balances( fd_replay_tile_t * ctx,
2519 0 : fd_bank_t * bank ) {
2520 0 : fd_accdb_fork_id_t fork_id = bank->accdb_fork_id;
2521 0 : fd_node_info_t node_info[1]; fd_node_info_read( node_info, ctx->node_info );
2522 0 : if( !fd_pubkey_check_zero( &node_info->identity ) ) {
2523 0 : update_metric_identity_balance( ctx, fork_id, &node_info->identity );
2524 0 : }
2525 :
2526 0 : if( !fd_pubkey_check_zero( &node_info->vote_account ) ) {
2527 0 : update_metric_epoch_credits( ctx, bank, fork_id, &node_info->vote_account );
2528 0 : update_metric_active_stake ( bank, &node_info->vote_account );
2529 0 : }
2530 0 : }
2531 :
2532 : static void
2533 : process_tower_optimistic_confirmed( fd_replay_tile_t * ctx,
2534 : fd_stem_context_t * stem,
2535 0 : fd_tower_slot_confirmed_t const * msg ) {
2536 :
2537 0 : fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &msg->block_id, NULL, ctx->block_id_arr );
2538 0 : if( FD_UNLIKELY( !block_id_ele ) ) {
2539 0 : FD_BASE58_ENCODE_32_BYTES( msg->block_id.key, block_id_b58 );
2540 0 : FD_LOG_WARNING(( "missing bank for confirmed block_id: %s level %d", block_id_b58, msg->level ));
2541 0 : return;
2542 0 : }
2543 :
2544 0 : ulong bank_idx = fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele );
2545 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, bank_idx );
2546 :
2547 0 : if( FD_UNLIKELY( !bank || bank->bank_seq!=block_id_ele->bank_seq || bank->state==FD_BANK_STATE_PRUNABLE ) ) {
2548 0 : FD_BASE58_ENCODE_32_BYTES( msg->block_id.key, block_id_cstr );
2549 0 : FD_LOG_WARNING(( "failed to query optimistically confirmed bank for block id %s", block_id_cstr ));
2550 0 : return;
2551 0 : }
2552 :
2553 0 : if( ctx->rpc_enabled ) {
2554 0 : bank->refcnt++;
2555 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for rpc", bank->idx, bank->f.slot, bank->refcnt ));
2556 0 : }
2557 :
2558 0 : fd_replay_oc_advanced_t * replay_msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
2559 0 : replay_msg->bank_idx = bank_idx;
2560 0 : replay_msg->bank_seq = bank->bank_seq;
2561 0 : replay_msg->slot = msg->slot;
2562 :
2563 0 : fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_OC_ADVANCED, ctx->replay_out->chunk, sizeof(fd_replay_oc_advanced_t), 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
2564 0 : ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(fd_replay_oc_advanced_t), ctx->replay_out->chunk0, ctx->replay_out->wmark );
2565 :
2566 0 : update_metric_balances( ctx, bank );
2567 0 : }
2568 :
2569 : /* snapmk_start instructs the snapmk tile to start producing a snapshot. */
2570 :
2571 : static void
2572 : snapmk_start( fd_replay_tile_t * ctx,
2573 : fd_stem_context_t * stem,
2574 0 : int incremental ) {
2575 :
2576 0 : FD_CHECK_CRIT( !ctx->snapmk.active, "snapshot creation already in progress" );
2577 :
2578 : /* pin current produced bank */
2579 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, ctx->published_root_bank_idx );
2580 0 : FD_CHECK_CRIT( bank, "invalid published_root_bank_idx" );
2581 :
2582 0 : if( FD_UNLIKELY( incremental ) ) {
2583 0 : FD_CHECK_CRIT( ctx->snapmk.base_slot!=ULONG_MAX, "incremental snapshot without a base full snapshot" );
2584 0 : FD_CHECK_CRIT( bank->f.slot>ctx->snapmk.base_slot, "incremental snapshot at or below its base slot" );
2585 0 : }
2586 :
2587 0 : bank->refcnt++;
2588 0 : ctx->snapmk.bank_idx = bank->idx;
2589 0 : ctx->snapmk.incremental = !!incremental;
2590 :
2591 : /* Send SNAP_START message to snapmk. */
2592 0 : fd_replay_snap_start_t * msg = fd_chunk_to_laddr( ctx->snapmk_out->mem, ctx->snapmk_out->chunk );
2593 0 : *msg = (fd_replay_snap_start_t) {
2594 0 : .bank_idx = ctx->published_root_bank_idx,
2595 0 : .base_slot = incremental ? ctx->snapmk.base_slot : bank->f.slot,
2596 0 : .slot = bank->f.slot
2597 0 : };
2598 0 : ulong out_idx = ctx->snapmk_out->idx;
2599 0 : ulong sig = REPLAY_SIG_SNAP_START;
2600 0 : ulong chunk = ctx->snapmk_out->chunk;
2601 0 : ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
2602 0 : ulong sz = sizeof(fd_replay_snap_start_t);
2603 0 : ulong seq = fd_stem_publish( stem, out_idx, sig, chunk, sz, 0UL, 0UL, tspub );
2604 0 : ctx->snapmk_out->chunk = fd_dcache_compact_next( ctx->snapmk_out->chunk, sz, ctx->snapmk_out->chunk0, ctx->snapmk_out->wmark );
2605 :
2606 : /* wake up the snapmk tile */
2607 0 : fd_frag_meta_t * replay_snapmk = stem->mcaches[ out_idx ];
2608 0 : ulong * snap_sync = fd_mcache_seq_laddr( replay_snapmk );
2609 0 : fd_mcache_seq_update( snap_sync, fd_seq_inc( seq, 1UL ) );
2610 0 : long ret = syscall( SYS_futex, snap_sync, FUTEX_WAKE, 1 );
2611 0 : if( FD_UNLIKELY( ret<0 ) ) {
2612 0 : FD_LOG_ERR(( "FUTEX_WAKE(snap_sync,seq=%u) failed (%i-%s)", (uint)seq, errno, fd_io_strerror( errno ) ));
2613 0 : }
2614 :
2615 : /* update internal state */
2616 0 : ctx->snapmk.active = 1;
2617 0 : }
2618 :
2619 : /* snapmk_done reacts to the snapmk tile reporting completion. */
2620 :
2621 : static void
2622 : snapmk_done( fd_replay_tile_t * ctx,
2623 : fd_stem_context_t * stem,
2624 0 : int success ) {
2625 0 : (void)stem;
2626 :
2627 0 : FD_CHECK_CRIT( ctx->snapmk.active, "spurious snap complete msg (not creating snapshot)" );
2628 :
2629 : /* release bank */
2630 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, ctx->snapmk.bank_idx );
2631 0 : FD_CHECK_CRIT( bank, "invalid snapmk.bank_idx" );
2632 0 : FD_CHECK_CRIT( bank->refcnt > 0UL, "invalid snapmk.bank_idx refcnt" );
2633 :
2634 : /* A completed full snapshot becomes the base of later incrementals. */
2635 0 : if( FD_LIKELY( success && !ctx->snapmk.incremental ) ) {
2636 0 : ctx->snapmk.base_slot = bank->f.slot;
2637 0 : }
2638 :
2639 0 : bank->refcnt--;
2640 0 : ctx->snapmk.active = 0;
2641 0 : }
2642 :
2643 : static void
2644 : msg_snapmk( fd_replay_tile_t * ctx,
2645 : fd_stem_context_t * stem,
2646 0 : ulong msg_type ) {
2647 0 : switch( msg_type ) {
2648 0 : case FD_SNAPMK_MSG_CREATED:
2649 0 : snapmk_done( ctx, stem, 1 );
2650 0 : break;
2651 0 : case FD_SNAPMK_MSG_FAILED:
2652 0 : snapmk_done( ctx, stem, 0 );
2653 0 : break;
2654 0 : default:
2655 0 : break;
2656 0 : }
2657 0 : }
2658 :
2659 : /* admin command handlers
2660 : every admin command must trigger one response frag */
2661 :
2662 : static void
2663 : admin_respond( fd_replay_tile_t * ctx,
2664 : fd_stem_context_t * stem,
2665 : ulong orig,
2666 0 : ulong err ) {
2667 0 : ulong ctl = fd_frag_meta_ctl( orig, 0, 0, !!err );
2668 0 : ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
2669 0 : fd_stem_publish( stem, ctx->admin_out_idx, err, 0UL, 0UL, ctl, 0UL, tspub );
2670 0 : }
2671 :
2672 : static void
2673 : admin_snap_create( fd_replay_tile_t * ctx,
2674 : fd_stem_context_t * stem,
2675 0 : ulong sig ) {
2676 0 : ulong target_slot = sig;
2677 :
2678 0 : if( FD_UNLIKELY( !ctx->snapmk.supported ) ) {
2679 0 : FD_LOG_WARNING(( "admin requested snapshot creation, but current config cannot create snapshots. increase [layout.snapzp_tile_count]?" ));
2680 0 : admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_SNAPSHOT_CREATE_RESULT_UNSUPPORTED );
2681 0 : return;
2682 0 : }
2683 :
2684 0 : if( FD_UNLIKELY( !ctx->is_booted ) ) {
2685 0 : FD_LOG_WARNING(( "admin requested snapshot creation, but client has not yet started" ));
2686 0 : admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_SNAPSHOT_CREATE_RESULT_NOT_READY );
2687 0 : return;
2688 0 : }
2689 :
2690 0 : if( FD_UNLIKELY( target_slot ) ) {
2691 0 : if( FD_UNLIKELY( target_slot<=ctx->published_root_slot ) ) {
2692 0 : FD_LOG_WARNING(( "admin requested snapshot creation at slot %lu, but rooting is already past it (published root slot %lu)", target_slot, ctx->published_root_slot ));
2693 0 : admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_SNAPSHOT_CREATE_RESULT_SLOT_IN_PAST );
2694 0 : return;
2695 0 : }
2696 :
2697 :
2698 0 : if( FD_UNLIKELY( ctx->snapmk.scheduled_at!=ULONG_MAX &&
2699 0 : ctx->snapmk.scheduled_at!=target_slot ) ) {
2700 0 : FD_LOG_WARNING(( "admin requested snapshot creation at slot %lu, but a snapshot is already scheduled at slot %lu. ignoring ...", target_slot, ctx->snapmk.scheduled_at ));
2701 0 : admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_SNAPSHOT_CREATE_RESULT_BUSY );
2702 0 : return;
2703 0 : }
2704 :
2705 0 : ctx->snapmk.scheduled_at = target_slot;
2706 0 : FD_LOG_NOTICE(( "snapshot creation scheduled at slot %lu", target_slot ));
2707 0 : admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_ADMINCTL_RESULT_SUCCESS );
2708 0 : return;
2709 0 : }
2710 :
2711 0 : if( FD_UNLIKELY( ctx->snapmk.active ) ) {
2712 0 : FD_LOG_WARNING(( "admin requested snapshot creation, but currently busy creating another snapshot. ignoring ..." ));
2713 0 : admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_SNAPSHOT_CREATE_RESULT_BUSY );
2714 0 : return;
2715 0 : }
2716 :
2717 0 : snapmk_start( ctx, stem, 0 );
2718 0 : admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_ADMINCTL_RESULT_SUCCESS );
2719 0 : }
2720 :
2721 : static void
2722 : msg_admin( fd_replay_tile_t * ctx,
2723 : fd_stem_context_t * stem,
2724 : ulong orig,
2725 0 : ulong sig ) {
2726 0 : switch( orig ) {
2727 0 : case FD_ADMINCTL_CMD_SNAP_CREATE:
2728 0 : admin_snap_create( ctx, stem, sig );
2729 0 : break;
2730 0 : default:
2731 0 : FD_LOG_CRIT(( "unknown admin cmd (orig=%lu, sig=%lu)", orig, sig ));
2732 0 : }
2733 0 : }
2734 :
2735 : static inline int
2736 : returnable_frag( fd_replay_tile_t * ctx,
2737 : ulong in_idx,
2738 : ulong seq,
2739 : ulong sig,
2740 : ulong chunk,
2741 : ulong sz,
2742 : ulong ctl,
2743 : ulong tsorig,
2744 : ulong tspub,
2745 0 : fd_stem_context_t * stem ) {
2746 0 : (void)seq;
2747 0 : (void)ctl;
2748 0 : (void)tsorig;
2749 0 : (void)tspub;
2750 :
2751 0 : if( FD_UNLIKELY( sz!=0UL && (chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) ) )
2752 0 : FD_LOG_CRIT(( "chunk %lu %lu from in %d corrupt, not in range [%lu,%lu]", chunk, sz, ctx->in_kind[ in_idx ], ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
2753 :
2754 0 : switch( ctx->in_kind[in_idx] ) {
2755 0 : case IN_KIND_GENESIS: {
2756 0 : fd_genesis_meta_t const * meta = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
2757 0 : ctx->has_genesis_hash = 1;
2758 0 : ctx->has_genesis_timestamp = 1;
2759 0 : ctx->genesis_timestamp = meta->creation_time_seconds;
2760 0 : *ctx->genesis_hash = meta->genesis_hash;
2761 0 : fd_node_info_write_begin( ctx->node_info );
2762 0 : ctx->node_info->info.genesis_hash = *ctx->genesis_hash;
2763 0 : fd_node_info_write_end( ctx->node_info );
2764 0 : if( FD_LIKELY( meta->bootstrap ) ) {
2765 0 : boot_genesis( ctx, stem, meta );
2766 0 : } else {
2767 0 : uchar const * genesis_blob = (uchar const *)( meta+1 );
2768 0 : FD_TEST( fd_genesis_parse( ctx->genesis, genesis_blob, meta->blob_sz ) );
2769 0 : }
2770 0 : ctx->has_genesis_timestamp = 1;
2771 0 : ctx->genesis_timestamp = ctx->genesis->creation_time;
2772 :
2773 0 : maybe_verify_cluster_type( ctx );
2774 0 : maybe_verify_shred_version( ctx );
2775 0 : maybe_verify_genesis_timestamp( ctx );
2776 0 : break;
2777 0 : }
2778 0 : case IN_KIND_IPECHO: {
2779 0 : FD_TEST( sig && sig<=USHORT_MAX );
2780 0 : ctx->ipecho_shred_version = (ushort)sig;
2781 0 : maybe_verify_shred_version( ctx );
2782 0 : break;
2783 0 : }
2784 0 : case IN_KIND_SNAP: {
2785 0 : on_snapshot_message( ctx, stem, in_idx, chunk, sig );
2786 0 : maybe_verify_cluster_type( ctx );
2787 0 : maybe_verify_shred_version( ctx );
2788 0 : maybe_verify_genesis_timestamp( ctx );
2789 0 : break;
2790 0 : }
2791 0 : case IN_KIND_EXECRP: {
2792 0 : process_exec_task_done( ctx, stem, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ), sig );
2793 0 : ctx->execrp_idle_cnt = 0UL;
2794 0 : break;
2795 0 : }
2796 0 : case IN_KIND_POH: {
2797 0 : process_poh_message( ctx, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
2798 0 : break;
2799 0 : }
2800 0 : case IN_KIND_RESOLV: {
2801 0 : fd_resolv_slot_exchanged_t * exchanged_slot = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
2802 0 : process_resolv_slot_completed( ctx, exchanged_slot->bank_idx );
2803 0 : break;
2804 0 : }
2805 0 : case IN_KIND_TOWER: {
2806 0 : if( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_DONE ) ) {
2807 0 : process_tower_slot_done( ctx, stem, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ), seq );
2808 0 : } else if( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_CONFIRMED ) ) {
2809 0 : fd_tower_slot_confirmed_t const * msg = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
2810 0 : if( msg->level==FD_TOWER_SLOT_CONFIRMED_OPTIMISTIC && !msg->fwd ) process_tower_optimistic_confirmed( ctx, stem, msg );
2811 0 : if( msg->level==FD_TOWER_SLOT_CONFIRMED_DUPLICATE ) fd_reasm_confirm( ctx->reasm, &msg->block_id );
2812 0 : } else if( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_IGNORED ) ) {
2813 0 : fd_tower_slot_ignored_t const * msg = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
2814 0 : fd_tower_slot_done_t ignored = {
2815 0 : .replay_slot = msg->slot,
2816 0 : .replay_bank_idx = msg->bank_idx,
2817 0 : .vote_slot = ULONG_MAX,
2818 0 : .reset_slot = ctx->reset_slot, /* Use most recent reset slot */
2819 0 : .reset_block_id = ctx->reset_block_id,
2820 0 : .root_slot = ULONG_MAX
2821 0 : };
2822 0 : process_tower_slot_done( ctx, stem, &ignored, seq );
2823 0 : }
2824 0 : break;
2825 0 : }
2826 0 : case IN_KIND_REPAIR: {
2827 : /* Store and reasm follow the invariant that any FEC in the
2828 : shred->out link, repair->out link, or reasm must be present in
2829 : store. If any FEC is rejected at this point, it must be
2830 : removed from store. See topology.c for more details. */
2831 0 : if( FD_UNLIKELY( sig==REPAIR_SIG_FEC || sig==REPAIR_SIG_FEC_LEADER || sig==REPAIR_SIG_FEC_INVALID ) ) {
2832 0 : process_fec_complete( ctx, sig, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
2833 0 : }
2834 0 : break;
2835 0 : }
2836 0 : case IN_KIND_TXSEND: {
2837 0 : process_vote_txn_sent( ctx, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
2838 0 : break;
2839 0 : }
2840 0 : case IN_KIND_GOSSIP_OUT: {
2841 0 : FD_TEST( sig==FD_GOSSIP_UPDATE_TAG_WFS_DONE );
2842 0 : ctx->wfs_complete = 1;
2843 0 : maybe_verify_shred_version( ctx );
2844 :
2845 : /* Recalculate next_leader_tickcount relative to now. The
2846 : original value was computed at boot time (in boot_genesis or
2847 : on_snapshot_message). */
2848 0 : ctx->next_leader_tickcount = LONG_MAX;
2849 0 : if( FD_LIKELY( ctx->next_leader_slot!=ULONG_MAX ) ) {
2850 0 : fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &ctx->reset_block_id, NULL, ctx->block_id_arr );
2851 0 : if( FD_LIKELY( block_id_ele ) ) {
2852 0 : fd_bank_t * reset_bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) );
2853 0 : if( FD_LIKELY( reset_bank && reset_bank->bank_seq==block_id_ele->bank_seq && reset_bank->state!=FD_BANK_STATE_PRUNABLE ) ) {
2854 0 : double slot_duration_ticks = (double)reset_bank->f.slot_params.ns_per_slot_adjusted*ctx->tick_per_ns;
2855 0 : ctx->next_leader_tickcount = (long)((double)(ctx->next_leader_slot-ctx->reset_slot-1UL)*slot_duration_ticks) + fd_tickcount();
2856 0 : }
2857 0 : }
2858 0 : }
2859 :
2860 0 : FD_LOG_NOTICE(( "Done waiting for supermajority. More than 80 percent of cluster stake has joined." ));
2861 0 : if( FD_LIKELY( ctx->replay_out->idx!=ULONG_MAX ) ) {
2862 0 : fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_WFS_DONE, ctx->replay_out->chunk, 0UL, 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
2863 0 : }
2864 0 : break;
2865 0 : }
2866 0 : case IN_KIND_RPC: {
2867 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, sig );
2868 0 : FD_TEST( bank );
2869 0 : bank->refcnt--;
2870 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt decremented to %lu for %s", bank->idx, bank->f.slot, bank->refcnt, ctx->in_kind[ in_idx ]==IN_KIND_RPC ? "rpc" : "gui" ));
2871 0 : break;
2872 0 : }
2873 0 : case IN_KIND_SNAPMK:
2874 0 : msg_snapmk( ctx, stem, sig );
2875 0 : break;
2876 0 : case IN_KIND_ADMIN:
2877 0 : msg_admin( ctx, stem, fd_frag_meta_ctl_orig( ctl ), sig );
2878 0 : break;
2879 0 : default:
2880 0 : FD_LOG_ERR(( "unhandled kind %d", ctx->in_kind[ in_idx ] ));
2881 0 : }
2882 :
2883 0 : return 0;
2884 0 : }
2885 :
2886 : static inline fd_replay_out_link_t
2887 : out1( fd_topo_t const * topo,
2888 : fd_topo_tile_t const * tile,
2889 0 : char const * name ) {
2890 0 : ulong idx = ULONG_MAX;
2891 :
2892 0 : for( ulong i=0UL; i<tile->out_cnt; i++ ) {
2893 0 : fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ];
2894 0 : if( !strcmp( link->name, name ) ) {
2895 0 : if( FD_UNLIKELY( idx!=ULONG_MAX ) ) FD_LOG_ERR(( "tile %s:%lu had multiple output links named %s but expected one", tile->name, tile->kind_id, name ));
2896 0 : idx = i;
2897 0 : }
2898 0 : }
2899 :
2900 0 : if( FD_UNLIKELY( idx==ULONG_MAX ) ) return (fd_replay_out_link_t){ .idx = ULONG_MAX, .mem = NULL, .chunk0 = 0, .wmark = 0, .chunk = 0 };
2901 :
2902 0 : void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp;
2903 0 : ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache );
2904 0 : ulong wmark = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, topo->links[ tile->out_link_id[ idx ] ].mtu );
2905 :
2906 0 : return (fd_replay_out_link_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0 };
2907 0 : }
2908 :
2909 : static void
2910 : privileged_init( fd_topo_t const * topo,
2911 0 : fd_topo_tile_t const * tile ) {
2912 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
2913 :
2914 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
2915 0 : fd_replay_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_tile_t), sizeof(fd_replay_tile_t) );
2916 :
2917 0 : if( FD_UNLIKELY( !strcmp( tile->replay.identity_key_path, "" ) ) ) FD_LOG_ERR(( "identity_key_path not set" ));
2918 :
2919 0 : ctx->identity_pubkey[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->replay.identity_key_path, /* pubkey only: */ 1 ) );
2920 0 : ctx->identity_idx = 0UL;
2921 0 : ctx->identity_dirty = 0;
2922 :
2923 0 : ctx->bundle.enabled = tile->replay.bundle.enabled;
2924 0 : if( FD_UNLIKELY( !tile->replay.bundle.vote_account_path[0] ) ) {
2925 0 : ctx->bundle.enabled = 0;
2926 0 : }
2927 :
2928 0 : if( FD_UNLIKELY( ctx->bundle.enabled ) ) {
2929 0 : if( FD_UNLIKELY( !fd_base58_decode_32( tile->replay.bundle.vote_account_path, ctx->bundle.vote_account.uc ) ) ) {
2930 0 : const uchar * vote_key = fd_keyload_load( tile->replay.bundle.vote_account_path, /* pubkey only: */ 1 );
2931 0 : fd_memcpy( ctx->bundle.vote_account.uc, vote_key, 32UL );
2932 0 : }
2933 0 : }
2934 :
2935 0 : FD_TEST( fd_rng_secure( &ctx->rng_seed, sizeof(ctx->rng_seed) ) );
2936 0 : FD_TEST( fd_rng_secure( &ctx->blockhash_seed, sizeof(ulong) ) );
2937 0 : FD_TEST( fd_rng_secure( &ctx->reasm_seed, sizeof(ulong) ) );
2938 0 : FD_TEST( fd_rng_secure( &ctx->vote_tracker_seed, sizeof(ulong) ) );
2939 0 : FD_TEST( fd_rng_secure( &ctx->block_id_map_seed, sizeof(ulong) ) );
2940 0 : FD_TEST( fd_rng_secure( &ctx->initial_block_id, sizeof(fd_hash_t) ) );
2941 0 : FD_TEST( fd_rng_secure( &ctx->runtime_stack_seed, sizeof(ulong) ) );
2942 0 : }
2943 :
2944 : static void
2945 : unprivileged_init( fd_topo_t const * topo,
2946 0 : fd_topo_tile_t const * tile ) {
2947 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
2948 :
2949 0 : ulong chain_cnt = fd_block_id_map_chain_cnt_est( tile->replay.max_live_slots );
2950 :
2951 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
2952 0 : fd_replay_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_tile_t), sizeof(fd_replay_tile_t) );
2953 0 : void * runtime_stack_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_runtime_stack_align(), fd_runtime_stack_footprint( FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS, FD_RUNTIME_MAX_STAKED_VOTE_ACCOUNTS, FD_RUNTIME_MAX_STAKE_ACCOUNTS ) );
2954 0 : void * block_id_arr_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_block_id_ele_t), sizeof(fd_block_id_ele_t) * tile->replay.max_live_slots );
2955 0 : void * block_id_map_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_block_id_map_align(), fd_block_id_map_footprint( chain_cnt ) );
2956 0 : void * _txncache = FD_SCRATCH_ALLOC_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->replay.max_live_slots ) );
2957 0 : void * _accdb = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->replay.max_live_slots ) );
2958 0 : void * reasm_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_reasm_align(), fd_reasm_footprint( tile->replay.fec_max ) );
2959 0 : void * sched_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_sched_align(), fd_sched_footprint( tile->replay.sched_depth, tile->replay.max_live_slots ) );
2960 0 : void * vote_tracker_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_tracker_align(), fd_vote_tracker_footprint() );
2961 0 : void * _capture_ctx = FD_SCRATCH_ALLOC_APPEND( l, fd_capture_ctx_align(), fd_capture_ctx_footprint() );
2962 0 : void * dump_proto_ctx_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_dump_proto_ctx_t), sizeof(fd_dump_proto_ctx_t) );
2963 0 : void * block_dump_ctx = NULL;
2964 0 : if( FD_UNLIKELY( tile->replay.dump_block_to_pb ) ) {
2965 0 : block_dump_ctx = FD_SCRATCH_ALLOC_APPEND( l, fd_block_dump_context_align(), fd_block_dump_context_footprint() );
2966 0 : }
2967 :
2968 0 : ctx->runtime_stack = fd_runtime_stack_join( fd_runtime_stack_new( runtime_stack_mem, FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS, FD_RUNTIME_MAX_STAKED_VOTE_ACCOUNTS, FD_RUNTIME_MAX_STAKE_ACCOUNTS, ctx->runtime_stack_seed ) );
2969 0 : FD_TEST( ctx->runtime_stack );
2970 :
2971 0 : ctx->wksp = topo->workspaces[ topo->objs[ tile->tile_obj_id ].wksp_id ].wksp;
2972 :
2973 0 : ulong store_obj_id = fd_pod_query_ulong( topo->props, "store", ULONG_MAX );
2974 0 : FD_TEST( store_obj_id!=ULONG_MAX );
2975 0 : ctx->store = fd_store_join( fd_topo_obj_laddr( topo, store_obj_id ) );
2976 0 : FD_TEST( ctx->store );
2977 :
2978 0 : ulong banks_obj_id = fd_pod_query_ulong( topo->props, "banks", ULONG_MAX );
2979 0 : FD_TEST( banks_obj_id!=ULONG_MAX );
2980 :
2981 0 : ctx->banks = fd_banks_join( fd_topo_obj_laddr( topo, banks_obj_id ) );
2982 0 : FD_TEST( ctx->banks );
2983 :
2984 0 : ulong node_info_obj_id = fd_pod_query_ulong( topo->props, "node_info", ULONG_MAX );
2985 0 : FD_TEST( node_info_obj_id!=ULONG_MAX );
2986 0 : ctx->node_info = fd_node_info_box_join( fd_topo_obj_laddr( topo, node_info_obj_id ) );
2987 0 : FD_TEST( ctx->node_info );
2988 0 : fd_node_info_write_begin( ctx->node_info );
2989 0 : ctx->node_info->info.identity = *ctx->identity_pubkey;
2990 0 : fd_node_info_write_end( ctx->node_info );
2991 :
2992 0 : FD_MGAUGE_SET( REPLAY, BANK_LIVE_MAX, fd_banks_pool_max_cnt( ctx->banks ) );
2993 :
2994 0 : ctx->consensus_root_slot = ULONG_MAX;
2995 0 : ctx->consensus_root = ctx->initial_block_id;
2996 0 : ctx->notified_root_slot = ULONG_MAX;
2997 0 : ctx->notified_root = ctx->initial_block_id;
2998 0 : ctx->notified_root_bank = NULL;
2999 0 : ctx->published_root_slot = ULONG_MAX;
3000 :
3001 0 : ctx->expected_shred_version = tile->replay.expected_shred_version;
3002 0 : ctx->ipecho_shred_version = 0;
3003 0 : fd_memcpy( ctx->genesis_path, tile->replay.genesis_path, sizeof(ctx->genesis_path) );
3004 0 : ctx->has_genesis_hash = 0;
3005 0 : ctx->has_cluster_type = 0;
3006 0 : ctx->has_genesis_timestamp = 0;
3007 0 : ctx->has_expected_genesis_timestamp = 0;
3008 0 : ctx->cluster_type = FD_CLUSTER_UNKNOWN;
3009 0 : ctx->hard_fork_cnt = ULONG_MAX;
3010 0 : ctx->has_manifest_block_id = 0;
3011 :
3012 0 : if( FD_UNLIKELY( ctx->bundle.enabled ) ) {
3013 0 : if( FD_UNLIKELY( !fd_bundle_crank_gen_init( ctx->bundle.gen,
3014 0 : (fd_acct_addr_t const *)tile->replay.bundle.tip_distribution_program_addr,
3015 0 : (fd_acct_addr_t const *)tile->replay.bundle.tip_payment_program_addr,
3016 0 : (fd_acct_addr_t const *)ctx->bundle.vote_account.uc,
3017 0 : (fd_acct_addr_t const *)ctx->bundle.vote_account.uc, "NAN", 0UL ) ) ) {
3018 0 : FD_LOG_ERR(( "failed to initialize bundle crank gen" ));
3019 0 : }
3020 0 : }
3021 :
3022 0 : FD_TEST( tile->replay.enable_features_cnt<=sizeof(ctx->enable_features)/sizeof(ctx->enable_features[0]) );
3023 0 : ctx->enable_features_cnt = tile->replay.enable_features_cnt;
3024 0 : for( ulong i=0UL; i<tile->replay.enable_features_cnt; i++ ) {
3025 0 : fd_memcpy( ctx->enable_features[ i ], tile->replay.enable_features[ i ], FD_BASE58_ENCODED_32_SZ );
3026 0 : }
3027 :
3028 0 : ulong progcache_obj_id; FD_TEST( (progcache_obj_id = fd_pod_query_ulong( topo->props, "progcache", ULONG_MAX ) )!=ULONG_MAX );
3029 0 : FD_TEST( fd_progcache_shmem_join( ctx->progcache, fd_topo_obj_laddr( topo, progcache_obj_id ) ) );
3030 :
3031 0 : fd_wksp_t * progcache_wksp = fd_wksp_containing( ctx->progcache->shmem );
3032 0 : FD_TEST( progcache_wksp );
3033 0 : fd_wksp_mon_init( ctx->progcache_wksp_mon, progcache_wksp, FD_WKSP_MON_DEFAULT_RATE, fd_tickcount() );
3034 :
3035 0 : void * _txncache_shmem = fd_topo_obj_laddr( topo, tile->replay.txncache_obj_id );
3036 0 : fd_txncache_shmem_t * txncache_shmem = fd_txncache_shmem_join( _txncache_shmem );
3037 0 : FD_TEST( txncache_shmem );
3038 0 : ctx->txncache = fd_txncache_join( fd_txncache_new( _txncache, txncache_shmem ) );
3039 0 : FD_TEST( ctx->txncache );
3040 :
3041 0 : void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->replay.accdb_obj_id );
3042 0 : fd_accdb_shmem_t * accdb_shmem = fd_accdb_shmem_join( _accdb_shmem );
3043 0 : FD_TEST( accdb_shmem );
3044 0 : ctx->accdb = fd_accdb_join( fd_accdb_new( _accdb, accdb_shmem, FD_ACCDB_FD_RW, 0UL, NULL ) );
3045 0 : FD_TEST( ctx->accdb );
3046 :
3047 0 : ctx->capture_ctx = NULL;
3048 0 : if( FD_UNLIKELY( strcmp( "", tile->replay.solcap_capture ) ) ) {
3049 0 : ctx->capture_ctx = fd_capture_ctx_join( fd_capture_ctx_new( _capture_ctx ) );
3050 0 : ctx->capture_ctx->solcap_start_slot = tile->replay.capture_start_slot;
3051 0 : ctx->capture_ctx->capture_solcap = 1;
3052 0 : }
3053 :
3054 0 : ctx->dump_proto_ctx = NULL;
3055 0 : if( FD_UNLIKELY( strcmp( "", tile->replay.dump_proto_dir ) ) ) {
3056 0 : ctx->dump_proto_ctx = dump_proto_ctx_mem;
3057 0 : ctx->dump_proto_ctx->dump_proto_output_dir = tile->replay.dump_proto_dir;
3058 0 : if( FD_LIKELY( tile->replay.dump_block_to_pb ) ) {
3059 0 : ctx->dump_proto_ctx->dump_block_to_pb = !!tile->replay.dump_block_to_pb;
3060 0 : }
3061 0 : }
3062 :
3063 0 : if( FD_UNLIKELY( tile->replay.dump_block_to_pb ) ) {
3064 0 : ctx->block_dump_ctx = fd_block_dump_context_join( fd_block_dump_context_new( block_dump_ctx ) );
3065 0 : } else {
3066 0 : ctx->block_dump_ctx = NULL;
3067 0 : }
3068 :
3069 0 : ctx->is_booted = 0;
3070 :
3071 0 : ctx->tick_per_ns = fd_tempo_tick_per_ns( NULL );
3072 :
3073 0 : ctx->larger_max_cost_per_block = tile->replay.larger_max_cost_per_block;
3074 :
3075 0 : FD_TEST( fd_rng_new( ctx->rng, ctx->rng_seed, 0UL ) );
3076 :
3077 0 : ctx->reasm = fd_reasm_join( fd_reasm_new( reasm_mem, tile->replay.fec_max, ctx->reasm_seed ) );
3078 0 : FD_TEST( ctx->reasm );
3079 0 : ctx->reasm_evicted = NULL;
3080 :
3081 0 : ctx->sched = fd_sched_join( fd_sched_new( sched_mem, ctx->rng, tile->replay.sched_depth, tile->replay.max_live_slots, fd_topo_tile_name_cnt( topo, "execrp" ) ) );
3082 0 : FD_TEST( ctx->sched );
3083 :
3084 0 : ctx->in_cnt = tile->in_cnt;
3085 0 : ctx->execrp_idle_cnt = 0UL;
3086 :
3087 0 : ctx->vote_tracker = fd_vote_tracker_join( fd_vote_tracker_new( vote_tracker_mem, ctx->vote_tracker_seed ) );
3088 0 : FD_TEST( ctx->vote_tracker );
3089 :
3090 0 : ctx->identity_vote_rooted = 0;
3091 :
3092 0 : ctx->wait_for_vote_to_start_leader = tile->replay.wait_for_vote_to_start_leader;
3093 :
3094 0 : ctx->wfs_enabled = memcmp( tile->replay.wait_for_supermajority_with_bank_hash.uc, ((fd_pubkey_t){ 0 }).uc, sizeof(fd_pubkey_t) );
3095 0 : ctx->expected_bank_hash = tile->replay.wait_for_supermajority_with_bank_hash;
3096 0 : ctx->wfs_complete = !ctx->wfs_enabled;
3097 :
3098 0 : ctx->mleaders = fd_multi_epoch_leaders_join( fd_multi_epoch_leaders_new( ctx->mleaders_mem ) );
3099 0 : FD_TEST( ctx->mleaders );
3100 :
3101 0 : ctx->is_leader = 0;
3102 0 : ctx->supports_leader = fd_topo_find_tile( topo, "pack", 0UL )!=ULONG_MAX;
3103 0 : ctx->snapmk.active = 0;
3104 0 : ctx->snapmk.supported = fd_topo_find_tile( topo, "snapmk", 0UL )!=ULONG_MAX;
3105 0 : ctx->snapmk.scheduled_at = ULONG_MAX;
3106 0 : ctx->snapmk.full_interval = tile->replay.full_snapshot_interval_slots;
3107 0 : ctx->snapmk.next_full_slot = ULONG_MAX;
3108 0 : ctx->snapmk.incremental_interval = tile->replay.incremental_snapshot_interval_slots;
3109 0 : ctx->snapmk.next_incremental_slot = ULONG_MAX;
3110 0 : ctx->snapmk.base_slot = ULONG_MAX;
3111 0 : if( FD_UNLIKELY( !ctx->snapmk.supported ) ) {
3112 0 : ctx->snapmk.full_interval = 0UL;
3113 0 : ctx->snapmk.incremental_interval = 0UL;
3114 0 : }
3115 0 : ctx->reset_slot = 0UL;
3116 0 : ctx->reset_block_id = ctx->initial_block_id;
3117 0 : ctx->reset_timestamp_nanos = 0UL;
3118 0 : ctx->next_leader_slot = ULONG_MAX;
3119 0 : ctx->next_leader_tickcount = LONG_MAX;
3120 0 : ctx->highwater_leader_slot = ULONG_MAX;
3121 :
3122 0 : ctx->caught_up = 0;
3123 0 : ctx->catch_up_max_fec_slot = ULONG_MAX;
3124 0 : ctx->catch_up_tip_advance_cnt = 0UL;
3125 0 : ctx->boot_timestamp_nanos = tile->replay.boot_timestamp_nanos;
3126 0 : ctx->leader_bank = NULL;
3127 :
3128 0 : ctx->block_id_len = tile->replay.max_live_slots;
3129 0 : ctx->block_id_arr = (fd_block_id_ele_t *)block_id_arr_mem;
3130 0 : ctx->block_id_map = fd_block_id_map_join( fd_block_id_map_new( block_id_map_mem, chain_cnt, ctx->block_id_map_seed ) );
3131 0 : FD_TEST( ctx->block_id_map );
3132 0 : for( ulong i=0UL; i<tile->replay.max_live_slots; i++ ) ctx->block_id_arr[ i ].block_id_seen = 0;
3133 :
3134 0 : ctx->resolv_tile_cnt = fd_topo_tile_name_cnt( topo, "resolv" );
3135 :
3136 0 : ctx->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
3137 0 : FD_TEST( ctx->keyswitch );
3138 0 : ctx->halt_leader = 0;
3139 :
3140 0 : FD_TEST( tile->in_cnt<=sizeof(ctx->in)/sizeof(ctx->in[0]) );
3141 0 : for( ulong i=0UL; i<tile->in_cnt; i++ ) {
3142 0 : fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
3143 0 : fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
3144 :
3145 0 : if( FD_LIKELY( link->dcache ) ) {
3146 0 : ctx->in[ i ].mem = link_wksp->wksp;
3147 0 : ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
3148 0 : ctx->in[ i ].wmark = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
3149 0 : ctx->in[ i ].mtu = link->mtu;
3150 0 : }
3151 :
3152 0 : if( !strcmp( link->name, "genesi_out" ) ) ctx->in_kind[ i ] = IN_KIND_GENESIS;
3153 0 : else if( !strcmp( link->name, "ipecho_out" ) ) ctx->in_kind[ i ] = IN_KIND_IPECHO;
3154 0 : else if( !strcmp( link->name, "snapin_manif" ) ) ctx->in_kind[ i ] = IN_KIND_SNAP;
3155 0 : else if( !strcmp( link->name, "execrp_replay" ) ) ctx->in_kind[ i ] = IN_KIND_EXECRP;
3156 0 : else if( !strcmp( link->name, "tower_out" ) ) ctx->in_kind[ i ] = IN_KIND_TOWER;
3157 0 : else if( !strcmp( link->name, "poh_replay" ) ) ctx->in_kind[ i ] = IN_KIND_POH;
3158 0 : else if( !strcmp( link->name, "resolv_replay" ) ) ctx->in_kind[ i ] = IN_KIND_RESOLV;
3159 0 : else if( !strcmp( link->name, "shred_out" ) ) ctx->in_kind[ i ] = IN_KIND_REPAIR;
3160 0 : else if( !strcmp( link->name, "repair_out" ) ) ctx->in_kind[ i ] = IN_KIND_REPAIR;
3161 0 : else if( !strcmp( link->name, "txsend_out" ) ) ctx->in_kind[ i ] = IN_KIND_TXSEND;
3162 0 : else if( !strcmp( link->name, "rpc_replay" ) ) ctx->in_kind[ i ] = IN_KIND_RPC;
3163 0 : else if( !strcmp( link->name, "gossip_out" ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP_OUT;
3164 0 : else if( !strcmp( link->name, "snapmk_out" ) ) ctx->in_kind[ i ] = IN_KIND_SNAPMK;
3165 0 : else if( !strcmp( link->name, "admin_replay" ) ) ctx->in_kind[ i ] = IN_KIND_ADMIN;
3166 0 : else FD_LOG_ERR(( "unexpected input link name %s", link->name ));
3167 :
3168 0 : if( ctx->in_kind[ i ]==IN_KIND_ADMIN ) {
3169 0 : FD_TEST( ( ctx->admin_out_idx = fd_topo_find_tile_out_link( topo, tile, "replay_admin", 0UL ) )!=ULONG_MAX );
3170 0 : }
3171 0 : }
3172 :
3173 0 : *ctx->epoch_out = out1( topo, tile, "replay_epoch" ); FD_TEST( ctx->epoch_out->idx!=ULONG_MAX );
3174 0 : *ctx->replay_out = out1( topo, tile, "replay_out" ); FD_TEST( ctx->replay_out->idx!=ULONG_MAX );
3175 0 : *ctx->snapmk_out = out1( topo, tile, "replay_snapmk" ); FD_TEST( ctx->snapmk.supported == (ctx->snapmk_out->idx!=ULONG_MAX) );
3176 0 : *ctx->exec_out = out1( topo, tile, "replay_execrp" ); FD_TEST( ctx->exec_out->idx!=ULONG_MAX );
3177 :
3178 0 : ctx->rpc_enabled = fd_topo_find_tile( topo, "rpc", 0UL )!=ULONG_MAX;
3179 :
3180 0 : if( FD_UNLIKELY( strcmp( "", tile->replay.solcap_capture ) ) ) {
3181 0 : ulong idx = fd_topo_find_tile_out_link( topo, tile, "cap_repl", 0UL );
3182 0 : FD_TEST( idx!=ULONG_MAX );
3183 0 : fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ idx ] ];
3184 :
3185 :
3186 0 : fd_capture_link_buf_t * cap_repl_out = ctx->cap_repl_out;
3187 0 : cap_repl_out->base.vt = &fd_capture_link_buf_vt;
3188 0 : cap_repl_out->idx = idx;
3189 0 : cap_repl_out->mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
3190 0 : cap_repl_out->chunk0 = fd_dcache_compact_chunk0( cap_repl_out->mem, link->dcache );
3191 0 : cap_repl_out->wmark = fd_dcache_compact_wmark( cap_repl_out->mem, link->dcache, link->mtu );
3192 0 : cap_repl_out->chunk = cap_repl_out->chunk0;
3193 0 : cap_repl_out->mcache = link->mcache;
3194 0 : cap_repl_out->depth = fd_mcache_depth( link->mcache );
3195 0 : cap_repl_out->seq = 0UL;
3196 :
3197 0 : ctx->capture_ctx->capctx_type.buf = cap_repl_out;
3198 0 : ctx->capture_ctx->capture_link = &cap_repl_out->base;
3199 0 : ctx->capture_ctx->current_txn_idx = 0UL;
3200 :
3201 :
3202 0 : ulong consumer_tile_idx = fd_topo_find_tile( topo, "solcap", 0UL );
3203 0 : fd_topo_tile_t const * consumer_tile = &topo->tiles[ consumer_tile_idx ];
3204 0 : cap_repl_out->fseq = NULL;
3205 0 : for( ulong j = 0UL; j < consumer_tile->in_cnt; j++ ) {
3206 0 : if( FD_UNLIKELY( consumer_tile->in_link_id[ j ] == link->id ) ) {
3207 0 : cap_repl_out->fseq = fd_fseq_join( fd_topo_obj_laddr( topo, consumer_tile->in_link_fseq_obj_id[ j ] ) );
3208 0 : FD_TEST( cap_repl_out->fseq );
3209 0 : break;
3210 0 : }
3211 0 : }
3212 0 : }
3213 :
3214 0 : fd_memset( &ctx->metrics, 0, sizeof(ctx->metrics) );
3215 :
3216 0 : fd_histf_join( fd_histf_new( ctx->metrics.store_query_wait, FD_MHIST_SECONDS_MIN( REPLAY, STORE_QUERY_WAIT_SECONDS ),
3217 0 : FD_MHIST_SECONDS_MAX( REPLAY, STORE_QUERY_WAIT_SECONDS ) ) );
3218 0 : fd_histf_join( fd_histf_new( ctx->metrics.store_query_work, FD_MHIST_SECONDS_MIN( REPLAY, STORE_QUERY_WORK_SECONDS ),
3219 0 : FD_MHIST_SECONDS_MAX( REPLAY, STORE_QUERY_WORK_SECONDS ) ) );
3220 :
3221 : /* Ensure precompiles are available, crash fast otherwise */
3222 0 : fd_precompiles();
3223 :
3224 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
3225 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
3226 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
3227 0 : }
3228 :
3229 : static ulong
3230 : populate_allowed_seccomp( fd_topo_t const * topo FD_FN_UNUSED,
3231 : fd_topo_tile_t const * tile FD_FN_UNUSED,
3232 : ulong out_cnt,
3233 0 : struct sock_filter * out ) {
3234 :
3235 0 : populate_sock_filter_policy_fd_replay_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), FD_ACCDB_FD_RW );
3236 0 : return sock_filter_policy_fd_replay_tile_instr_cnt;
3237 0 : }
3238 :
3239 : static ulong
3240 : populate_allowed_fds( fd_topo_t const * topo FD_FN_UNUSED,
3241 : fd_topo_tile_t const * tile FD_FN_UNUSED,
3242 : ulong out_fds_cnt,
3243 0 : int * out_fds ) {
3244 :
3245 0 : if( FD_UNLIKELY( out_fds_cnt<3UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
3246 :
3247 0 : ulong out_cnt = 0UL;
3248 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
3249 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
3250 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
3251 0 : out_fds[ out_cnt++ ] = FD_ACCDB_FD_RW; /* accounts db */
3252 :
3253 0 : return out_cnt;
3254 0 : }
3255 :
3256 : static inline void
3257 0 : during_housekeeping( fd_replay_tile_t * ctx ) {
3258 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
3259 0 : FD_CHECK_CRIT( ctx->halt_leader, "state machine corruption" );
3260 0 : FD_LOG_DEBUG(( "keyswitch: unhalting leader" ));
3261 0 : ctx->halt_leader = 0;
3262 0 : fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
3263 0 : }
3264 :
3265 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
3266 0 : FD_LOG_DEBUG(( "keyswitch: halting leader" ));
3267 0 : ctx->halt_leader = 1;
3268 0 : if( !ctx->is_leader ) maybe_switch_identity( ctx );
3269 0 : }
3270 0 : }
3271 :
3272 : #undef DEBUG_LOGGING
3273 :
3274 : /* counting carefully, after_credit can generate at most 8 frags and
3275 : returnable_frag boot_genesis can generate at most 7 frags, so 15 is a
3276 : conservative bound. */
3277 0 : #define STEM_BURST (15UL)
3278 :
3279 : /* fd_tempo_lazy_default( 16384 ) where 16384 is the minimum out-link
3280 : depth (i.e. cr_max) but excludes replay_epoch, which is so infrequent
3281 : credit availability is a non-issue. */
3282 0 : #define STEM_LAZY ((long)36865)
3283 :
3284 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_replay_tile_t
3285 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_replay_tile_t)
3286 :
3287 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
3288 0 : #define STEM_CALLBACK_AFTER_CREDIT after_credit
3289 0 : #define STEM_CALLBACK_BEFORE_FRAG before_frag
3290 0 : #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
3291 0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
3292 :
3293 : #include "../../disco/stem/fd_stem.c"
3294 :
3295 : fd_topo_run_tile_t fd_tile_replay = {
3296 : .name = "replay",
3297 : .populate_allowed_seccomp = populate_allowed_seccomp,
3298 : .populate_allowed_fds = populate_allowed_fds,
3299 : .scratch_align = scratch_align,
3300 : .scratch_footprint = scratch_footprint,
3301 : .privileged_init = privileged_init,
3302 : .unprivileged_init = unprivileged_init,
3303 : .run = stem_run,
3304 : };
|