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 "../../ballet/bls/fd_bls12_381.h"
9 : #include "fd_sched.h"
10 : #include "fd_execrp.h"
11 : #include "generated/fd_replay_tile_seccomp.h"
12 :
13 : #include "../admin/fd_adminctl.h"
14 : #include <errno.h>
15 : #include <fcntl.h>
16 : #include "../genesis/fd_genesi_tile.h"
17 : #include "../poh/fd_poh.h"
18 : #include "../poh/fd_poh_tile.h"
19 : #include "../tower/fd_tower_tile.h"
20 : #include "../votor/fd_votor_tile.h"
21 : #include "../resolv/fd_resolv_tile.h"
22 : #include "../restore/utils/fd_ssload.h"
23 :
24 : #include "../../disco/tiles.h"
25 : #include "../../disco/fd_txn_m.h"
26 : #include "../../disco/shred/fd_fec_set.h"
27 : #include "../../disco/shred/fd_shred_tile.h"
28 : #include "../../disco/pack/fd_pack.h"
29 : #include "../../disco/pack/fd_pack_cost.h"
30 : #include "../backup/fd_snapmk_tile.h"
31 : #include "../reasm/fd_reasm.h"
32 : #include "../../disco/keyguard/fd_keyload.h"
33 : #include "../../disco/genesis/fd_genesis_cluster.h"
34 : #include "../../discof/genesis/genesis_hash.h"
35 : #include "../../util/pod/fd_pod.h"
36 : #include "../../flamenco/rewards/fd_rewards.h"
37 : #include "../../flamenco/leaders/fd_multi_epoch_leaders.h"
38 : #include "../../flamenco/progcache/fd_progcache_admin.h"
39 : #include "../../flamenco/rewards/fd_rewards.h"
40 : #include "../../disco/metrics/fd_metrics.h"
41 : #include "../../flamenco/events/fd_event_runtime.h"
42 : #include "../../disco/events/generated/fd_event_gen.h"
43 : #include "../repair/fd_repair_tile.h"
44 : #include "../rotor/fd_rotor_tile.h"
45 : #include "../../flamenco/runtime/fd_runtime.h"
46 : #include "../../flamenco/runtime/fd_runtime_stack_tmpl.h"
47 :
48 : #include "../../flamenco/runtime/sysvar/fd_sysvar_cache.h"
49 : #include "../../flamenco/runtime/sysvar/fd_sysvar_stake_history.h"
50 : #include "../../flamenco/runtime/sysvar/fd_sysvar_clock.h"
51 : #include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h"
52 : #include "../../flamenco/runtime/sysvar/fd_sysvar_rent.h"
53 : #include "../../flamenco/runtime/program/fd_precompiles.h"
54 : #include "../../flamenco/runtime/program/vote/fd_vote_state_versioned.h"
55 : #include "../../flamenco/runtime/program/vote/fd_vote_codec_tmpl.h"
56 : #include "../../flamenco/runtime/tests/fd_dump_pb.h"
57 : #include "../../disco/events/fd_event_report.h"
58 :
59 : /* Replay concepts:
60 :
61 : - Blocks are aggregations of entries aka. microblocks which are
62 : groupings of txns and are constructed by the block producer (see
63 : fd_pack).
64 :
65 : - Entries are grouped into entry batches by the block producer (see
66 : fd_pack / fd_shredder).
67 :
68 : - Entry batches are divided into chunks known as shreds by the block
69 : producer (see fd_shredder).
70 :
71 : - Shreds are grouped into forward-error-correction sets (FEC sets) by
72 : the block producer (see fd_shredder).
73 :
74 : - Shreds are transmitted to the rest of the cluster via the Turbine
75 : protocol (see fd_shredder / fd_shred).
76 :
77 : - Once enough shreds within a FEC set are received to recover the
78 : entirety of the shred data encoded by that FEC set, the receiver
79 : can "complete" the FEC set (see fd_fec_resolver).
80 :
81 : - If shreds in the FEC set are missing such that it can't complete,
82 : the receiver can use the Repair protocol to request missing shreds
83 : in FEC set (see fd_repair).
84 :
85 : - The current Repair protocol does not support requesting coding
86 : shreds. As a result, some FEC sets might be actually complete
87 : (contain all data shreds). Repair currently hacks around this by
88 : forcing completion but the long-term solution is to add support for
89 : fec_repairing coding shreds via Repair.
90 :
91 : - FEC sets are delivered in partial-order to the Replay tile by the
92 : Repair tile. Currently Replay only supports replaying entry batches
93 : so FEC sets need to reassembled into an entry batch before they can
94 : be replayed. The new Dispatcher will change this by taking a FEC
95 : set as input instead. */
96 :
97 0 : #define IN_KIND_SNAP ( 0)
98 0 : #define IN_KIND_GENESIS ( 1)
99 0 : #define IN_KIND_IPECHO ( 2)
100 0 : #define IN_KIND_TOWER ( 3)
101 0 : #define IN_KIND_RESOLV ( 4)
102 0 : #define IN_KIND_POH ( 5)
103 0 : #define IN_KIND_EXECRP ( 6)
104 0 : #define IN_KIND_REPAIR ( 7)
105 0 : #define IN_KIND_TXSEND ( 8)
106 0 : #define IN_KIND_RPC ( 9)
107 0 : #define IN_KIND_GOSSIP_OUT (10)
108 0 : #define IN_KIND_SNAPMK (11)
109 0 : #define IN_KIND_ADMIN (12)
110 0 : #define IN_KIND_VOTOR (13)
111 :
112 : #define DEBUG_LOGGING 0
113 :
114 : /* The first bank that the replay tile produces either for genesis
115 : or the snapshot boot will always be at bank index 0. */
116 0 : #define FD_REPLAY_BOOT_BANK_SEQ (0UL)
117 :
118 : static inline ulong
119 0 : fd_block_id_ele_get_idx( fd_block_id_ele_t * ele_arr, fd_block_id_ele_t * ele ) {
120 0 : return (ulong)(ele - ele_arr);
121 0 : }
122 :
123 : static inline fd_block_id_ele_t *
124 : fd_block_id_ele_query( fd_replay_tile_t * ctx,
125 : fd_hash_t const * block_id,
126 0 : ulong slot ) {
127 0 : if( !ctx->alpenglow ) {
128 0 : return fd_block_id_map_ele_query( ctx->block_id_map, block_id, NULL, ctx->block_id_arr );
129 0 : } else {
130 0 : ag_block_id_t key = ag_block_id( slot, block_id->uc );
131 0 : return fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &key, NULL, ctx->block_id_arr );
132 0 : }
133 0 : }
134 :
135 : FD_FN_CONST static inline ulong
136 0 : scratch_align( void ) {
137 0 : return 128UL;
138 0 : }
139 : FD_FN_PURE static inline ulong
140 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
141 0 : ulong chain_cnt = fd_block_id_map_chain_cnt_est( tile->replay.max_live_slots );
142 :
143 0 : ulong l = FD_LAYOUT_INIT;
144 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_replay_tile_t), sizeof(fd_replay_tile_t) );
145 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 ) );
146 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_block_id_ele_t), sizeof(fd_block_id_ele_t) * tile->replay.max_live_slots );
147 0 : if( FD_UNLIKELY( tile->replay.report_runtime_diffs ) ) {
148 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_hash_t), sizeof(fd_hash_t) * FD_FEC_BLK_MAX * tile->replay.max_live_slots );
149 0 : l = FD_LAYOUT_APPEND( l, 8UL, FD_EVENT_RUNTIME_SLOT_DIFFS_FOOTPRINT * tile->replay.max_live_slots );
150 0 : }
151 0 : if( !tile->replay.alpenglow ) {
152 0 : l = FD_LAYOUT_APPEND( l, fd_block_id_map_align(), fd_block_id_map_footprint( chain_cnt ) );
153 0 : } else {
154 0 : l = FD_LAYOUT_APPEND( l, fd_ag_block_id_map_align(), fd_ag_block_id_map_footprint( chain_cnt ) );
155 0 : }
156 0 : l = FD_LAYOUT_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->replay.max_live_slots ) );
157 0 : l = FD_LAYOUT_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->replay.max_live_slots ) );
158 0 : if( !tile->replay.alpenglow ) {
159 0 : l = FD_LAYOUT_APPEND( l, fd_reasm_align(), fd_reasm_footprint( tile->replay.fec_max ) );
160 0 : }
161 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_reception_stats_t), sizeof(fd_reception_stats_t)*tile->replay.max_live_slots );
162 0 : l = FD_LAYOUT_APPEND( l, fd_sched_align(), fd_sched_footprint( tile->replay.sched_depth, tile->replay.max_live_slots, tile->replay.max_shreds_per_block, tile->replay.max_txn_per_slot ) );
163 0 : l = FD_LAYOUT_APPEND( l, fd_vote_tracker_align(), fd_vote_tracker_footprint() );
164 0 : l = FD_LAYOUT_APPEND( l, fd_capture_ctx_align(), fd_capture_ctx_footprint() );
165 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_dump_proto_ctx_t), sizeof(fd_dump_proto_ctx_t) );
166 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_event_block_completed_t), sizeof(fd_event_block_completed_t) );
167 0 : l = FD_LAYOUT_APPEND( l, fd_timing_slot_pool_align(), fd_timing_slot_pool_footprint( FD_REPLAY_TXN_TIMING_SLOTS ) );
168 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_replay_txn_timing_t), FD_REPLAY_TXN_TIMING_SLOTS*tile->replay.max_txn_per_slot*sizeof(fd_replay_txn_timing_t) );
169 0 : l = FD_LAYOUT_APPEND( l, alignof(ulong), tile->replay.max_live_slots*sizeof(ulong) );
170 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_reasm_fec_t *), (tile->replay.max_shreds_per_block/FD_FEC_SHRED_CNT)*sizeof(fd_reasm_fec_t *) );
171 :
172 0 : if( FD_UNLIKELY( tile->replay.dump_block_to_pb ) ) {
173 0 : l = FD_LAYOUT_APPEND( l, fd_block_dump_context_align(), fd_block_dump_context_footprint() );
174 0 : }
175 :
176 0 : l = FD_LAYOUT_FINI( l, scratch_align() );
177 :
178 0 : return l;
179 0 : }
180 :
181 : static inline void
182 0 : metrics_write( fd_replay_tile_t * ctx ) {
183 0 : fd_accdb_flush_metrics( ctx->accdb );
184 :
185 0 : ulong store_fec_spilled = 0UL;
186 0 : ulong store_fec_spill_bytes= 0UL;
187 0 : ulong store_fec_spill_reads= 0UL;
188 0 : ulong store_fec_spill_read_bytes = 0UL;
189 0 : ulong store_fec_cache_used = 0UL;
190 0 : ulong store_fec_cache_max = 0UL;
191 0 : ulong store_fec_cache_pinned = 0UL;
192 0 : if( FD_LIKELY( ctx->store ) ) {
193 0 : store_fec_spilled = FD_VOLATILE_CONST( ctx->store->fec_spill_cnt );
194 0 : store_fec_spill_bytes = FD_VOLATILE_CONST( ctx->store->fec_spill_bytes );
195 0 : store_fec_spill_reads = FD_VOLATILE_CONST( ctx->store->fec_spill_read_cnt );
196 0 : store_fec_spill_read_bytes = FD_VOLATILE_CONST( ctx->store->fec_spill_read_bytes );
197 0 : store_fec_cache_pinned = FD_VOLATILE_CONST( ctx->store->cache_pinned_cnt );
198 :
199 0 : store_fec_cache_max = FD_VOLATILE_CONST( ctx->store->cache_slot_cnt );
200 0 : ulong store_fec_cache_free = fd_ulong_min( FD_VOLATILE_CONST( ctx->store->cache_free_cnt ), store_fec_cache_max );
201 0 : store_fec_cache_used = store_fec_cache_max - store_fec_cache_free;
202 0 : }
203 :
204 0 : FD_MHIST_COPY( REPLAY, STORE_QUERY_WORK_SECONDS, ctx->metrics.store_query_work );
205 0 : FD_MCNT_SET ( REPLAY, STORE_QUERIED, ctx->metrics.store_query_cnt );
206 0 : FD_MCNT_SET ( REPLAY, STORE_QUERY_MISSING, ctx->metrics.store_query_missing_cnt );
207 0 : FD_MCNT_SET ( REPLAY, STORE_FEC_SPILLED, store_fec_spilled );
208 0 : FD_MCNT_SET ( REPLAY, STORE_FEC_SPILL_BYTES, store_fec_spill_bytes );
209 0 : FD_MCNT_SET ( REPLAY, STORE_FEC_SPILL_READS, store_fec_spill_reads );
210 0 : FD_MCNT_SET ( REPLAY, STORE_FEC_SPILL_READ_BYTES, store_fec_spill_read_bytes );
211 0 : FD_MGAUGE_SET( REPLAY, STORE_FEC_CACHE_USED, store_fec_cache_used );
212 0 : FD_MGAUGE_SET( REPLAY, STORE_FEC_CACHE_MAX, store_fec_cache_max );
213 0 : FD_MGAUGE_SET( REPLAY, STORE_FEC_CACHE_PINNED, store_fec_cache_pinned );
214 0 : FD_MGAUGE_SET( REPLAY, STORE_QUERY_MERKLE_ROOT_SAMPLE, ctx->metrics.store_query_mr );
215 0 : FD_MGAUGE_SET( REPLAY, STORE_QUERY_MISSING_MERKLE_ROOT_SAMPLE, ctx->metrics.store_query_missing_mr );
216 :
217 0 : FD_MGAUGE_SET( REPLAY, ROOT_SLOT, ctx->consensus_root_slot==ULONG_MAX ? 0UL : ctx->consensus_root_slot );
218 0 : ulong leader_slot = ctx->leader_bank ? ctx->leader_bank->f.slot : 0UL;
219 :
220 0 : if( FD_LIKELY( ctx->leader_bank ) ) {
221 0 : FD_MGAUGE_SET( REPLAY, NEXT_LEADER_SLOT, leader_slot );
222 0 : FD_MGAUGE_SET( REPLAY, LEADER_SLOT, leader_slot );
223 0 : } else {
224 0 : FD_MGAUGE_SET( REPLAY, NEXT_LEADER_SLOT, ctx->next_leader_slot==ULONG_MAX ? 0UL : ctx->next_leader_slot );
225 0 : FD_MGAUGE_SET( REPLAY, LEADER_SLOT, 0UL );
226 0 : }
227 0 : FD_MGAUGE_SET( REPLAY, RESET_SLOT, ctx->reset_slot==ULONG_MAX ? 0UL : ctx->reset_slot );
228 0 : FD_MGAUGE_SET( REPLAY, VOTE_SLOT_LAST_REWARDED, ctx->metrics.voted_slot );
229 :
230 0 : FD_MGAUGE_SET( REPLAY, BANK_LIVE, fd_banks_pool_used_cnt( ctx->banks ) );
231 :
232 0 : ulong reasm_free = ctx->reasm ? fd_reasm_free( ctx->reasm ) : 0UL;
233 0 : FD_MGAUGE_SET( REPLAY, REASSEMBLY_FREE, reasm_free );
234 :
235 0 : FD_MCNT_SET( REPLAY, SLOT_REPLAYED, ctx->metrics.slots_total );
236 0 : FD_MCNT_SET( REPLAY, TXN_PROCESSED, ctx->metrics.transactions_total );
237 :
238 0 : FD_MGAUGE_SET( REPLAY, REASSEMBLY_LATEST_SLOT, ctx->metrics.reasm_latest_slot );
239 0 : FD_MGAUGE_SET( REPLAY, REASSEMBLY_LATEST_FEC_INDEX, ctx->metrics.reasm_latest_fec_idx );
240 :
241 0 : fd_sched_metrics_write( ctx->sched );
242 :
243 0 : FD_MCNT_SET( REPLAY, FEC_SCHED_FULL, ctx->metrics.sched_full );
244 0 : FD_MCNT_SET( REPLAY, FEC_REASSEMBLY_EMPTY, ctx->metrics.reasm_empty );
245 0 : FD_MCNT_SET( REPLAY, FEC_LEADER_BID_WAIT, ctx->metrics.leader_bid_wait );
246 0 : FD_MCNT_SET( REPLAY, FEC_BANK_FULL, ctx->metrics.banks_full );
247 0 : FD_MCNT_SET( REPLAY, STORAGE_ROOT_BEHIND, ctx->metrics.storage_root_behind );
248 :
249 0 : fd_progcache_admin_metrics_t const * pcm = &fd_progcache_admin_metrics_g;
250 0 : FD_MCNT_SET( REPLAY, PROGCACHE_ROOTED, pcm->root_cnt );
251 :
252 : /* Shared metrics for the program cache */
253 0 : ulong pc_class_used[ FD_PROGCACHE_CACHE_CLASS_CNT ];
254 0 : ulong pc_class_max [ FD_PROGCACHE_CACHE_CLASS_CNT ];
255 0 : fd_progcache_cache_class_occupancy( ctx->progcache, pc_class_used, pc_class_max );
256 0 : FD_MGAUGE_ENUM_COPY( REPLAY, PROGCACHE_CLASS_USED, pc_class_used );
257 0 : FD_MGAUGE_ENUM_COPY( REPLAY, PROGCACHE_CLASS_MAX, pc_class_max );
258 :
259 0 : FD_ACCDB_METRICS_WRITE( REPLAY, fd_accdb_metrics( ctx->accdb ) );
260 0 : }
261 :
262 : static ushort
263 : replay_voter_rank( fd_replay_tile_t * ctx,
264 : fd_bank_t * bank,
265 0 : ulong epoch ) {
266 0 : if( FD_LIKELY( !ctx->alpenglow ) ) return USHORT_MAX;
267 :
268 0 : ulong fork_id = bank->vote_stakes_fork_id;
269 0 : ulong fork_epoch = fd_vote_stakes_fork_epoch( fork_id );
270 0 : int iter_kind = FD_VOTE_STAKES_ITER_T_2;
271 0 : if( FD_UNLIKELY( epoch!=fork_epoch ) ) {
272 0 : if( FD_UNLIKELY( !fork_epoch || epoch!=fork_epoch-1UL ) ) return USHORT_MAX;
273 0 : iter_kind = FD_VOTE_STAKES_ITER_T_3;
274 0 : }
275 :
276 0 : fd_vote_stakes_t const * vote_stakes = fd_bank_vote_stakes( bank );
277 0 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
278 0 : for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, iter_kind, iter_mem );
279 0 : !fd_vote_stakes_iter_done( vote_stakes, fork_id, iter_kind, iter );
280 0 : fd_vote_stakes_iter_next( vote_stakes, fork_id, iter_kind, iter ) ) {
281 0 : fd_pubkey_t vote_key;
282 0 : fd_pubkey_t identity;
283 0 : ushort rank;
284 0 : fd_vote_stakes_iter_ele( vote_stakes, fork_id, iter_kind, iter, &vote_key, &identity,
285 0 : NULL, NULL, NULL, NULL, NULL, &rank, NULL, NULL );
286 0 : if( FD_UNLIKELY( fd_pubkey_eq( &identity, ctx->identity_pubkey ) ) ) return rank;
287 0 : }
288 0 : return USHORT_MAX;
289 0 : }
290 :
291 : static int
292 : replay_reward_cert_voted( fd_replay_tile_t * ctx,
293 : fd_bank_t * bank,
294 0 : ushort * rank_out ) {
295 0 : *rank_out = USHORT_MAX;
296 0 : if( FD_LIKELY( !ctx->alpenglow ) ) return 0;
297 :
298 0 : if( FD_UNLIKELY( bank->f.slot<FD_NUM_SLOTS_FOR_REWARD ) ) return 0;
299 :
300 0 : ulong reward_slot = bank->f.slot-FD_NUM_SLOTS_FOR_REWARD;
301 0 : ulong reward_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, reward_slot, NULL );
302 0 : ushort rank = replay_voter_rank( ctx, bank, reward_epoch );
303 0 : *rank_out = rank;
304 :
305 0 : fd_block_footer_t const * footer = bank==ctx->leader_bank ? ctx->leader_footer : fd_sched_get_footer( ctx->sched, bank->idx );
306 0 : if( FD_LIKELY( !footer || ( !footer->has_skip_reward_cert && !footer->has_notar_reward_cert ) ) ) return 0;
307 :
308 0 : if( FD_UNLIKELY( rank==USHORT_MAX ) ) return 0;
309 :
310 : /* bits at or past the cert's nbits are left clear at decode, so the
311 : set test alone bounds the rank */
312 0 : int in_cert = ( footer->has_skip_reward_cert && fd_bls_set_test( footer->skip_reward_cert.signer_set, rank ) ) ||
313 0 : ( footer->has_notar_reward_cert && fd_bls_set_test( footer->notar_reward_cert.signer_set, rank ) );
314 :
315 0 : if( FD_UNLIKELY( in_cert && ( ctx->metrics.voted_slot==ULONG_MAX || reward_slot>ctx->metrics.voted_slot ) ) ) ctx->metrics.voted_slot = reward_slot;
316 0 : return in_cert;
317 0 : }
318 :
319 : static void
320 : publish_epoch_info( fd_replay_tile_t * ctx,
321 : fd_stem_context_t * stem,
322 : fd_bank_t * bank,
323 0 : int next_epoch ) {
324 0 : fd_epoch_schedule_t const * schedule = &bank->f.epoch_schedule;
325 0 : ulong epoch = fd_slot_to_epoch( schedule, bank->f.slot, NULL ) + fd_ulong_if( next_epoch, 1UL, 0UL );
326 :
327 0 : fd_features_t const * features = &bank->f.features;
328 :
329 0 : fd_runtime_stack_t * runtime_stack = ctx->runtime_stack;
330 :
331 0 : fd_epoch_info_msg_t * epoch_info_msg = fd_chunk_to_laddr( ctx->epoch_out->mem, ctx->epoch_out->chunk );
332 :
333 0 : epoch_info_msg->staked_vote_cnt = next_epoch ? runtime_stack->epoch_weights.next_stake_weights_cnt : runtime_stack->epoch_weights.stake_weights_cnt;
334 0 : epoch_info_msg->staked_id_cnt = next_epoch ? runtime_stack->epoch_weights.next_id_weights_cnt : runtime_stack->epoch_weights.id_weights_cnt;
335 0 : epoch_info_msg->epoch_schedule = *schedule;
336 0 : epoch_info_msg->features = *features;
337 0 : epoch_info_msg->epoch = epoch;
338 0 : epoch_info_msg->start_slot = fd_epoch_slot0( schedule, epoch );
339 0 : epoch_info_msg->slot_cnt = fd_epoch_slot_cnt( schedule, epoch );
340 0 : epoch_info_msg->ns_per_slot = fd_slot_params_at_slot( bank, epoch_info_msg->start_slot ).ns_per_slot;
341 :
342 0 : fd_vote_stake_weight_t * stake_weights = fd_type_pun( epoch_info_msg + 1 );
343 0 : fd_vote_stake_weight_t * src_stake_weights = next_epoch ? runtime_stack->epoch_weights.next_stake_weights : runtime_stack->epoch_weights.stake_weights;
344 0 : memcpy( stake_weights, src_stake_weights, epoch_info_msg->staked_vote_cnt * sizeof(fd_vote_stake_weight_t) );
345 :
346 0 : fd_stake_weight_t * id_weights = fd_epoch_info_msg_id_weights( epoch_info_msg );
347 0 : fd_stake_weight_t * src_id_weights = next_epoch ? runtime_stack->epoch_weights.next_id_weights : runtime_stack->epoch_weights.id_weights;
348 0 : fd_memcpy( id_weights, src_id_weights, epoch_info_msg->staked_id_cnt * sizeof(fd_stake_weight_t) );
349 :
350 0 : ulong epoch_info_sz = fd_epoch_info_msg_sz( epoch_info_msg->staked_vote_cnt, epoch_info_msg->staked_id_cnt );
351 0 : ulong epoch_info_sig = 4UL;
352 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() ) );
353 0 : ctx->epoch_out->chunk = fd_dcache_compact_next( ctx->epoch_out->chunk, epoch_info_sz, ctx->epoch_out->chunk0, ctx->epoch_out->wmark );
354 :
355 0 : fd_multi_epoch_leaders_epoch_msg_init( ctx->mleaders, epoch_info_msg );
356 0 : fd_multi_epoch_leaders_epoch_msg_fini( ctx->mleaders );
357 0 : }
358 :
359 : /**********************************************************************/
360 : /* Transaction execution state machine helpers */
361 : /**********************************************************************/
362 :
363 : static inline void
364 : timing_slot_release( fd_replay_tile_t * ctx,
365 0 : ulong bank_idx ) {
366 0 : ulong tslot = ctx->timing_slot_of_bank[ bank_idx ];
367 0 : if( FD_LIKELY( tslot!=fd_timing_slot_pool_idx_null( ctx->timing_slot_pool ) ) ) {
368 0 : fd_timing_slot_pool_idx_release( ctx->timing_slot_pool, tslot );
369 0 : ctx->timing_slot_of_bank[ bank_idx ] = fd_timing_slot_pool_idx_null( ctx->timing_slot_pool );
370 0 : }
371 0 : }
372 :
373 : static void
374 : replay_block_start( fd_replay_tile_t * ctx,
375 : ulong bank_idx,
376 : ulong parent_bank_idx,
377 0 : ulong slot ) {
378 0 : long before = fd_clock_tile_now( ctx->clock );
379 :
380 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, bank_idx );
381 0 : FD_CHECK_CRIT( bank, "invariant violation: bank is NULL" );
382 0 : FD_CHECK_CRIT( bank->state==FD_BANK_STATE_INIT, "invariant violation: bank is not in correct state" );
383 :
384 0 : bank->preparation_begin_nanos = before;
385 :
386 0 : FD_TEST( ctx->timing_slot_of_bank[ bank_idx ]==fd_timing_slot_pool_idx_null( ctx->timing_slot_pool ) );
387 0 : if( FD_LIKELY( fd_timing_slot_pool_free( ctx->timing_slot_pool ) ) ) {
388 0 : ulong tslot = fd_timing_slot_pool_idx_acquire( ctx->timing_slot_pool );
389 0 : fd_timing_slot_pool_ele( ctx->timing_slot_pool, tslot )->cnt = 0UL;
390 0 : ctx->timing_slot_of_bank[ bank_idx ] = tslot;
391 0 : }
392 :
393 0 : fd_bank_t * parent_bank = fd_banks_bank_query( ctx->banks, parent_bank_idx );
394 0 : FD_CHECK_CRIT( parent_bank, "invariant violation: parent bank is NULL" );
395 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" );
396 :
397 : /* Clone the bank from the parent. We must special case the first
398 : slot that is executed as the snapshot does not provide a parent
399 : block id. */
400 :
401 0 : bank = fd_banks_clone_from_parent( ctx->banks, bank_idx );
402 0 : if( FD_UNLIKELY( !bank ) ) {
403 0 : FD_LOG_CRIT(( "invariant violation: bank is NULL for bank index %lu", bank_idx ));
404 0 : }
405 0 : bank->f.slot = slot;
406 0 : bank->txncache_fork_id = fd_txncache_attach_child ( ctx->txncache, parent_bank->txncache_fork_id );
407 0 : bank->progcache_fork_id = fd_progcache_attach_child( ctx->progcache, parent_bank->progcache_fork_id );
408 0 : bank->accdb_fork_id = fd_accdb_attach_child ( ctx->accdb, parent_bank->accdb_fork_id );
409 0 : bank->parent_accdb_fork_id = parent_bank->accdb_fork_id;
410 :
411 0 : ulong new_epoch = fd_slot_to_epoch( &parent_bank->f.epoch_schedule, slot, NULL );
412 0 : ulong root_epoch = fd_slot_to_epoch( &parent_bank->f.epoch_schedule, ctx->published_root_slot, NULL );
413 0 : if( FD_UNLIKELY( new_epoch>root_epoch+1UL ) ) {
414 0 : FD_LOG_CRIT(( "firedancer replay does not support replaying more than one epoch ahead of the current root" ));
415 0 : }
416 :
417 : /* Update required runtime state and handle potential boundary. */
418 :
419 0 : int is_epoch_boundary = 0;
420 0 : fd_runtime_block_execute_prepare( ctx->banks, bank, ctx->accdb, ctx->runtime_stack, ctx->capture_ctx, &is_epoch_boundary );
421 :
422 0 : ulong max_tick_height;
423 0 : if( FD_UNLIKELY( FD_RUNTIME_EXECUTE_SUCCESS!=fd_runtime_compute_max_tick_height( parent_bank->f.ticks_per_slot, slot, &max_tick_height ) ) ) {
424 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 ));
425 0 : }
426 0 : bank->f.max_tick_height = max_tick_height;
427 0 : if( FD_UNLIKELY( ctx->alpenglow ) ) {
428 : /* in alpenglow, we expect only one tick per block. Instead of
429 : adjusting max tick height, we match agave behavior by setting
430 : tick height to max tick height - 1. These fields must stay in
431 : line with agave behavior. */
432 0 : bank->f.tick_height = bank->f.max_tick_height - 1UL;
433 0 : bank->f.slot_params.hashes_per_tick = 1UL;
434 0 : }
435 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 );
436 :
437 0 : FD_LOG_DEBUG(( "replay_block_start: bank_idx=%lu slot=%lu parent_bank_idx=%lu", bank_idx, slot, parent_bank_idx ));
438 0 : }
439 :
440 : static void
441 0 : cost_tracker_snap( fd_bank_t * bank, fd_replay_slot_completed_t * slot_info ) {
442 0 : if( FD_LIKELY( bank->cost_tracker_pool_idx!=ULONG_MAX ) ) {
443 0 : fd_cost_tracker_t const * cost_tracker = fd_bank_cost_tracker_query( bank );
444 0 : if( FD_UNLIKELY( cost_tracker->block_cost_limit==0UL ) ) {
445 0 : memset( &slot_info->cost_tracker, -1 /* ULONG_MAX */, sizeof(slot_info->cost_tracker) );
446 0 : } else {
447 0 : slot_info->cost_tracker.block_cost = cost_tracker->block_cost;
448 0 : slot_info->cost_tracker.allocated_accounts_data_size = cost_tracker->allocated_accounts_data_size;
449 0 : slot_info->cost_tracker.block_cost_limit = cost_tracker->block_cost_limit;
450 0 : slot_info->cost_tracker.account_cost_limit = cost_tracker->account_cost_limit;
451 0 : }
452 0 : } else {
453 0 : memset( &slot_info->cost_tracker, -1 /* ULONG_MAX */, sizeof(slot_info->cost_tracker) );
454 0 : }
455 0 : slot_info->cost_tracker.pool_idx = bank->cost_tracker_pool_idx;
456 0 : }
457 :
458 : static int
459 0 : sched_dead_reason_to_event( int sched_reason ) {
460 0 : switch( sched_reason ) {
461 0 : case FD_SCHED_DEAD_REASON_UNPARSEABLE_CONTENT: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_UNPARSEABLE_CONTENT;
462 0 : case FD_SCHED_DEAD_REASON_SHORT_BLOCK: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_SHORT_BLOCK;
463 0 : case FD_SCHED_DEAD_REASON_TOO_MANY_TXNS: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TOO_MANY_TXNS;
464 0 : case FD_SCHED_DEAD_REASON_TOO_MANY_MICROBLOCKS: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TOO_MANY_MICROBLOCKS;
465 0 : case FD_SCHED_DEAD_REASON_DUPLICATE_ACCOUNT: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_DUPLICATE_ACCOUNT;
466 0 : case FD_SCHED_DEAD_REASON_TRAILING_ENTRY: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TRAILING_ENTRY;
467 0 : case FD_SCHED_DEAD_REASON_TOO_MANY_TICKS: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TOO_MANY_TICKS;
468 0 : case FD_SCHED_DEAD_REASON_TOO_FEW_TICKS: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TOO_FEW_TICKS;
469 0 : case FD_SCHED_DEAD_REASON_ZERO_MICROBLOCKS: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ZERO_MICROBLOCKS;
470 0 : case FD_SCHED_DEAD_REASON_WRONG_HASHES_PER_TICK: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_WRONG_HASHES_PER_TICK;
471 0 : case FD_SCHED_DEAD_REASON_INCONSISTENT_TICK_HASHES: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_INCONSISTENT_TICK_HASHES;
472 0 : case FD_SCHED_DEAD_REASON_TICK_HASHES_OVERFLOW: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TICK_HASHES_OVERFLOW;
473 0 : case FD_SCHED_DEAD_REASON_TICK_HASHES_OVERFLOW_INGEST: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TICK_HASHES_OVERFLOW_INGEST;
474 0 : case FD_SCHED_DEAD_REASON_ZERO_HASH_TICK: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ZERO_HASH_TICK;
475 0 : case FD_SCHED_DEAD_REASON_ZERO_HASH_TICK_INGEST: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ZERO_HASH_TICK_INGEST;
476 0 : case FD_SCHED_DEAD_REASON_TICK_HASH_MISMATCH: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TICK_HASH_MISMATCH;
477 0 : case FD_SCHED_DEAD_REASON_ENTRY_HASH_MISMATCH: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ENTRY_HASH_MISMATCH;
478 0 : case FD_SCHED_DEAD_REASON_ENTRY_HASH_MISMATCH_INGEST: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ENTRY_HASH_MISMATCH_INGEST;
479 0 : case FD_SCHED_DEAD_REASON_DEAD_ANCESTOR: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_PARENT_DEAD;
480 0 : case FD_SCHED_DEAD_REASON_BAD_BLOCK_MARKER: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_BAD_BLOCK_MARKER;
481 0 : case FD_SCHED_DEAD_REASON_ALPENGLOW_HASH_CNT: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ALPENGLOW_HASH_CNT;
482 0 : case FD_SCHED_DEAD_REASON_MISSING_PARENT_MARKER: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_MISSING_PARENT_MARKER;
483 0 : case FD_SCHED_DEAD_REASON_MULTIPLE_BLOCK_HEADERS: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_MULTIPLE_BLOCK_HEADERS;
484 0 : case FD_SCHED_DEAD_REASON_GENESIS_CERT_OUT_OF_ORDER: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_GENESIS_CERT_OUT_OF_ORDER;
485 0 : case FD_SCHED_DEAD_REASON_MULTIPLE_BLOCK_FOOTERS: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_MULTIPLE_BLOCK_FOOTERS;
486 0 : case FD_SCHED_DEAD_REASON_ENTRY_AFTER_BLOCK_FOOTER: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ENTRY_AFTER_BLOCK_FOOTER;
487 0 : case FD_SCHED_DEAD_REASON_INVALID_ALPENTICK_POSITION: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_INVALID_ALPENTICK_POSITION;
488 0 : case FD_SCHED_DEAD_REASON_MISSING_BLOCK_FOOTER: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_MISSING_BLOCK_FOOTER;
489 0 : case FD_SCHED_DEAD_REASON_SPURIOUS_UPDATE_PARENT: return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_SPURIOUS_UPDATE_PARENT;
490 0 : default: FD_LOG_CRIT(( "unmapped scheduler dead reason %d", sched_reason ));
491 0 : }
492 0 : }
493 :
494 : static int
495 0 : sched_block_dead_reason_to_event( fd_replay_tile_t * ctx, ulong bank_idx ) {
496 0 : if( FD_UNLIKELY( fd_sched_block_is_discarded( ctx->sched, bank_idx ) ) ) return FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD;
497 0 : return sched_dead_reason_to_event( fd_sched_get_dead_reason( ctx->sched, bank_idx ) );
498 0 : }
499 :
500 : static void
501 : block_completed_event_fill_reception( fd_replay_tile_t * ctx,
502 : fd_event_block_completed_t * ev,
503 : fd_hash_t const * mr,
504 0 : ulong slot ) {
505 0 : ev->lowest_verified_fec_index = UINT_MAX;
506 0 : ev->last_completed_fec_set_index = UINT_MAX;
507 :
508 0 : if( !ctx->reasm ) return;
509 :
510 0 : fd_reasm_fec_t * chain_tip = fd_reasm_query( ctx->reasm, mr );
511 0 : if( FD_LIKELY( chain_tip ) ) {
512 0 : ulong n = 0UL;
513 0 : fd_reasm_fec_t * f = chain_tip;
514 0 : for( ; f && f->slot==slot; f = fd_reasm_parent( ctx->reasm, f ) ) {
515 0 : n++;
516 0 : if( f->eqvoc ) ev->equivocation_detected_shred = 1;
517 0 : }
518 0 : ev->fec_set_count = n;
519 :
520 : /* The walk's terminating FEC is the parent block's final one: its
521 : merkle root is the parent block id. Recover parent_slot from it
522 : too, for rows whose bank never learned it. */
523 0 : if( f ) {
524 0 : fd_memcpy( ev->parent_block_id, f->key.uc, sizeof(ev->parent_block_id) );
525 0 : if( !ev->parent_slot ) ev->parent_slot = f->slot;
526 0 : }
527 :
528 0 : fd_reception_stats_t * stats = &ctx->reception_stats[ slot % ctx->reception_stats_cnt ];
529 : /* If there's a mismatch in the slot's stats, we just ignore it and
530 : return. This can only happen in the case where there is a large
531 : jump in the slot number and it exactly matches the expected
532 : slot's modulo with max_live_slots. */
533 0 : if( FD_UNLIKELY( stats->slot!=slot ) ) return;
534 0 : fd_fec_complete_metrics_t const * m = &stats->metrics;
535 0 : ev->last_completed_fec_set_index = stats->fec_set_idx;
536 0 : ev->turbine_shred_count = m->blk_turbine_cnt;
537 0 : ev->repair_shred_count = m->blk_repair_cnt;
538 0 : ev->recovered_shred_count = m->blk_recovered_cnt;
539 0 : ev->data_shred_count = m->blk_data_cnt;
540 0 : ev->parity_shred_count = m->blk_parity_cnt;
541 0 : ev->chain_confirmed = !!m->blk_chain_confirmed;
542 0 : ev->slot_complete_flag = !!m->blk_slot_complete;
543 0 : ev->lowest_verified_fec_index = m->blk_lowest_verified_fec;
544 :
545 0 : ev->repair_request_window_count = m->blk_req_window_cnt;
546 0 : ev->repair_request_highest_window_count = m->blk_req_highest_cnt;
547 0 : ev->repair_request_orphan_count = m->blk_req_orphan_cnt;
548 0 : ev->repair_responses_received = m->blk_repair_responses;
549 0 : ev->repair_requests_retransmitted = m->blk_req_retransmit_cnt;
550 0 : ev->repair_failed_chain_verify = !!m->blk_chain_verify_failed;
551 0 : ev->first_shred_received_time = m->blk_first_shred_ts_nanos;
552 0 : ev->last_shred_received_time = m->blk_last_shred_ts_nanos;
553 0 : ev->first_repair_request_time = m->blk_first_req_ts_nanos;
554 0 : ev->last_repair_received_time = m->blk_last_repair_resp_ts_nanos;
555 0 : }
556 0 : }
557 :
558 : static void
559 : block_completed_event_fill_bank( fd_replay_tile_t * ctx,
560 : fd_event_block_completed_t * ev,
561 0 : fd_bank_t * bank ) {
562 0 : fd_bank_t * parent_bank = fd_banks_get_parent( ctx->banks, bank );
563 :
564 0 : int prepared = bank->preparation_begin_nanos!=0L;
565 0 : ulong slot = prepared ? bank->f.slot : ctx->block_id_arr[ bank->idx ].slot;
566 :
567 0 : ev->bank_seq = bank->bank_seq;
568 0 : ev->slot = slot;
569 0 : ev->parent_slot = prepared ? bank->f.parent_slot
570 0 : : bank->parent_idx!=ULONG_MAX ? ctx->block_id_arr[ bank->parent_idx ].slot
571 0 : : 0UL;
572 0 : if( FD_LIKELY( prepared ) ) ev->epoch = bank->f.epoch;
573 0 : else if( FD_LIKELY( ctx->notified_root_bank ) ) ev->epoch = fd_slot_to_epoch( &ctx->notified_root_bank->f.epoch_schedule, slot, NULL );
574 0 : fd_block_id_ele_t const * ele = &ctx->block_id_arr[ bank->idx ];
575 0 : fd_memcpy( ev->block_id, ctx->alpenglow && ele->block_id_seen ? ele->dmr.uc : ele->latest_mr.uc, sizeof(ev->block_id) );
576 0 : if( FD_LIKELY( bank->block_completed_nanos ) ) fd_memcpy( ev->bank_hash, bank->f.bank_hash.uc, sizeof(ev->bank_hash) );
577 :
578 0 : ev->first_fec_set_received_time = bank->is_leader ? 0UL : (ulong)bank->first_fec_set_received_nanos;
579 0 : ev->preparation_begin_time = (ulong)bank->preparation_begin_nanos;
580 0 : ev->first_transaction_scheduled_time = (ulong)bank->first_transaction_scheduled_nanos;
581 0 : ev->last_transaction_finished_time = (ulong)bank->last_transaction_finished_nanos;
582 0 : ev->block_completed_time = (ulong)bank->block_completed_nanos;
583 0 : ev->parent_block_completed_time = parent_bank ? (ulong)parent_bank->block_completed_nanos : 0UL;
584 :
585 0 : if( FD_UNLIKELY( bank->cost_tracker_pool_idx!=ULONG_MAX ) ) {
586 0 : fd_cost_tracker_t const * ct = fd_bank_cost_tracker_query( bank );
587 0 : ev->cost_tracker_block_cost = ct->block_cost;
588 0 : ev->cost_tracker_allocated_accounts_data_size = ct->allocated_accounts_data_size;
589 0 : ev->cost_tracker_block_cost_limit = ct->block_cost_limit;
590 0 : ev->cost_tracker_account_cost_limit = ct->account_cost_limit;
591 0 : }
592 :
593 0 : ev->bank_idx = bank->idx;
594 0 : if( FD_LIKELY( prepared ) ) {
595 0 : ev->txncache_fork_id = bank->txncache_fork_id.val;
596 0 : ev->progcache_fork_id = bank->progcache_fork_id;
597 0 : ev->accdb_fork_id = bank->accdb_fork_id.val;
598 0 : ev->vote_stakes_fork_id = bank->vote_stakes_fork_id;
599 0 : ev->collector_overrides_fork_id = bank->collector_overrides_fork_id;
600 0 : ev->stake_rewards_fork_id = bank->stake_rewards_fork_id;
601 0 : ev->epoch_credits_fork_id = bank->epoch_credits_fork_id;
602 0 : ev->stake_delegations_fork_id = bank->stake_delegations_fork_id;
603 0 : ev->cost_tracker_pool_idx = bank->cost_tracker_pool_idx;
604 0 : }
605 :
606 0 : block_completed_event_fill_reception( ctx, ev, &ctx->block_id_arr[ bank->idx ].latest_mr, slot );
607 0 : }
608 :
609 : static int
610 0 : pack_end_reason_to_event( int reason ) {
611 0 : switch( reason ) {
612 0 : case FD_PACK_END_SLOT_REASON_TIME: return FD_EVENT_BLOCK_COMPLETED_PACK_END_REASON_TIME;
613 0 : case FD_PACK_END_SLOT_REASON_MICROBLOCK: return FD_EVENT_BLOCK_COMPLETED_PACK_END_REASON_MICROBLOCK_LIMIT;
614 0 : case FD_PACK_END_SLOT_REASON_ABANDONED: return FD_EVENT_BLOCK_COMPLETED_PACK_END_REASON_ABANDONED;
615 0 : default: FD_LOG_CRIT(( "unmapped pack end reason %d", reason ));
616 0 : }
617 0 : }
618 :
619 : static void
620 : block_completed_event_fill_leader( fd_replay_tile_t * ctx,
621 : fd_event_block_completed_t * ev,
622 0 : fd_bank_t * bank ) {
623 0 : FD_TEST( ctx->leader_stats.slot==bank->f.slot );
624 :
625 0 : ev->became_leader_time = (ulong)ctx->leader_stats.became_leader_nanos;
626 0 : ev->leader_slot_start_time = (ulong)ctx->leader_stats.leader_slot_start_nanos;
627 :
628 0 : ev->first_fec_set_received_time = (ulong)ctx->leader_stats.first_fec_returned_nanos;
629 0 : ev->recovered_shred_count = 0UL;
630 :
631 0 : ev->microblock_count = ctx->leader_stats.microblock_count;
632 0 : ev->pack_block_cost = ctx->leader_stats.pack_block_cost;
633 0 : ev->pack_vote_cost = ctx->leader_stats.pack_vote_cost;
634 0 : ev->pack_data_bytes = ctx->leader_stats.pack_data_bytes;
635 0 : ev->bundle_txn_count = ctx->leader_stats.bundle_txn_count;
636 0 : ev->pack_start_time = (ulong)ctx->leader_stats.pack_start_nanos;
637 0 : ev->pack_end_time = (ulong)ctx->leader_stats.pack_end_nanos;
638 0 : ev->pack_end_reason = pack_end_reason_to_event( ctx->leader_stats.pack_end_reason );
639 0 : }
640 :
641 : static void
642 : block_completed_event_fill_leader_txn_timing( fd_replay_tile_t * ctx,
643 : fd_event_block_completed_t * ev,
644 0 : fd_bank_t * bank ) {
645 0 : ulong tidx = ctx->leader_stats.timing_table_idx;
646 0 : if( FD_UNLIKELY( !ctx->leader_txn_timing || tidx>=FD_LEADER_TXN_TIMING_TABLE_CNT ) ) return;
647 0 : fd_leader_txn_timing_table_t const * table = fd_leader_txn_timing_table_const( ctx->leader_txn_timing, tidx, ctx->max_txn_per_slot );
648 0 : if( FD_UNLIKELY( table->slot!=bank->f.slot ) ) return;
649 :
650 0 : ulong cnt = fd_ulong_min( table->cnt, FD_EVENT_BLOCK_COMPLETED_TXN_TIMING_MAX );
651 0 : long first_dispatch = LONG_MAX;
652 0 : for( ulong i=0UL; i<cnt; i++ ) {
653 0 : fd_leader_txn_timing_rec_t const * rec = &table->rec[ i ];
654 0 : first_dispatch = fd_long_min( first_dispatch, rec->dispatched_ticks );
655 0 : ev->txn_timing[ i ] = (fd_event_block_completed_txn_timing_t){
656 0 : .received_time = (ulong)rec->received_ns,
657 0 : .dispatched_time = rec->dispatched_ticks==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, rec->dispatched_ticks ),
658 0 : .replayed_time = rec->replayed_ticks ==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, rec->replayed_ticks ),
659 0 : .poh_mixed_time = rec->poh_mixed_ticks ==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, rec->poh_mixed_ticks ),
660 0 : };
661 0 : }
662 0 : ev->txn_timing_cnt = cnt;
663 :
664 0 : ev->first_transaction_scheduled_time = first_dispatch==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, first_dispatch );
665 0 : }
666 :
667 : static void
668 : report_block_completed( fd_replay_tile_t * ctx,
669 : fd_bank_t * bank,
670 : int is_leader,
671 0 : fd_replay_slot_completed_t const * slot_info ) {
672 0 : if( FD_LIKELY( !fd_event_tl ) ) return;
673 :
674 0 : fd_event_block_completed_t * ev = ctx->block_completed_event;
675 0 : memset( ev, 0, FD_EVENT_BLOCK_COMPLETED_PREFIX_SZ );
676 :
677 0 : block_completed_event_fill_bank( ctx, ev, bank );
678 :
679 0 : if( FD_LIKELY( slot_info->cost_tracker.block_cost_limit!=ULONG_MAX ) ) {
680 0 : ev->cost_tracker_block_cost = slot_info->cost_tracker.block_cost;
681 0 : ev->cost_tracker_allocated_accounts_data_size = slot_info->cost_tracker.allocated_accounts_data_size;
682 0 : ev->cost_tracker_block_cost_limit = slot_info->cost_tracker.block_cost_limit;
683 0 : ev->cost_tracker_account_cost_limit = slot_info->cost_tracker.account_cost_limit;
684 0 : }
685 0 : ev->cost_tracker_pool_idx = slot_info->cost_tracker.pool_idx;
686 :
687 0 : ev->root_slot = ctx->consensus_root_slot;
688 0 : ev->storage_slot = ctx->published_root_slot;
689 0 : ev->caught_up = !!ctx->caught_up;
690 0 : ev->turbine_slot = ctx->catch_up_max_fec_slot==ULONG_MAX ? 0UL : ctx->catch_up_max_fec_slot;
691 0 : ev->fork_width = ctx->banks->curr_fork_width;
692 0 : ev->snapshot_in_progress = !!ctx->snapmk.active;
693 0 : ev->live_bank_count = fd_banks_pool_used_cnt( ctx->banks );
694 0 : ev->is_leader = is_leader;
695 :
696 0 : ev->pack_end_reason = FD_EVENT_BLOCK_COMPLETED_PACK_END_REASON_NOT_LEADER;
697 0 : if( FD_UNLIKELY( is_leader ) ) block_completed_event_fill_leader( ctx, ev, bank );
698 :
699 0 : ev->dead = 0;
700 0 : ev->dead_reason = FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD;
701 0 : ev->dead_time = 0UL;
702 0 : ev->abandoned = 0;
703 0 : ev->abandoned_reason = FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED;
704 0 : ev->abandoned_time = 0UL;
705 :
706 0 : if( FD_UNLIKELY( is_leader ) ) {
707 0 : block_completed_event_fill_leader_txn_timing( ctx, ev, bank );
708 0 : } else {
709 0 : ulong tslot = ctx->timing_slot_of_bank[ bank->idx ];
710 0 : if( FD_LIKELY( tslot!=fd_timing_slot_pool_idx_null( ctx->timing_slot_pool ) ) ) {
711 0 : fd_replay_txn_timing_slot_t const * slot = fd_timing_slot_pool_ele( ctx->timing_slot_pool, tslot );
712 0 : fd_replay_txn_timing_t const * rec = ctx->timing_rec + tslot*ctx->max_txn_per_slot;
713 0 : ulong cnt = fd_ulong_min( slot->cnt, FD_EVENT_BLOCK_COMPLETED_TXN_TIMING_MAX );
714 0 : for( ulong i=0UL; i<cnt; i++ ) {
715 0 : fd_replay_txn_timing_t const * t = &rec[ i ];
716 0 : ev->txn_timing[ i ] = (fd_event_block_completed_txn_timing_t){
717 0 : .received_time = (ulong)t->received_ns, /* already wallclock */
718 0 : .parsed_time = t->parsed_ticks ==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, t->parsed_ticks ),
719 0 : .sigverify_dispatched_time = t->sigverify_disp_ticks==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, t->sigverify_disp_ticks ),
720 0 : .sigverify_done_time = t->sigverify_done_ticks==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, t->sigverify_done_ticks ),
721 0 : .dispatched_time = t->exec_disp_ticks ==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, t->exec_disp_ticks ),
722 0 : .replayed_time = t->exec_done_ticks ==LONG_MAX ? 0UL : (ulong)fd_clock_epoch_y( ctx->clock->epoch, t->exec_done_ticks ),
723 0 : };
724 0 : }
725 0 : ev->txn_timing_cnt = cnt;
726 0 : }
727 0 : }
728 :
729 0 : if( FD_UNLIKELY( !ev->first_transaction_scheduled_time ) ) ev->first_transaction_scheduled_time = ev->last_transaction_finished_time;
730 :
731 0 : fd_event_report_block_completed( ev );
732 0 : }
733 :
734 : static void
735 : publish_slot_completed( fd_replay_tile_t * ctx,
736 : fd_stem_context_t * stem,
737 : fd_bank_t * bank,
738 : int is_initial,
739 : int is_leader,
740 : ulong execution_fees_pre_settle,
741 0 : ulong priority_fees_pre_settle ) {
742 :
743 0 : ulong slot = bank->f.slot;
744 :
745 0 : if( FD_UNLIKELY( ctx->alpenglow ) ) ctx->reset_slot = fd_ulong_max( ctx->reset_slot, slot );
746 :
747 0 : if( FD_UNLIKELY( is_initial ) ) bank->block_completed_nanos = fd_clock_tile_now( ctx->clock );
748 :
749 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ bank->idx ];
750 :
751 : /* HACKY: hacky way of checking if we should send a null parent block
752 : id */
753 0 : fd_hash_t block_id = ctx->alpenglow ? block_id_ele->dmr : block_id_ele->latest_mr;
754 0 : fd_hash_t parent_block_id = {0};
755 0 : if( FD_LIKELY( !is_initial ) ) {
756 0 : fd_block_id_ele_t const * parent_ele = &ctx->block_id_arr[ bank->parent_idx ];
757 0 : parent_block_id = ctx->alpenglow ? parent_ele->dmr : parent_ele->latest_mr;
758 0 : }
759 :
760 0 : fd_hash_t const * bank_hash = &bank->f.bank_hash;
761 0 : fd_hash_t const * block_hash = fd_blockhashes_peek_last_hash( &bank->f.block_hash_queue );
762 0 : FD_TEST( block_hash );
763 :
764 0 : if( FD_LIKELY( !is_initial ) ) fd_txncache_finalize_fork( ctx->txncache, bank->txncache_fork_id, 0UL, block_hash->uc );
765 :
766 0 : fd_epoch_schedule_t const * epoch_schedule = &bank->f.epoch_schedule;
767 0 : ulong slot_idx;
768 0 : ulong epoch = fd_slot_to_epoch( epoch_schedule, slot, &slot_idx );
769 :
770 0 : ctx->metrics.slots_total++;
771 0 : ctx->metrics.transactions_total = bank->f.parent_txn_count + bank->f.txn_count;
772 :
773 : /* Caught up once replay completes a slot within a few slots of the
774 : cluster tip. Require the tip to have advanced a few times first so
775 : a brief view of the tip right after boot does not count. */
776 0 : if( FD_UNLIKELY( !ctx->caught_up && !is_initial &&
777 0 : ctx->catch_up_tip_advance_cnt>=12UL &&
778 0 : ctx->catch_up_max_fec_slot<slot+3UL ) ) {
779 0 : ctx->caught_up = 1;
780 0 : double boot_secs = (double)(fd_log_wallclock()-ctx->boot_timestamp_nanos)/1e9;
781 0 : FD_LOG_NOTICE(( "caught up to cluster at slot %s%lu%s %s(%.1f seconds since boot)%s",
782 0 : fd_log_style_bold(), slot, fd_log_style_normal(),
783 0 : fd_log_style_dim(), boot_secs, fd_log_style_normal() ));
784 0 : }
785 :
786 0 : fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
787 0 : slot_info->slot = slot;
788 0 : slot_info->root_slot = ctx->consensus_root_slot;
789 0 : slot_info->storage_slot = ctx->published_root_slot;
790 0 : slot_info->epoch = epoch;
791 0 : slot_info->slot_in_epoch = slot_idx;
792 0 : slot_info->slots_per_epoch = fd_epoch_slot_cnt( epoch_schedule, epoch );
793 0 : slot_info->block_height = bank->f.block_height;
794 0 : slot_info->parent_slot = bank->f.parent_slot;
795 0 : slot_info->block_id = block_id;
796 0 : slot_info->parent_block_id = parent_block_id;
797 0 : slot_info->bank_hash = *bank_hash;
798 0 : slot_info->block_hash = *block_hash;
799 0 : slot_info->transaction_count = bank->f.parent_txn_count + bank->f.txn_count;
800 :
801 0 : fd_inflation_t inflation = bank->f.inflation;
802 0 : slot_info->inflation.foundation = inflation.foundation;
803 0 : slot_info->inflation.foundation_term = inflation.foundation_term;
804 0 : slot_info->inflation.terminal = inflation.terminal;
805 0 : slot_info->inflation.initial = inflation.initial;
806 0 : slot_info->inflation.taper = inflation.taper;
807 :
808 0 : fd_rent_t rent = bank->f.rent;
809 0 : slot_info->rent.burn_percent = rent.burn_percent;
810 0 : slot_info->rent.lamports_per_uint8_year = rent.lamports_per_uint8_year;
811 0 : slot_info->rent.exemption_threshold = rent.exemption_threshold;
812 :
813 0 : slot_info->first_fec_set_received_nanos = bank->first_fec_set_received_nanos;
814 0 : slot_info->preparation_begin_nanos = bank->preparation_begin_nanos;
815 0 : slot_info->first_transaction_scheduled_nanos = bank->first_transaction_scheduled_nanos;
816 0 : slot_info->last_transaction_finished_nanos = bank->last_transaction_finished_nanos;
817 0 : slot_info->completion_time_nanos = fd_clock_tile_now( ctx->clock );
818 0 : if( !slot_info->first_transaction_scheduled_nanos ) { /* edge case: empty slot */
819 0 : slot_info->first_transaction_scheduled_nanos = slot_info->last_transaction_finished_nanos;
820 0 : }
821 :
822 : /* refcnt should be incremented by 1 for each consumer that uses
823 : `bank_idx`. Each consumer should decrement the bank's refcnt once
824 : they are done using the bank. */
825 0 : if( FD_LIKELY( !ctx->alpenglow ) ) bank->refcnt++; /* tower_tile */
826 0 : if( FD_LIKELY( ctx->rpc_enabled ) ) bank->refcnt++; /* rpc tile */
827 0 : slot_info->bank_idx = bank->idx;
828 0 : slot_info->bank_seq = bank->bank_seq;
829 0 : slot_info->accdb_fork_id = bank->accdb_fork_id;
830 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for tower, rpc", bank->idx, slot, bank->refcnt ));
831 :
832 0 : fd_bank_t * parent_bank = fd_banks_get_parent( ctx->banks, bank );
833 0 : slot_info->parent_bank_idx = parent_bank ? parent_bank->idx : ULONG_MAX;
834 0 : slot_info->parent_bank_seq = parent_bank ? parent_bank->bank_seq : ULONG_MAX;
835 0 : if( FD_LIKELY( parent_bank ) ) {
836 0 : ulong total_txn_cnt = bank->f.txn_count;
837 0 : ulong nonvote_txn_cnt = bank->f.nonvote_txn_count;
838 0 : ulong failed_txn_cnt = bank->f.failed_txn_count;
839 0 : ulong nonvote_failed_txn_cnt = bank->f.nonvote_failed_txn_count;
840 :
841 0 : slot_info->nonvote_success = nonvote_txn_cnt - nonvote_failed_txn_cnt;
842 0 : slot_info->nonvote_failed = nonvote_failed_txn_cnt;
843 0 : slot_info->vote_failed = failed_txn_cnt - nonvote_failed_txn_cnt;
844 0 : slot_info->vote_success = total_txn_cnt - nonvote_txn_cnt - slot_info->vote_failed;
845 0 : } else {
846 0 : slot_info->vote_failed = ULONG_MAX;
847 0 : slot_info->vote_success = ULONG_MAX;
848 0 : slot_info->nonvote_success = ULONG_MAX;
849 0 : slot_info->nonvote_failed = ULONG_MAX;
850 0 : }
851 :
852 0 : slot_info->is_leader = is_leader;
853 0 : slot_info->transaction_fee = execution_fees_pre_settle;
854 0 : slot_info->transaction_fee -= (slot_info->transaction_fee>>1); /* burn */
855 0 : slot_info->priority_fee = priority_fees_pre_settle;
856 0 : slot_info->tips = bank->f.tips;
857 0 : slot_info->shred_cnt = bank->f.shred_cnt;
858 :
859 0 : slot_info->voted = replay_reward_cert_voted( ctx, bank, &slot_info->voted_rank );
860 :
861 0 : slot_info->vote_balance = ULONG_MAX;
862 0 : slot_info->vote_commission = USHORT_MAX;
863 0 : if( FD_UNLIKELY( ctx->alpenglow && ctx->has_vote_account && slot%512UL==0UL ) ) {
864 0 : fd_acc_t acc = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, ctx->vote_account->uc );
865 0 : if( FD_LIKELY( acc.lamports ) ) {
866 0 : slot_info->vote_balance = acc.lamports;
867 :
868 0 : ushort bps;
869 0 : if( FD_LIKELY( !fd_vote_account_commission_bps( acc.data, acc.data_len, FD_FEATURE_ACTIVE_BANK( bank, commission_rate_in_basis_points ), &bps ) ) ) slot_info->vote_commission = bps;
870 0 : }
871 0 : fd_accdb_unread_one( ctx->accdb, &acc );
872 0 : }
873 :
874 0 : FD_BASE58_ENCODE_32_BYTES( slot_info->block_id.uc, block_id_b58 );
875 0 : FD_BASE58_ENCODE_32_BYTES( bank->f.bank_hash.uc, bank_hash_b58 );
876 0 : FD_BASE58_ENCODE_32_BYTES( bank->f.poh.uc, poh_hash_b58 );
877 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) "
878 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]",
879 0 : bank->f.slot, block_id_b58,
880 0 : bank_hash_b58,
881 0 : poh_hash_b58,
882 0 : bank->f.txn_count,
883 0 : bank->f.txn_count - bank->f.nonvote_txn_count,
884 0 : bank->f.shred_cnt,
885 0 : bank->f.total_compute_units_used,
886 0 : execution_fees_pre_settle + priority_fees_pre_settle,
887 0 : !!parent_bank ? parent_bank->block_completed_nanos - bank->first_fec_set_received_nanos : LONG_MAX,
888 0 : bank->preparation_begin_nanos - bank->first_fec_set_received_nanos,
889 0 : bank->first_transaction_scheduled_nanos - bank->preparation_begin_nanos,
890 0 : bank->last_transaction_finished_nanos - bank->first_transaction_scheduled_nanos,
891 0 : bank->block_completed_nanos - bank->last_transaction_finished_nanos ));
892 :
893 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() ) );
894 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 );
895 :
896 : /* Skip the telemetry event for the initial boot block (snapshot /
897 : genesis): it was not replayed. */
898 0 : if( FD_LIKELY( !is_initial ) ) report_block_completed( ctx, bank, is_leader, slot_info );
899 0 : timing_slot_release( ctx, bank->idx );
900 0 : }
901 :
902 : static void
903 : report_block_incomplete( fd_replay_tile_t * ctx,
904 : ulong slot,
905 : fd_hash_t const * block_id,
906 : fd_bank_t * bank,
907 : int dead_reason,
908 0 : int abandoned_reason ) {
909 0 : if( FD_LIKELY( !fd_event_tl ) ) return;
910 :
911 0 : fd_event_block_completed_t * ev = ctx->block_completed_event;
912 0 : memset( ev, 0, FD_EVENT_BLOCK_COMPLETED_PREFIX_SZ );
913 0 : if( FD_LIKELY( bank ) ) {
914 0 : block_completed_event_fill_bank( ctx, ev, bank );
915 0 : ev->is_leader = !!bank->is_leader;
916 0 : } else {
917 0 : ev->slot = slot;
918 0 : if( FD_LIKELY( ctx->notified_root_bank ) ) ev->epoch = fd_slot_to_epoch( &ctx->notified_root_bank->f.epoch_schedule, slot, NULL );
919 0 : fd_memcpy( ev->block_id, block_id->uc, sizeof(ev->block_id) );
920 0 : block_completed_event_fill_reception( ctx, ev, block_id, slot );
921 0 : }
922 0 : ev->root_slot = ctx->consensus_root_slot;
923 0 : ev->storage_slot = ctx->published_root_slot;
924 0 : ev->caught_up = !!ctx->caught_up;
925 0 : ev->turbine_slot = ctx->catch_up_max_fec_slot==ULONG_MAX ? 0UL : ctx->catch_up_max_fec_slot;
926 0 : ev->fork_width = ctx->banks->curr_fork_width;
927 0 : ev->snapshot_in_progress = !!ctx->snapmk.active;
928 0 : ev->live_bank_count = fd_banks_pool_used_cnt( ctx->banks );
929 :
930 0 : int dead = dead_reason !=FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD;
931 0 : int abandoned = abandoned_reason!=FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED;
932 0 : long now = fd_clock_tile_now( ctx->clock );
933 :
934 0 : ev->dead = dead;
935 0 : ev->dead_reason = dead_reason;
936 0 : ev->dead_time = dead ? (ulong)now : 0UL;
937 0 : ev->abandoned = abandoned;
938 0 : ev->abandoned_reason = abandoned_reason;
939 0 : ev->abandoned_time = abandoned ? (ulong)now : 0UL;
940 :
941 0 : ev->pack_end_reason = FD_EVENT_BLOCK_COMPLETED_PACK_END_REASON_NOT_LEADER;
942 0 : if( FD_UNLIKELY( bank && bank->is_leader ) ) {
943 0 : block_completed_event_fill_leader( ctx, ev, bank );
944 0 : block_completed_event_fill_leader_txn_timing( ctx, ev, bank );
945 0 : }
946 :
947 0 : fd_event_report_block_completed( ev );
948 0 : }
949 :
950 : static void
951 : publish_slot_dead( fd_replay_tile_t * ctx,
952 : fd_stem_context_t * stem,
953 : ulong slot,
954 0 : fd_hash_t const * block_id ) {
955 0 : fd_replay_slot_dead_t * slot_dead = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
956 0 : slot_dead->slot = slot;
957 0 : slot_dead->block_id = *block_id;
958 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() ) );
959 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 );
960 0 : }
961 :
962 : static void
963 : publish_txn_executed( fd_replay_tile_t * ctx,
964 : fd_stem_context_t * stem,
965 : ulong bank_idx,
966 0 : ulong txn_idx ) {
967 0 : fd_sched_txn_info_t * txn_info = fd_sched_get_txn_info( ctx->sched, txn_idx );
968 :
969 0 : FD_TEST( txn_info->index_in_slot<ctx->max_txn_per_slot );
970 0 : ulong tslot = ctx->timing_slot_of_bank[ bank_idx ];
971 0 : if( FD_LIKELY( tslot!=fd_timing_slot_pool_idx_null( ctx->timing_slot_pool ) ) ) {
972 0 : fd_replay_txn_timing_slot_t * slot = fd_timing_slot_pool_ele( ctx->timing_slot_pool, tslot );
973 0 : fd_replay_txn_timing_t * t = ctx->timing_rec + tslot*ctx->max_txn_per_slot + txn_info->index_in_slot;
974 0 : t->received_ns = txn_info->received_ns;
975 0 : t->parsed_ticks = txn_info->tick_parsed;
976 0 : t->sigverify_disp_ticks = txn_info->tick_sigverify_disp;
977 0 : t->sigverify_done_ticks = txn_info->tick_sigverify_done;
978 0 : t->exec_disp_ticks = txn_info->tick_exec_disp;
979 0 : t->exec_done_ticks = txn_info->tick_exec_done;
980 0 : slot->cnt = fd_ulong_max( slot->cnt, txn_info->index_in_slot+1UL );
981 0 : }
982 :
983 0 : fd_replay_txn_executed_t * txn_executed = fd_type_pun( fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk ) );
984 0 : *txn_executed->txn = *fd_sched_get_txn( ctx->sched, txn_idx );
985 0 : txn_executed->txn_err = txn_info->txn_err;
986 0 : txn_executed->is_committable = !!(txn_info->flags&FD_SCHED_TXN_IS_COMMITTABLE);
987 0 : txn_executed->is_fees_only = !!(txn_info->flags&FD_SCHED_TXN_IS_FEES_ONLY);
988 0 : txn_executed->is_noop = !!(txn_info->flags&FD_SCHED_TXN_IS_NOOP);
989 0 : txn_executed->is_simple_vote = txn_info->is_simple_vote;
990 0 : txn_executed->tick_parsed = txn_info->tick_parsed;
991 0 : txn_executed->tick_sigverify_disp = txn_info->tick_sigverify_disp;
992 0 : txn_executed->tick_sigverify_done = txn_info->tick_sigverify_done;
993 0 : txn_executed->tick_exec_disp = txn_info->tick_exec_disp;
994 0 : txn_executed->tick_exec_done = txn_info->tick_exec_done;
995 :
996 0 : txn_executed->tick_load_start = txn_info->tick_load_start;
997 0 : txn_executed->tick_check_start = txn_info->tick_check_start;
998 0 : txn_executed->tick_exec_start = txn_info->tick_exec_start;
999 0 : txn_executed->tick_commit_start = txn_info->tick_commit_start;
1000 0 : txn_executed->tick_commit_end = txn_info->tick_commit_end;
1001 :
1002 0 : txn_executed->slot = txn_info->slot;
1003 0 : txn_executed->bank_seq = txn_info->bank_seq;
1004 0 : txn_executed->index_in_slot = txn_info->index_in_slot;
1005 0 : txn_executed->exec_tile_idx = txn_info->exec_tile_idx;
1006 0 : txn_executed->sigverify_exec_tile_idx = txn_info->sigverify_exec_tile_idx;
1007 0 : txn_executed->compute_units_consumed = txn_info->compute_units_consumed;
1008 0 : txn_executed->max_compute_units = txn_info->max_compute_units;
1009 0 : txn_executed->transaction_fee = txn_info->transaction_fee;
1010 0 : txn_executed->priority_fee = txn_info->priority_fee;
1011 0 : txn_executed->tips = txn_info->tips;
1012 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() ) );
1013 0 : ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(*txn_executed), ctx->replay_out->chunk0, ctx->replay_out->wmark );
1014 0 : }
1015 :
1016 : static inline ushort
1017 0 : shred_version( fd_replay_tile_t * ctx ) {
1018 : /* for backtest */
1019 0 : return ctx->shred_version ? ctx->shred_version : ctx->expected_shred_version;
1020 0 : }
1021 :
1022 : /* Emit the runtime_block event for a finalized bank (replayed or
1023 : leader-produced). The fee arguments are the bank's values snapshotted
1024 : before fd_runtime_block_execute_finalize, which settles (zeroes) the
1025 : fee fields and applies the tip commission. */
1026 :
1027 : static void
1028 : replay_runtime_block_emit( fd_replay_tile_t * ctx,
1029 : fd_bank_t * bank,
1030 : ulong execution_fees,
1031 : ulong priority_fees,
1032 0 : ulong tips ) {
1033 0 : fd_block_id_ele_t const * ele = &ctx->block_id_arr[ bank->idx ];
1034 0 : fd_hash_t const * block_id = fd_ptr_if( ctx->alpenglow, &ele->dmr, &ele->latest_mr );
1035 0 : fd_hash_t parent_block_id = {0};
1036 0 : if( FD_LIKELY( bank->parent_idx!=ULONG_MAX ) ) {
1037 0 : fd_block_id_ele_t const * parent_ele = &ctx->block_id_arr[ bank->parent_idx ];
1038 0 : parent_block_id = fd_ptr_if( ctx->alpenglow, &parent_ele->dmr, &parent_ele->latest_mr )[0];
1039 0 : }
1040 0 : fd_pubkey_t leader = {0};
1041 0 : fd_pubkey_t const * _leader = fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, bank->f.slot );
1042 0 : if( FD_LIKELY( _leader ) ) leader = *_leader;
1043 0 : fd_sol_sysvar_clock_t clock = {0};
1044 0 : if( FD_UNLIKELY( !fd_sysvar_clock_read( ctx->accdb, bank->accdb_fork_id, &clock ) ) ) FD_LOG_ERR(( "failed to read clock sysvar for slot %lu", bank->f.slot ));
1045 0 : ulong num_shreds = fd_ulong_if( bank==ctx->leader_bank, (ulong)ctx->block_id_arr[ bank->idx ].fec_cnt*FD_FEC_SHRED_CNT, bank->f.shred_cnt );
1046 0 : fd_event_runtime_block_emit( bank, block_id->uc, parent_block_id.uc, leader.uc,
1047 0 : execution_fees, priority_fees, tips, num_shreds, &clock,
1048 0 : ctx->fec_chain + bank->idx*FD_FEC_BLK_MAX,
1049 0 : ctx->block_id_arr[ bank->idx ].fec_cnt );
1050 0 : }
1051 :
1052 : static void
1053 : mark_bank_dead( fd_replay_tile_t * ctx,
1054 : fd_stem_context_t * stem,
1055 : ulong bank_idx,
1056 : int dead_reason,
1057 : int abandoned_reason );
1058 :
1059 :
1060 : /**********************************************************************/
1061 : /* Leader bank management */
1062 : /**********************************************************************/
1063 :
1064 : static fd_bank_t *
1065 : prepare_leader_bank( fd_replay_tile_t * ctx,
1066 : fd_bank_t * parent_bank,
1067 : ulong slot,
1068 0 : long now ) {
1069 :
1070 : /* Make sure that we are not already leader. */
1071 0 : FD_TEST( ctx->leader_bank==NULL );
1072 :
1073 0 : ctx->leader_bank = fd_banks_new_bank( ctx->banks, parent_bank->idx, now, 1 );
1074 0 : if( FD_UNLIKELY( !ctx->leader_bank ) ) {
1075 0 : FD_LOG_CRIT(( "invariant violation: leader bank is NULL for slot %lu", slot ));
1076 0 : }
1077 :
1078 0 : ctx->leader_bank = fd_banks_clone_from_parent( ctx->banks, ctx->leader_bank->idx );
1079 0 : if( FD_UNLIKELY( !ctx->leader_bank ) ) {
1080 0 : FD_LOG_CRIT(( "invariant violation: bank is NULL for slot %lu", slot ));
1081 0 : }
1082 :
1083 0 : ctx->leader_bank->preparation_begin_nanos = now;
1084 :
1085 0 : ctx->leader_bank->f.slot = slot;
1086 :
1087 : /* Clear the previous occupant of this reused bank idx from whichever
1088 : map it is currently keyed in. */
1089 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ ctx->leader_bank->idx ];
1090 0 : if( !ctx->alpenglow && 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 ) ) {
1091 0 : FD_TEST( fd_block_id_map_ele_remove( ctx->block_id_map, &block_id_ele->latest_mr, NULL, ctx->block_id_arr ) );
1092 0 : } else if( ctx->alpenglow && FD_LIKELY( fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr )==block_id_ele ) ) {
1093 0 : FD_TEST( fd_ag_block_id_map_ele_remove( ctx->ag_block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr ) );
1094 0 : }
1095 :
1096 0 : block_id_ele->block_id_seen = 0;
1097 0 : block_id_ele->slot = slot;
1098 0 : block_id_ele->bank_seq = ctx->leader_bank->bank_seq;
1099 0 : block_id_ele->latest_fec_idx = 0U;
1100 0 : memset( &block_id_ele->latest_mr, 0, sizeof(fd_hash_t) );
1101 0 : memset( &block_id_ele->dmr, 0, sizeof(fd_hash_t) );
1102 :
1103 0 : ctx->leader_bank->txncache_fork_id = fd_txncache_attach_child ( ctx->txncache, parent_bank->txncache_fork_id );
1104 0 : ctx->leader_bank->progcache_fork_id = fd_progcache_attach_child( ctx->progcache, parent_bank->progcache_fork_id );
1105 0 : ctx->leader_bank->accdb_fork_id = fd_accdb_attach_child ( ctx->accdb, parent_bank->accdb_fork_id );
1106 0 : ctx->leader_bank->parent_accdb_fork_id = parent_bank->accdb_fork_id;
1107 :
1108 0 : int is_epoch_boundary = 0;
1109 0 : fd_runtime_block_execute_prepare( ctx->banks, ctx->leader_bank, ctx->accdb, ctx->runtime_stack, ctx->capture_ctx, &is_epoch_boundary );
1110 :
1111 0 : ulong max_tick_height;
1112 0 : if( FD_UNLIKELY( FD_RUNTIME_EXECUTE_SUCCESS!=fd_runtime_compute_max_tick_height( parent_bank->f.ticks_per_slot, slot, &max_tick_height ) ) ) {
1113 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 ));
1114 0 : }
1115 0 : ctx->leader_bank->f.max_tick_height = max_tick_height;
1116 :
1117 : /* Now that a bank has been created for the leader slot, increment the
1118 : reference count until we are done with the leader slot. */
1119 0 : ctx->leader_bank->refcnt++;
1120 :
1121 0 : return ctx->leader_bank;
1122 0 : }
1123 :
1124 : static inline void
1125 0 : maybe_switch_identity( fd_replay_tile_t * ctx ) {
1126 :
1127 0 : if( FD_LIKELY( fd_keyswitch_state_query( ctx->keyswitch )!=FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) return;
1128 :
1129 : /* Switch identity */
1130 :
1131 0 : FD_LOG_DEBUG(( "keyswitch: switching identity" ));
1132 :
1133 0 : memcpy( ctx->identity_pubkey, ctx->keyswitch->bytes, 32UL );
1134 0 : ctx->identity_dirty = 1;
1135 :
1136 0 : ctx->metrics.voted_slot = ULONG_MAX;
1137 :
1138 0 : fd_node_info_write_begin( ctx->node_info );
1139 0 : ctx->node_info->info.identity = *ctx->identity_pubkey;
1140 0 : fd_node_info_write_end ( ctx->node_info );
1141 :
1142 0 : fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
1143 :
1144 : /* The next leader slot will be incorrect now that the identity has
1145 : switched. The next leader slot normally gets updated based on the
1146 : reset slot returned by tower. */
1147 0 : if( FD_LIKELY( !ctx->alpenglow ) ) {
1148 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 ) );
1149 0 : ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, min_leader_slot, ctx->identity_pubkey );
1150 0 : ctx->next_leader_tickcount = LONG_MAX;
1151 0 : if( FD_LIKELY( ctx->next_leader_slot != ULONG_MAX && ctx->is_booted ) ) {
1152 : /* If we are booted, we have a reset_bank, so use it to set
1153 : next_leader_tickcount. If we are not booted, then we don't need
1154 : to set next_leader_tickcount as it will be set when we boot. */
1155 0 : fd_block_id_ele_t * block_id_ele = fd_block_id_ele_query( ctx, &ctx->reset_cmr, ctx->reset_slot );
1156 0 : if( FD_LIKELY( block_id_ele ) ) {
1157 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 ) );
1158 0 : if( FD_LIKELY( reset_bank && reset_bank->bank_seq==block_id_ele->bank_seq && reset_bank->state!=FD_BANK_STATE_PRUNABLE ) ) {
1159 0 : double slot_duration_ticks = (double)reset_bank->f.slot_params.ns_per_slot_adjusted*ctx->tick_per_ns;
1160 0 : ctx->next_leader_tickcount = (long)((double)(ctx->next_leader_slot-ctx->reset_slot-1UL)*slot_duration_ticks) + fd_tickcount();
1161 0 : }
1162 0 : }
1163 0 : }
1164 0 : }
1165 :
1166 0 : ctx->identity_vote_rooted = 0;
1167 0 : ctx->identity_idx++;
1168 0 : fd_vote_tracker_reset( ctx->vote_tracker );
1169 0 : }
1170 :
1171 : static void
1172 : construct_footer_certs( fd_replay_tile_t const * ctx,
1173 : ulong leader_slot,
1174 : ulong migration_slot,
1175 0 : fd_block_footer_t * footer ) {
1176 0 : fd_votor_certed_t const * fin = ctx->votor_final;
1177 0 : int use_fast = fin->slot!=ULONG_MAX && fin->kind==AG_CERT_KIND_FAST_FINAL;
1178 0 : int use_slow = fin->slot!=ULONG_MAX && fin->kind==AG_CERT_KIND_FINAL;
1179 :
1180 0 : footer->has_fast_final_cert = use_fast;
1181 0 : footer->has_final_cert = use_slow;
1182 0 : if( footer->has_fast_final_cert ) {
1183 0 : footer->has_fast_final_cert = fd_block_footer_cert_from_agg( &footer->fast_final_cert, fin->slot, fin->block_id.uc, &fin->agg );
1184 0 : }
1185 0 : if( footer->has_final_cert ) {
1186 0 : footer->has_final_cert = fd_block_footer_cert_from_agg( &footer->final_cert, fin->slot, NULL, &fin->agg ) &&
1187 0 : fd_block_footer_cert_from_agg( &footer->notar_cert, fin->slot, fin->block_id.uc, &fin->agg2 );
1188 0 : }
1189 :
1190 0 : int reward_ok = migration_slot!=ULONG_MAX &&
1191 0 : leader_slot>=migration_slot+FD_NUM_SLOTS_FOR_REWARD+1UL;
1192 0 : if( FD_LIKELY( reward_ok ) ) {
1193 0 : ulong reward_slot = leader_slot-FD_NUM_SLOTS_FOR_REWARD;
1194 0 : fd_votor_reward_t const * reward = &ctx->votor_reward[ reward_slot%(FD_NUM_SLOTS_FOR_REWARD+AG_SLOTS_PER_WINDOW+1UL) ];
1195 0 : footer->has_notar_reward_cert = reward->slot==reward_slot && !fd_bls_set_is_null( reward->agg_notar.set ) && fd_block_footer_cert_from_agg( &footer->notar_reward_cert, reward_slot, reward->block_id.uc, &reward->agg_notar );
1196 0 : footer->has_skip_reward_cert = reward->slot==reward_slot && !fd_bls_set_is_null( reward->agg_skip.set ) && fd_block_footer_cert_from_agg( &footer->skip_reward_cert, reward_slot, NULL, &reward->agg_skip );
1197 0 : }
1198 0 : }
1199 :
1200 : static void
1201 : publish_leader_footer( fd_replay_tile_t * ctx,
1202 : fd_stem_context_t * stem,
1203 : ulong slot,
1204 0 : fd_block_footer_t const * footer ) {
1205 0 : fd_replay_leader_footer_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
1206 0 : msg->slot = slot;
1207 0 : msg->footer = *footer;
1208 :
1209 0 : ulong sz = sizeof(fd_replay_leader_footer_t);
1210 0 : fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_LEADER_FOOTER, ctx->replay_out->chunk, sz, 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
1211 0 : ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sz, ctx->replay_out->chunk0, ctx->replay_out->wmark );
1212 0 : }
1213 :
1214 : /* For the most part, try_become_leader_ag mirrors try_become_leader
1215 : with modifications for Alpenglow-specific changes, in particular
1216 : 1. no more PoH-hashing and 2. picking the reset bank is based on
1217 : ParentReady rather than fork choice. */
1218 :
1219 : static inline int
1220 : try_become_leader_ag( fd_replay_tile_t * ctx,
1221 0 : fd_stem_context_t * stem ) {
1222 :
1223 : /* Mirrors check from try_become_leader. */
1224 :
1225 0 : if( FD_LIKELY( ctx->next_leader_slot==ULONG_MAX ||
1226 0 : ctx->is_leader ||
1227 : /* (!ctx->identity_vote_rooted && ctx->wait_for_vote_to_start_leader) || // deprecated */
1228 0 : ctx->replay_out->idx==ULONG_MAX ||
1229 0 : !ctx->wfs_complete ) ) {
1230 0 : return 0;
1231 0 : }
1232 :
1233 0 : ulong parent_slot = ctx->votor_leader->parent_slot;
1234 0 : fd_hash_t const * parent_block_id = &ctx->votor_leader->parent_block_id;
1235 :
1236 0 : fd_block_id_ele_t * block_id_ele = fd_block_id_ele_query( ctx, parent_block_id, parent_slot );
1237 0 : if( FD_UNLIKELY( !block_id_ele ) ) return 0;
1238 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 ) );
1239 0 : if( FD_UNLIKELY( !reset_bank || reset_bank->bank_seq!=block_id_ele->bank_seq || reset_bank->state!=FD_BANK_STATE_FROZEN ) ) return 0;
1240 :
1241 0 : if( FD_UNLIKELY( !fd_banks_can_start_bank( ctx->banks ) ) ) return 0;
1242 0 : if( FD_UNLIKELY( ctx->halt_leader ) ) return 0;
1243 0 : if( !ctx->supports_leader ) return 0;
1244 :
1245 : /* In Alpenglow, the "reset" block is signaled by ParentReady (a state
1246 : transition in the Votor consensus logic). ParentReady can occur
1247 : ahead of replay, unlike Tower, in which the reset bank has by
1248 : definition been replayed. So we publish reset here instead, once
1249 : we know we are certain we can build off the ParentReady bank. */
1250 :
1251 0 : ctx->reset_cmr = block_id_ele->latest_mr;
1252 0 : ctx->reset_dmr = *parent_block_id;
1253 0 : ctx->reset_slot = parent_slot;
1254 0 : ctx->reset_timestamp_nanos = fd_clock_tile_now( ctx->clock );
1255 :
1256 0 : FD_TEST( ctx->next_leader_slot>ctx->reset_slot );
1257 0 : long now = fd_tickcount();
1258 0 : ctx->next_leader_tickcount = now;
1259 :
1260 0 : fd_poh_reset_t * reset = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
1261 :
1262 0 : reset->bank_idx = reset_bank->idx;
1263 0 : reset->timestamp = ctx->reset_timestamp_nanos;
1264 0 : reset->completed_slot = ctx->reset_slot;
1265 0 : reset->hashcnt_per_tick = reset_bank->f.slot_params.hashes_per_tick;
1266 0 : reset->ticks_per_slot = reset_bank->f.ticks_per_slot;
1267 0 : reset->tick_duration_ns = reset_bank->f.slot_params.ns_per_slot_adjusted/reset->ticks_per_slot;
1268 :
1269 0 : fd_memcpy( reset->completed_cmr, &block_id_ele->latest_mr, sizeof(fd_hash_t) );
1270 0 : fd_memcpy( reset->completed_dmr, parent_block_id, sizeof(fd_hash_t) );
1271 :
1272 0 : fd_blockhashes_t const * block_hash_queue = &reset_bank->f.block_hash_queue;
1273 0 : fd_hash_t const * last_hash = fd_blockhashes_peek_last_hash( block_hash_queue );
1274 0 : FD_TEST( last_hash );
1275 0 : fd_memcpy( reset->completed_blockhash, last_hash->uc, sizeof(fd_hash_t) );
1276 :
1277 0 : reset->max_microblocks_in_slot = FD_POH_ALPENGLOW_MAX_MICROBLOCKS_PER_SLOT;
1278 0 : reset->next_leader_slot = ctx->next_leader_slot;
1279 0 : reset->wfs_paused = !ctx->wfs_complete;
1280 :
1281 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() ) );
1282 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 );
1283 :
1284 : /* The remainder of this function mirrors try_become_leader. */
1285 :
1286 0 : long now_nanos = fd_clock_epoch_y( ctx->clock->epoch, now );
1287 :
1288 0 : ctx->is_leader = 1;
1289 0 : ctx->recv_poh = 0;
1290 :
1291 0 : memset( &ctx->leader_stats, 0, sizeof(ctx->leader_stats) );
1292 0 : ctx->leader_stats.slot = ctx->next_leader_slot;
1293 0 : ctx->leader_stats.timing_table_idx = ULONG_MAX;
1294 0 : ctx->leader_stats.became_leader_nanos = now_nanos;
1295 0 : ctx->leader_stats.leader_slot_start_nanos = fd_clock_epoch_y( ctx->clock->epoch, ctx->next_leader_tickcount );
1296 :
1297 0 : FD_TEST( ctx->highwater_leader_slot==ULONG_MAX || ctx->highwater_leader_slot<ctx->next_leader_slot );
1298 0 : ctx->highwater_leader_slot = ctx->next_leader_slot;
1299 :
1300 0 : FD_LOG_INFO(( "becoming leader for slot %lu, parent slot is %lu", ctx->next_leader_slot, ctx->reset_slot ));
1301 :
1302 0 : fd_bank_t * bank = prepare_leader_bank( ctx, reset_bank, ctx->next_leader_slot, now_nanos );
1303 :
1304 0 : fd_bundle_crank_tip_payment_config_t config[1] = { 0 };
1305 0 : fd_pubkey_t tip_receiver_owner = {0};
1306 :
1307 0 : if( FD_UNLIKELY( ctx->bundle.enabled ) ) {
1308 0 : fd_acct_addr_t tip_payment_config[1];
1309 0 : fd_acct_addr_t tip_receiver[1];
1310 0 : fd_bundle_crank_get_addresses( ctx->bundle.gen, bank->f.epoch, tip_payment_config, tip_receiver );
1311 :
1312 0 : fd_acc_t tip_config_acc = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, tip_payment_config->b );
1313 0 : if( FD_UNLIKELY( !tip_config_acc.lamports ) ) {
1314 0 : FD_BASE58_ENCODE_32_BYTES( tip_payment_config->b, tip_config_acc_b58 );
1315 0 : FD_LOG_WARNING(( "tip payment config account %s does not exist", tip_config_acc_b58 ));
1316 0 : fd_accdb_unread_one( ctx->accdb, &tip_config_acc );
1317 0 : } else if( FD_UNLIKELY( tip_config_acc.data_len<sizeof(fd_bundle_crank_tip_payment_config_t) ) ) {
1318 0 : FD_LOG_HEXDUMP_WARNING(( "invalid tip payment config account data", tip_config_acc.data, tip_config_acc.data_len ));
1319 0 : fd_accdb_unread_one( ctx->accdb, &tip_config_acc );
1320 0 : } else {
1321 0 : memcpy( config, tip_config_acc.data, sizeof(fd_bundle_crank_tip_payment_config_t) );
1322 0 : fd_accdb_unread_one( ctx->accdb, &tip_config_acc );
1323 0 : }
1324 :
1325 : /* It is possible that the tip receiver account does not exist yet
1326 : if it is the first time in an epoch. */
1327 0 : fd_acc_t tip_receiver_acc = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, tip_receiver->b );
1328 0 : if( FD_LIKELY( tip_receiver_acc.lamports ) ) {
1329 0 : fd_memcpy( tip_receiver_owner.uc, tip_receiver_acc.owner, 32UL );
1330 0 : }
1331 0 : fd_accdb_unread_one( ctx->accdb, &tip_receiver_acc );
1332 0 : }
1333 :
1334 :
1335 0 : fd_became_leader_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
1336 0 : msg->slot = ctx->next_leader_slot;
1337 0 : msg->slot_start_ns = now_nanos;
1338 0 : msg->slot_end_ns = now_nanos+(long)bank->f.slot_params.ns_per_slot_adjusted;
1339 0 : msg->bank = NULL;
1340 0 : msg->bank_idx = bank->idx;
1341 0 : msg->bank_seq = bank->bank_seq;
1342 0 : msg->ticks_per_slot = bank->f.ticks_per_slot;
1343 0 : msg->hashcnt_per_tick = 1UL; /* one tick per block, no hash budget (see replay_block_start) */
1344 0 : msg->tick_duration_ns = bank->f.slot_params.ns_per_slot_adjusted/msg->ticks_per_slot;
1345 0 : msg->bundle->config[0] = config[0];
1346 0 : memcpy( msg->bundle->last_blockhash, bank->f.poh.hash, sizeof(fd_hash_t) );
1347 0 : memcpy( msg->bundle->tip_receiver_owner, tip_receiver_owner.uc, sizeof(fd_pubkey_t) );
1348 :
1349 0 : msg->max_microblocks_in_slot = FD_POH_ALPENGLOW_MAX_MICROBLOCKS_PER_SLOT;
1350 :
1351 0 : msg->total_skipped_ticks = 0UL; /* even when slots are skipped, ticks increment by exactly one for every block */
1352 0 : msg->epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, ctx->next_leader_slot, NULL );
1353 :
1354 0 : fd_cost_tracker_t const * cost_tracker = fd_bank_cost_tracker_query( bank );
1355 :
1356 0 : msg->limits.slot_max_cost = cost_tracker->block_cost_limit;
1357 0 : msg->limits.slot_max_vote_cost = FD_PACK_MAX_VOTE_COST_PER_BLOCK_UPPER_BOUND;
1358 0 : msg->limits.slot_max_write_cost_per_acct = cost_tracker->account_cost_limit;
1359 0 : msg->limits.slot_max_allocated_data_per_block = cost_tracker->data_size_limit;
1360 0 : msg->limits.slot_max_data_shreds = bank->f.slot_params.max_shred_idx;
1361 :
1362 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() ) );
1363 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 );
1364 :
1365 0 : ctx->next_leader_slot = ULONG_MAX;
1366 0 : ctx->next_leader_tickcount = LONG_MAX;
1367 :
1368 0 : return 1;
1369 0 : }
1370 :
1371 : static int
1372 : replay_block_finalize( fd_replay_tile_t * ctx,
1373 : fd_stem_context_t * stem,
1374 0 : fd_bank_t * bank ) {
1375 0 : bank->last_transaction_finished_nanos = fd_clock_tile_now( ctx->clock );
1376 :
1377 : /* Set poh hash in bank. */
1378 0 : fd_hash_t * poh = fd_sched_get_poh( ctx->sched, bank->idx );
1379 0 : bank->f.poh = *poh;
1380 :
1381 : /* Set shred count in bank. */
1382 0 : bank->f.shred_cnt = fd_sched_get_shred_cnt( ctx->sched, bank->idx );
1383 :
1384 0 : ulong execution_fees_pre_settle = bank->f.execution_fees;
1385 0 : ulong priority_fees_pre_settle = bank->f.priority_fees;
1386 0 : ulong tips_pre_settle = bank->f.tips;
1387 :
1388 0 : fd_block_footer_t const * footer = NULL;
1389 0 : if( FD_UNLIKELY( ctx->alpenglow ) ) footer = fd_sched_get_footer( ctx->sched, bank->idx ); // guaranteed by sched
1390 :
1391 : /* Do hashing and other end-of-block processing. */
1392 0 : if( FD_UNLIKELY( fd_runtime_block_execute_finalize( bank, ctx->accdb, ctx->capture_ctx, footer, shred_version( ctx ) ) ) ) {
1393 0 : mark_bank_dead( ctx, stem, bank->idx, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_BAD_FOOTER, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED );
1394 0 : return 1;
1395 0 : }
1396 :
1397 0 : if( FD_UNLIKELY( ctx->alpenglow ) ) {
1398 0 : fd_hash_t const * footer_bank_hash = &footer->bank_hash;
1399 0 : if( FD_UNLIKELY( memcmp( footer_bank_hash->uc, bank->f.bank_hash.uc, sizeof(fd_hash_t) ) ) ) {
1400 0 : FD_BASE58_ENCODE_32_BYTES( footer_bank_hash->uc, footer_bank_hash_b58 );
1401 0 : FD_BASE58_ENCODE_32_BYTES( bank->f.bank_hash.uc, executed_bank_hash_b58 );
1402 0 : FD_LOG_WARNING(( "slot %lu: bank hash mismatch, footer declares %s but executed %s. ", bank->f.slot, footer_bank_hash_b58, executed_bank_hash_b58 ));
1403 0 : mark_bank_dead( ctx, stem, bank->idx, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_BAD_FOOTER, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED );
1404 0 : return 1;
1405 0 : } else {
1406 0 : FD_BASE58_ENCODE_32_BYTES( footer_bank_hash->uc, footer_bank_hash_b58 );
1407 0 : FD_BASE58_ENCODE_32_BYTES( bank->f.bank_hash.uc, executed_bank_hash_b58 );
1408 0 : FD_LOG_INFO(( "slot %lu: bank hash matches, footer declares %s, executed %s", bank->f.slot, footer_bank_hash_b58, executed_bank_hash_b58 ));
1409 0 : }
1410 0 : }
1411 :
1412 0 : if( FD_UNLIKELY( ctx->report_runtime_diffs ) ) replay_runtime_block_emit( ctx, bank, execution_fees_pre_settle, priority_fees_pre_settle, tips_pre_settle );
1413 :
1414 : /* Copy out cost tracker fields before freezing */
1415 0 : fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
1416 0 : cost_tracker_snap( bank, slot_info );
1417 :
1418 : /* fetch identity balance infrequently and after set-identity. */
1419 0 : slot_info->identity_balance = ULONG_MAX;
1420 0 : if( FD_UNLIKELY( ctx->identity_dirty || bank->f.slot%4096UL==0UL ) ) {
1421 0 : ctx->identity_dirty = 0;
1422 0 : slot_info->identity_balance = fd_accdb_lamports( ctx->accdb, bank->accdb_fork_id, ctx->identity_pubkey->uc );
1423 0 : }
1424 :
1425 : /* Mark the bank as frozen. */
1426 0 : fd_block_id_ele_t const * block_id_ele = &ctx->block_id_arr[ bank->idx ];
1427 0 : bank->f.block_id = ctx->alpenglow ? block_id_ele->dmr : block_id_ele->latest_mr;
1428 0 : fd_banks_mark_bank_frozen( bank );
1429 0 : bank->block_completed_nanos = fd_clock_tile_now( ctx->clock );
1430 :
1431 : /**********************************************************************/
1432 : /* Bank hash comparison, and halt if there's a mismatch after replay */
1433 : /**********************************************************************/
1434 :
1435 : /* Must be last so we can measure completion time correctly, even
1436 : though we could technically do this before the hash cmp and vote
1437 : tower stuff. */
1438 0 : publish_slot_completed( ctx, stem, bank, 0, 0 /* is_leader */, execution_fees_pre_settle, priority_fees_pre_settle );
1439 :
1440 : /* If enabled, dump the block to a file and reset the dumping
1441 : context state */
1442 0 : if( FD_UNLIKELY( ctx->dump_proto_ctx && ctx->dump_proto_ctx->dump_block_to_pb ) ) {
1443 0 : fd_dump_block_to_protobuf( ctx->block_dump_ctx, ctx->banks, bank, ctx->accdb, ctx->dump_proto_ctx, ctx->runtime_stack );
1444 0 : fd_block_dump_context_reset( ctx->block_dump_ctx );
1445 0 : }
1446 0 : return 0;
1447 0 : }
1448 :
1449 : static int
1450 : try_fini_leader( fd_replay_tile_t * ctx,
1451 0 : fd_stem_context_t * stem ) {
1452 :
1453 : /* If we are leader, we can only unbecome the leader iff we have
1454 : received the poh hash from the poh tile and block id from reasm.
1455 : The block id entry is claimed for the leader slot in
1456 : prepare_leader_bank, so a slot mismatch here means the claim
1457 : discipline broke. */
1458 :
1459 0 : if( FD_LIKELY( !ctx->is_leader ) ) return 0;
1460 0 : if( !ctx->recv_poh ) return 0;
1461 0 : if( !ctx->block_id_arr[ ctx->leader_bank->idx ].block_id_seen ) return 0;
1462 0 : FD_TEST( ctx->block_id_arr[ ctx->leader_bank->idx ].slot==ctx->leader_bank->f.slot );
1463 :
1464 0 : ulong curr_slot = ctx->leader_bank->f.slot;
1465 :
1466 0 : ulong execution_fees_pre_settle;
1467 0 : ulong priority_fees_pre_settle;
1468 0 : ulong tips_pre_settle;
1469 :
1470 0 : if( FD_UNLIKELY( ctx->alpenglow ) ) {
1471 :
1472 : /* Already finalized above, when the footer was published. */
1473 :
1474 0 : execution_fees_pre_settle = ctx->leader_execution_fees;
1475 0 : priority_fees_pre_settle = ctx->leader_priority_fees;
1476 0 : tips_pre_settle = ctx->leader_tips;
1477 :
1478 0 : } else {
1479 :
1480 0 : ctx->leader_bank->last_transaction_finished_nanos = fd_clock_tile_now( ctx->clock );
1481 :
1482 0 : fd_sched_block_add_done( ctx->sched, ctx->leader_bank->idx, ctx->leader_bank->parent_idx, curr_slot );
1483 :
1484 0 : execution_fees_pre_settle = ctx->leader_bank->f.execution_fees;
1485 0 : priority_fees_pre_settle = ctx->leader_bank->f.priority_fees;
1486 0 : tips_pre_settle = ctx->leader_bank->f.tips;
1487 :
1488 0 : fd_runtime_block_execute_finalize( ctx->leader_bank, ctx->accdb, ctx->capture_ctx, NULL, shred_version( ctx ) );
1489 0 : }
1490 :
1491 0 : if( FD_UNLIKELY( ctx->report_runtime_diffs ) ) replay_runtime_block_emit( ctx, ctx->leader_bank, execution_fees_pre_settle, priority_fees_pre_settle, tips_pre_settle );
1492 :
1493 0 : fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
1494 0 : cost_tracker_snap( ctx->leader_bank, slot_info );
1495 0 : slot_info->identity_balance = ULONG_MAX;
1496 0 : if( FD_UNLIKELY( ctx->identity_dirty || curr_slot%4096UL==0UL ) ) {
1497 0 : ctx->identity_dirty = 0;
1498 0 : slot_info->identity_balance = fd_accdb_lamports( ctx->accdb, ctx->leader_bank->accdb_fork_id, ctx->identity_pubkey->uc );
1499 0 : }
1500 :
1501 0 : ctx->leader_bank->f.block_id = ctx->alpenglow ? ctx->block_id_arr[ ctx->leader_bank->idx ].dmr : ctx->block_id_arr[ ctx->leader_bank->idx ].latest_mr;
1502 0 : fd_banks_mark_bank_frozen( ctx->leader_bank );
1503 0 : ctx->leader_bank->block_completed_nanos = fd_clock_tile_now( ctx->clock );
1504 :
1505 0 : publish_slot_completed( ctx, stem, ctx->leader_bank, 0, 1 /* is_leader */, execution_fees_pre_settle, priority_fees_pre_settle );
1506 :
1507 : /* The reference on the bank is finally no longer needed. */
1508 0 : ctx->leader_bank->refcnt--;
1509 :
1510 0 : fd_bank_t * completed = ctx->leader_bank;
1511 :
1512 : /* We are no longer leader so we can clear the bank index we use for
1513 : being the leader. */
1514 0 : ctx->leader_bank = NULL;
1515 0 : ctx->recv_poh = 0;
1516 0 : ctx->is_leader = 0;
1517 :
1518 0 : maybe_switch_identity( ctx );
1519 :
1520 0 : if( FD_UNLIKELY( ctx->alpenglow && ctx->next_leader_slot==ULONG_MAX && (curr_slot+1UL)%AG_SLOTS_PER_WINDOW ) ) {
1521 0 : *ctx->votor_leader = (fd_votor_leader_t){
1522 0 : .slot = curr_slot+1UL,
1523 0 : .parent_slot = curr_slot,
1524 0 : .parent_block_id = ctx->block_id_arr[ completed->idx ].dmr
1525 0 : };
1526 0 : ctx->next_leader_slot = curr_slot+1UL;
1527 0 : try_become_leader_ag( ctx, stem );
1528 0 : }
1529 :
1530 0 : return 1;
1531 0 : }
1532 :
1533 : static void
1534 : publish_root_advanced( fd_replay_tile_t * ctx,
1535 : fd_stem_context_t * stem,
1536 0 : fd_bank_t * bank ) {
1537 :
1538 : /* If the new consensus root is in the next epoch from the one the
1539 : replay tile currently holds, send the next epoch's leader schedule.
1540 : We can't use the new root's parent slot safely here. */
1541 0 : if( FD_UNLIKELY( bank->f.epoch>fd_slot_to_epoch( &bank->f.epoch_schedule, ctx->notified_root_slot, NULL ) ) ) {
1542 0 : fd_runtime_update_next_leaders( bank, ctx->runtime_stack );
1543 0 : publish_epoch_info( ctx, stem, bank, 1 );
1544 0 : }
1545 :
1546 0 : if( ctx->rpc_enabled ) {
1547 0 : bank->refcnt++;
1548 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for rpc", bank->idx, bank->f.slot, bank->refcnt ));
1549 0 : }
1550 :
1551 : /* Increment the reference count on the consensus root bank to account
1552 : for the number of resolv tiles that are waiting on it. */
1553 0 : bank->refcnt += ctx->resolv_tile_cnt;
1554 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for resolv", bank->idx, bank->f.slot, bank->refcnt ));
1555 :
1556 0 : fd_replay_root_advanced_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
1557 : /* f.* first: gcc 11 SLP otherwise materialises the 27 KB idx..f.slot gap element-wise */
1558 0 : msg->slot = bank->f.slot;
1559 0 : msg->bank_hash = bank->f.bank_hash;
1560 0 : msg->block_id = bank->f.block_id;
1561 0 : msg->bank_idx = bank->idx;
1562 0 : msg->bank_seq = bank->bank_seq;
1563 :
1564 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() ) );
1565 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 );
1566 0 : }
1567 :
1568 : /* Determine the default slot params to use for slots where no
1569 : reduce_slot_time feature gate is in effect. This is important for
1570 : the inflation calculations, which use the slot times for
1571 : historical slots as input. Therefore we need the same semantics
1572 : as Agave, even after the reduce_slot_time feature gates are
1573 : active. */
1574 : static fd_slot_params_t
1575 0 : restore_default_slot_params( fd_bank_t const * bank ) {
1576 :
1577 : /* A reduction is effective if the effective ns_per_slot is less than
1578 : the 400ms value.
1579 : https://github.com/anza-xyz/agave/blob/v4.2/runtime/src/slot_params.rs#L332-L350 */
1580 0 : int reduction_effective = fd_slot_params_lookup( &FD_SLOT_PARAMS_400MS,
1581 0 : &bank->f.features,
1582 0 : &bank->f.epoch_schedule,
1583 0 : bank->f.slot ).ns_per_slot < FD_SLOT_PARAMS_400MS.ns_per_slot;
1584 :
1585 : /* In order to behave correctly in real networks, if a reduction is
1586 : effective then we use the 400ms slot params as the default. */
1587 0 : if( reduction_effective ) {
1588 0 : return FD_SLOT_PARAMS_400MS;
1589 0 : }
1590 :
1591 : /* If a reduction is not effective, then we can rely on the slot
1592 : times having remained constant throughout the lifetime of the
1593 : cluster, and can use the slot params from the manifest. Note that
1594 : in test clusters these may differ from the 400ms values. */
1595 0 : return bank->f.slot_params;
1596 0 : }
1597 :
1598 : static void
1599 : init_after_snapshot( fd_replay_tile_t * ctx,
1600 0 : fd_stem_context_t * stem ) {
1601 : /* snapin seeded the root stake delegations from the account stream.
1602 : Refresh against the completed accdb to resolve duplicate account
1603 : versions, remove stale entries, and calculate activation state. */
1604 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, FD_REPLAY_BOOT_BANK_SEQ );
1605 0 : if( FD_UNLIKELY( !bank ) ) {
1606 0 : FD_LOG_CRIT(( "invariant violation: replay bank is NULL at bank index %lu", FD_REPLAY_BOOT_BANK_SEQ ));
1607 0 : }
1608 :
1609 0 : char const * one_offs[ 16UL ];
1610 0 : for( ulong i=0UL; i<ctx->enable_features_cnt; i++ ) one_offs[ i ] = ctx->enable_features[ i ];
1611 0 : fd_features_enable_one_offs( &bank->f.features, one_offs, (uint)ctx->enable_features_cnt, 0UL );
1612 :
1613 : /* Set slot params based on the feature gates in the snapshot,
1614 : and assert that these are consistent with the values from the
1615 : manifest. These assertions match Agave:
1616 : https://github.com/anza-xyz/agave/blob/v4.2/runtime/src/bank.rs#L4839-L4869 */
1617 0 : fd_slot_params_t manifest_params = bank->f.slot_params;
1618 0 : bank->f.slot_params_default = restore_default_slot_params( bank );
1619 0 : bank->f.slot_params = fd_slot_params_at_slot( bank, bank->f.slot );
1620 0 : FD_TEST( bank->f.slot_params.ns_per_slot == manifest_params.ns_per_slot );
1621 0 : FD_TEST( bank->f.slot_params.slots_per_year == manifest_params.slots_per_year );
1622 0 : if( FD_LIKELY( manifest_params.hashes_per_tick && !FD_FEATURE_ACTIVE_BANK( bank, alpenglow ) ) ) {
1623 0 : FD_TEST( bank->f.slot_params.hashes_per_tick==manifest_params.hashes_per_tick );
1624 0 : }
1625 :
1626 0 : fd_runtime_update_next_leaders( bank, ctx->runtime_stack );
1627 0 : fd_runtime_update_leaders( bank, ctx->runtime_stack );
1628 :
1629 : /* Typically, when we cross an epoch boundary during normal
1630 : operation, we publish the stake weights for the new epoch. But
1631 : since we are starting from a snapshot, we need to publish two
1632 : epochs worth of stake weights: the previous epoch (which is
1633 : needed for voting on the current epoch), and the current epoch
1634 : (which is needed for voting on the next epoch). */
1635 0 : publish_epoch_info( ctx, stem, bank, 0 );
1636 0 : publish_epoch_info( ctx, stem, bank, 1 );
1637 :
1638 0 : fd_progcache_reset( ctx->progcache );
1639 0 : bank->progcache_fork_id = fd_progcache_fork_id_initial();
1640 :
1641 0 : bank->f.warmup_cooldown_rate_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, bank->f.features.reduce_stake_warmup_cooldown, NULL );
1642 0 : fd_stake_delegations_t * root_delegations = fd_banks_stake_delegations_root_query( ctx->banks );
1643 0 : fd_stake_history_t stake_history_[1];
1644 0 : fd_stake_history_t const * stake_history = fd_sysvar_cache_stake_history_view( &bank->f.sysvar_cache, stake_history_ );
1645 : /* Despite claims like https://github.com/solana-program/stake/pull/81
1646 : that the stake history sysvar is contiguous, testnet has in fact
1647 : had a gap at epoch 386. */
1648 0 : if( FD_UNLIKELY( !fd_sysvar_stake_history_is_contiguous( stake_history ) ) ) {
1649 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 ));
1650 0 : }
1651 0 : fd_stake_delegations_refresh(
1652 0 : root_delegations,
1653 0 : bank->f.epoch,
1654 0 : stake_history, /* may be NULL */
1655 0 : &bank->f.warmup_cooldown_rate_epoch,
1656 0 : FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ),
1657 0 : FD_FEATURE_ACTIVE_BANK( bank, remove_inactive_stakes ),
1658 0 : ctx->accdb,
1659 0 : bank->accdb_fork_id );
1660 0 : bank->f.total_effective_stake = root_delegations->effective_stake;
1661 0 : bank->f.total_activating_stake = root_delegations->activating_stake;
1662 0 : bank->f.total_deactivating_stake = root_delegations->deactivating_stake;
1663 :
1664 : /* Emit the stake-delegations boot baseline from the finalized root
1665 : cache (snapshot accepted and refreshed, or genesis loaded), so the
1666 : rows match the post-reconciliation cache rather than the raw
1667 : account stream. */
1668 0 : if( FD_UNLIKELY( ctx->report_runtime_diffs ) ) {
1669 0 : fd_stake_delegations_iter_t iter_[1];
1670 0 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, root_delegations, ctx->accdb, bank->accdb_fork_id, bank->f.epoch, &bank->f.warmup_cooldown_rate_epoch );
1671 0 : !fd_stake_delegations_iter_done( iter );
1672 0 : fd_stake_delegations_iter_next( iter ) ) {
1673 0 : fd_stake_delegation_t const * d = fd_stake_delegations_iter_ele( iter );
1674 0 : ulong ae = d->activation_epoch ==(ushort)USHORT_MAX ? ULONG_MAX : (ulong)d->activation_epoch;
1675 0 : ulong de = d->deactivation_epoch==(ushort)USHORT_MAX ? ULONG_MAX : (ulong)d->deactivation_epoch;
1676 0 : fd_event_runtime_stake_delegation_bootup_emit( bank->f.slot, bank->f.epoch, d->stake_account.uc,
1677 0 : d->vote_account.uc, d->stake, ae, de, d->credits_observed );
1678 0 : }
1679 0 : }
1680 :
1681 0 : fd_vote_stakes_refresh( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, ctx->accdb, bank->accdb_fork_id );
1682 :
1683 : /* After both snapshots have been loaded in, we can determine if we should
1684 : start distributing rewards. */
1685 :
1686 0 : fd_rewards_recalculate_partitioned_rewards( ctx->banks, bank, ctx->accdb, ctx->runtime_stack, ctx->capture_ctx );
1687 :
1688 : /* Signals fd_startup_gate */
1689 0 : FD_MGAUGE_SET( REPLAY, RUNTIME_STATUS, 1UL );
1690 0 : }
1691 :
1692 : static inline int
1693 : try_become_leader( fd_replay_tile_t * ctx,
1694 0 : fd_stem_context_t * stem ) {
1695 :
1696 0 : if( FD_LIKELY( ctx->next_leader_slot==ULONG_MAX ||
1697 0 : ctx->is_leader ||
1698 0 : (!ctx->identity_vote_rooted && ctx->wait_for_vote_to_start_leader) ||
1699 0 : ctx->replay_out->idx==ULONG_MAX ||
1700 0 : !ctx->wfs_complete ) ) {
1701 0 : return 0;
1702 0 : }
1703 :
1704 : /* If we have evicted the reset bank we can't become leader it may be
1705 : inactive or have been resused, we can't become leader. We may miss
1706 : our leader slot if we happen to evict our reset bank. As soon as
1707 : we re-replay the slot, we will be able to become leader again. */
1708 0 : fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &ctx->reset_cmr, NULL, ctx->block_id_arr );
1709 0 : if( FD_UNLIKELY( !block_id_ele ) ) return 0;
1710 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 ) );
1711 0 : if( FD_UNLIKELY( !reset_bank || reset_bank->bank_seq!=block_id_ele->bank_seq || reset_bank->state==FD_BANK_STATE_PRUNABLE ) ) return 0;
1712 :
1713 0 : if( FD_UNLIKELY( !fd_banks_can_start_bank( ctx->banks ) ) ) return 0;
1714 0 : if( FD_UNLIKELY( ctx->halt_leader ) ) return 0;
1715 0 : if( !ctx->supports_leader ) return 0;
1716 :
1717 0 : FD_TEST( ctx->next_leader_slot>ctx->reset_slot );
1718 0 : long now = fd_tickcount();
1719 0 : if( FD_LIKELY( now<ctx->next_leader_tickcount ) ) return 0;
1720 :
1721 : /* If a prior leader is still in the process of publishing their slot,
1722 : delay ours to let them finish ... unless they are so delayed that
1723 : we risk getting skipped by the leader following us. 3*slot duration
1724 : is a reasonable default here, although any value within our leader
1725 : span could be considered reasonable. This is arbitrary and chosen
1726 : due to intuition.
1727 :
1728 : If we are becoming leader for a slot at an epoch boundary where a
1729 : slot time reduction is taking effect, we have a choice for the
1730 : grace period: give the previous leader maximal time to complete
1731 : their slot, or use a shorter grace period reflecting the new
1732 : shorter slot duration that the next leader after us will use to
1733 : time out our slot. We choose the latter to minimize the risk of
1734 : our slot getting skipped: a grace period based on the new shorter
1735 : slot duration at next_leader_slot. This only matters for epoch
1736 : boundaries where a slot time reduction is taking effect, so either
1737 : choice is defensible. */
1738 0 : ulong ns_per_slot_adjusted = fd_slot_params_at_slot( reset_bank, ctx->next_leader_slot ).ns_per_slot_adjusted;
1739 0 : double slot_duration_ticks = (double)ns_per_slot_adjusted*ctx->tick_per_ns;
1740 0 : if( FD_UNLIKELY( now<ctx->next_leader_tickcount+(long)(3.0*slot_duration_ticks) ) ) {
1741 : /* TODO: Make the max_active_descendant calculation more efficient
1742 : by caching it in the bank structure and updating it as banks are
1743 : created and completed. */
1744 0 : ulong max_active_descendant = 0UL;
1745 0 : ulong child_idx = reset_bank->child_idx;
1746 0 : while( child_idx!=ULONG_MAX ) {
1747 0 : fd_bank_t * child_bank = fd_banks_bank_query( ctx->banks, child_idx );
1748 0 : max_active_descendant = fd_ulong_max( max_active_descendant, child_bank->f.slot );
1749 0 : child_idx = child_bank->sibling_idx;
1750 0 : }
1751 :
1752 : /* If the max_active_descendant is >= next_leader_slot, we waited
1753 : too long and a leader after us started publishing to try and skip
1754 : us. Just start our leader slot immediately, we might win ... */
1755 0 : if( FD_LIKELY( max_active_descendant>=ctx->reset_slot && max_active_descendant<ctx->next_leader_slot ) ) {
1756 : /* If one of the leaders between the reset slot and our leader
1757 : slot is in the process of publishing (they have a descendant
1758 : bank that is in progress of being replayed), then keep waiting.
1759 : We probably wouldn't get a leader slot out before they
1760 : finished.
1761 :
1762 : Unless... we are past the deadline to start our slot by more
1763 : than 3*(slot duration), in which case we should probably start
1764 : it to avoid getting skipped by the leader behind us. */
1765 0 : return 0;
1766 0 : }
1767 0 : }
1768 :
1769 : /* If we haven't started replaying the prior block, but we have
1770 : finished replaying the second to last slot of the prior
1771 : leader (and that leader is not us), we should give the prior leader
1772 : a little more time. */
1773 0 : if( FD_UNLIKELY( ctx->next_leader_slot==ctx->reset_slot+2UL && now<ctx->next_leader_tickcount+(long)(1.0*slot_duration_ticks) ) ) {
1774 :
1775 0 : fd_pubkey_t const * reset_leader = fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, ctx->reset_slot );
1776 0 : if( FD_UNLIKELY( reset_leader && !fd_memeq( reset_leader, ctx->identity_pubkey, 32UL ) ) ) return 0;
1777 0 : }
1778 :
1779 0 : long now_nanos = fd_clock_epoch_y( ctx->clock->epoch, now );
1780 :
1781 0 : ctx->is_leader = 1;
1782 0 : ctx->recv_poh = 0;
1783 :
1784 0 : memset( &ctx->leader_stats, 0, sizeof(ctx->leader_stats) );
1785 0 : ctx->leader_stats.slot = ctx->next_leader_slot;
1786 0 : ctx->leader_stats.timing_table_idx = ULONG_MAX;
1787 0 : ctx->leader_stats.became_leader_nanos = now_nanos;
1788 0 : ctx->leader_stats.leader_slot_start_nanos = fd_clock_epoch_y( ctx->clock->epoch, ctx->next_leader_tickcount );
1789 :
1790 0 : FD_TEST( ctx->highwater_leader_slot==ULONG_MAX || ctx->highwater_leader_slot<ctx->next_leader_slot );
1791 0 : ctx->highwater_leader_slot = ctx->next_leader_slot;
1792 :
1793 0 : FD_LOG_INFO(( "becoming leader for slot %lu, parent slot is %lu", ctx->next_leader_slot, ctx->reset_slot ));
1794 :
1795 0 : fd_bank_t * bank = prepare_leader_bank( ctx, reset_bank, ctx->next_leader_slot, now_nanos );
1796 :
1797 0 : fd_bundle_crank_tip_payment_config_t config[1] = { 0 };
1798 0 : fd_pubkey_t tip_receiver_owner = {0};
1799 :
1800 0 : if( FD_UNLIKELY( ctx->bundle.enabled ) ) {
1801 0 : fd_acct_addr_t tip_payment_config[1];
1802 0 : fd_acct_addr_t tip_receiver[1];
1803 0 : fd_bundle_crank_get_addresses( ctx->bundle.gen, bank->f.epoch, tip_payment_config, tip_receiver );
1804 :
1805 0 : fd_acc_t tip_config_acc = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, tip_payment_config->b );
1806 0 : if( FD_UNLIKELY( !tip_config_acc.lamports ) ) {
1807 0 : FD_BASE58_ENCODE_32_BYTES( tip_payment_config->b, tip_config_acc_b58 );
1808 0 : FD_LOG_WARNING(( "tip payment config account %s does not exist", tip_config_acc_b58 ));
1809 0 : fd_accdb_unread_one( ctx->accdb, &tip_config_acc );
1810 0 : } else if( FD_UNLIKELY( tip_config_acc.data_len<sizeof(fd_bundle_crank_tip_payment_config_t) ) ) {
1811 0 : FD_LOG_HEXDUMP_WARNING(( "invalid tip payment config account data", tip_config_acc.data, tip_config_acc.data_len ));
1812 0 : fd_accdb_unread_one( ctx->accdb, &tip_config_acc );
1813 0 : } else {
1814 0 : memcpy( config, tip_config_acc.data, sizeof(fd_bundle_crank_tip_payment_config_t) );
1815 0 : fd_accdb_unread_one( ctx->accdb, &tip_config_acc );
1816 0 : }
1817 :
1818 : /* It is possible that the tip receiver account does not exist yet
1819 : if it is the first time in an epoch. */
1820 0 : fd_acc_t tip_receiver_acc = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, tip_receiver->b );
1821 0 : if( FD_LIKELY( tip_receiver_acc.lamports ) ) {
1822 0 : fd_memcpy( tip_receiver_owner.uc, tip_receiver_acc.owner, 32UL );
1823 0 : }
1824 0 : fd_accdb_unread_one( ctx->accdb, &tip_receiver_acc );
1825 0 : }
1826 :
1827 :
1828 0 : fd_became_leader_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
1829 0 : msg->slot = ctx->next_leader_slot;
1830 0 : msg->slot_start_ns = now_nanos;
1831 0 : msg->slot_end_ns = now_nanos+(long)bank->f.slot_params.ns_per_slot_adjusted;
1832 0 : msg->bank = NULL;
1833 0 : msg->bank_idx = bank->idx;
1834 0 : msg->bank_seq = bank->bank_seq;
1835 0 : msg->ticks_per_slot = bank->f.ticks_per_slot;
1836 0 : msg->hashcnt_per_tick = bank->f.slot_params.hashes_per_tick;
1837 0 : msg->tick_duration_ns = bank->f.slot_params.ns_per_slot_adjusted/msg->ticks_per_slot;
1838 0 : msg->bundle->config[0] = config[0];
1839 0 : memcpy( msg->bundle->last_blockhash, bank->f.poh.hash, sizeof(fd_hash_t) );
1840 0 : memcpy( msg->bundle->tip_receiver_owner, tip_receiver_owner.uc, sizeof(fd_pubkey_t) );
1841 :
1842 0 : msg->max_microblocks_in_slot = fd_poh_max_microblocks_per_slot( msg->ticks_per_slot, msg->hashcnt_per_tick );
1843 :
1844 0 : msg->total_skipped_ticks = msg->ticks_per_slot*(ctx->next_leader_slot-ctx->reset_slot);
1845 0 : msg->epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, ctx->next_leader_slot, NULL );
1846 :
1847 0 : fd_cost_tracker_t const * cost_tracker = fd_bank_cost_tracker_query( bank );
1848 :
1849 0 : msg->limits.slot_max_cost = cost_tracker->block_cost_limit;
1850 0 : msg->limits.slot_max_vote_cost = FD_PACK_MAX_VOTE_COST_PER_BLOCK_UPPER_BOUND;
1851 0 : msg->limits.slot_max_write_cost_per_acct = cost_tracker->account_cost_limit;
1852 0 : msg->limits.slot_max_allocated_data_per_block = cost_tracker->data_size_limit;
1853 0 : msg->limits.slot_max_data_shreds = bank->f.slot_params.max_shred_idx;
1854 :
1855 0 : if( FD_UNLIKELY( msg->ticks_per_slot+msg->total_skipped_ticks>USHORT_MAX ) ) {
1856 : /* There can be at most USHORT_MAX skipped ticks, because the
1857 : parent_offset field in the shred data is only 2 bytes wide. */
1858 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 ));
1859 0 : }
1860 :
1861 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() ) );
1862 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 );
1863 :
1864 0 : ctx->next_leader_slot = ULONG_MAX;
1865 0 : ctx->next_leader_tickcount = LONG_MAX;
1866 :
1867 0 : return 1;
1868 0 : }
1869 :
1870 : /* https://github.com/anza-xyz/agave/blob/v4.3/runtime/src/block_component_processor.rs#L699-L765 */
1871 :
1872 : static ulong
1873 : enforce_nanosecond_clock_bounds( fd_replay_tile_t * ctx,
1874 : fd_bank_t * bank,
1875 0 : ulong now_nanos ) {
1876 0 : fd_pubkey_t alpenclock_addr;
1877 0 : fd_alpenglow_pda( "alpenclock", &alpenclock_addr );
1878 :
1879 0 : fd_acc_t acc = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, alpenclock_addr.uc );
1880 0 : FD_CHECK_CRIT( acc.lamports && acc.data_len>=sizeof(ulong), "alpenclock account missing" ); /* Firedancer does not support producing the FIRST alpenglow block */
1881 0 : long parent_nanos = (long)FD_LOAD( ulong, acc.data );
1882 0 : fd_accdb_unread_one( ctx->accdb, &acc );
1883 :
1884 0 : ulong elapsed = fd_slot_params_slot_range_duration_ns( bank, bank->f.parent_slot+1UL, bank->f.slot+1UL /* inclusive */ );
1885 :
1886 0 : long lo = fd_long_sat_add( parent_nanos, 1L );
1887 0 : long hi = fd_long_sat_add( parent_nanos, (long)fd_ulong_min( fd_ulong_sat_mul( elapsed, 2UL ), (ulong)LONG_MAX ) );
1888 :
1889 0 : return (ulong)fd_long_max( lo, fd_long_min( hi, (long)fd_ulong_min( now_nanos, (ulong)LONG_MAX ) ) );
1890 0 : }
1891 :
1892 : static void
1893 : process_poh_message( fd_replay_tile_t * ctx,
1894 : fd_stem_context_t * stem,
1895 0 : fd_poh_leader_slot_ended_t const * slot_ended ) {
1896 :
1897 0 : FD_TEST( ctx->is_booted );
1898 0 : FD_TEST( ctx->is_leader );
1899 0 : FD_TEST( ctx->leader_bank!=NULL );
1900 :
1901 0 : FD_TEST( ctx->highwater_leader_slot>=slot_ended->slot );
1902 0 : FD_TEST( ctx->next_leader_slot>ctx->highwater_leader_slot );
1903 :
1904 0 : if( FD_LIKELY( ctx->leader_stats.slot==slot_ended->slot ) ) {
1905 0 : ctx->leader_stats.microblock_count = slot_ended->microblock_count;
1906 0 : ctx->leader_stats.pack_block_cost = slot_ended->pack_block_cost;
1907 0 : ctx->leader_stats.pack_vote_cost = slot_ended->pack_vote_cost;
1908 0 : ctx->leader_stats.pack_data_bytes = slot_ended->pack_data_bytes;
1909 0 : ctx->leader_stats.bundle_txn_count = slot_ended->bundle_txn_count;
1910 0 : ctx->leader_stats.pack_end_reason = slot_ended->pack_end_reason;
1911 0 : ctx->leader_stats.pack_start_nanos = slot_ended->pack_start_ns;
1912 0 : ctx->leader_stats.pack_end_nanos = slot_ended->pack_end_ns;
1913 0 : ctx->leader_stats.timing_table_idx = slot_ended->timing_table_idx;
1914 0 : }
1915 :
1916 0 : if( FD_UNLIKELY( !slot_ended->completed ) ) {
1917 : /* The leader slot was aborted by a reset mid-production. The
1918 : block-complete entry was never emitted, so no slot-complete FEC
1919 : (and thus no block id) will ever arrive. */
1920 0 : ulong bank_idx = ctx->leader_bank->idx;
1921 0 : ctx->leader_bank->refcnt--;
1922 0 : ctx->leader_bank = NULL;
1923 0 : ctx->recv_poh = 0;
1924 0 : ctx->is_leader = 0;
1925 0 : mark_bank_dead( ctx, stem, bank_idx, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_RESET );
1926 0 : maybe_switch_identity( ctx );
1927 0 : return;
1928 0 : }
1929 :
1930 : /* Update the poh hash in the bank. We will want to maintain a refcnt
1931 : on the bank until we have received the block id for the block after
1932 : it has been shredded. */
1933 :
1934 0 : memcpy( &ctx->leader_bank->f.poh, slot_ended->blockhash, sizeof(fd_hash_t) );
1935 :
1936 0 : ctx->recv_poh = 1;
1937 :
1938 0 : if( FD_UNLIKELY( ctx->alpenglow ) ) {
1939 0 : ctx->leader_bank->last_transaction_finished_nanos = fd_clock_tile_now( ctx->clock );
1940 0 : fd_sched_block_add_done( ctx->sched, ctx->leader_bank->idx, ctx->leader_bank->parent_idx, ctx->leader_bank->f.slot );
1941 :
1942 : /* cache fees before runtime zeros */
1943 :
1944 0 : ctx->leader_execution_fees = ctx->leader_bank->f.execution_fees;
1945 0 : ctx->leader_priority_fees = ctx->leader_bank->f.priority_fees;
1946 0 : ctx->leader_tips = ctx->leader_bank->f.tips;
1947 :
1948 0 : fd_block_footer_t * footer = ctx->leader_footer;
1949 0 : fd_memset( footer, 0, sizeof(fd_block_footer_t) );
1950 0 : footer->block_producer_time_nanos = enforce_nanosecond_clock_bounds( ctx, ctx->leader_bank, (ulong)fd_clock_tile_now( ctx->clock ) );
1951 :
1952 0 : ulong migration_slot = fd_alpenglow_migration_slot( ctx->leader_bank, ctx->accdb );
1953 0 : construct_footer_certs( ctx, ctx->leader_bank->f.slot, migration_slot, footer );
1954 :
1955 : /* The block goes out regardless: the certs are already committed to
1956 : the bank hash, so there is nothing left to fall back to. */
1957 0 : if( FD_UNLIKELY( fd_runtime_block_execute_finalize( ctx->leader_bank, ctx->accdb, ctx->capture_ctx, footer, shred_version( ctx ) ) ) ) {
1958 0 : FD_LOG_WARNING(( "slot %lu: our own block footer certs did not apply; the block we produce will be dead to the cluster", ctx->leader_bank->f.slot ));
1959 0 : }
1960 0 : footer->bank_hash = ctx->leader_bank->f.bank_hash;
1961 :
1962 0 : publish_leader_footer( ctx, stem, ctx->leader_bank->f.slot, footer );
1963 0 : }
1964 0 : }
1965 :
1966 : static void
1967 : store_xinsert( fd_store_t * store,
1968 : fd_store_map_t * map_join,
1969 0 : fd_hash_t const * merkle_root ) {
1970 0 : fd_store_fec_t * fec;
1971 0 : FD_TEST( !fd_store_insert( store, map_join, merkle_root, &fec ) && fec );
1972 0 : }
1973 :
1974 : static void
1975 : boot_genesis( fd_replay_tile_t * ctx,
1976 : fd_stem_context_t * stem,
1977 0 : fd_genesis_meta_t const * meta ) {
1978 :
1979 : /* TODO boot_genesis for Alpenglow */
1980 :
1981 0 : FD_CHECK_ERR( !ctx->alpenglow, "alpenglow does not support genesis yet" );
1982 :
1983 : /* If we are bootstrapping, we can't wait to wait for our identity
1984 : vote to be rooted as this creates a circular dependency. */
1985 0 : ctx->identity_vote_rooted = 1;
1986 :
1987 0 : ctx->caught_up = 1;
1988 :
1989 0 : ctx->hard_fork_cnt = 0UL;
1990 :
1991 0 : uchar const * genesis_blob = (uchar const *)( meta+1 );
1992 0 : FD_TEST( meta->bootstrap && meta->has_lthash );
1993 0 : FD_TEST( fd_genesis_parse( ctx->genesis, genesis_blob, meta->blob_sz ) );
1994 :
1995 0 : fd_bank_t * bank = fd_banks_init_bank( ctx->banks );
1996 0 : FD_TEST( bank );
1997 0 : bank->f.slot = 0UL;
1998 0 : FD_TEST( bank->idx==FD_REPLAY_BOOT_BANK_SEQ );
1999 :
2000 0 : static const fd_accdb_fork_id_t accdb_root = { .val = USHORT_MAX };
2001 0 : bank->accdb_fork_id = fd_accdb_attach_child( ctx->accdb, accdb_root );
2002 0 : bank->parent_accdb_fork_id = bank->accdb_fork_id;
2003 :
2004 0 : fd_runtime_read_genesis( ctx->banks, bank, ctx->accdb, NULL, &meta->genesis_hash, &meta->lthash, ctx->genesis, genesis_blob, ctx->runtime_stack );
2005 0 : FD_TEST( fd_sysvar_cache_restore( bank, ctx->accdb ) );
2006 :
2007 0 : bank->txncache_fork_id = fd_txncache_attach_child ( ctx->txncache, (fd_txncache_fork_id_t){USHORT_MAX} );
2008 0 : bank->progcache_fork_id = fd_progcache_attach_child( ctx->progcache, fd_progcache_fork_id_initial() );
2009 :
2010 0 : fd_hash_t const * block_hash = fd_blockhashes_peek_last_hash( &bank->f.block_hash_queue );
2011 0 : fd_txncache_finalize_fork( ctx->txncache, bank->txncache_fork_id, 0UL, block_hash->uc );
2012 :
2013 : /* We call this after fd_runtime_read_genesis, which sets up the
2014 : slot_bank needed in blockstore_init. */
2015 0 : init_after_snapshot( ctx, stem );
2016 :
2017 0 : ctx->published_root_slot = 0UL;
2018 0 : fd_sched_block_add_done( ctx->sched, bank->idx, ULONG_MAX, 0UL );
2019 :
2020 0 : bank->f.block_height = 1UL;
2021 :
2022 0 : ctx->consensus_root = ctx->initial_block_id;
2023 0 : ctx->consensus_root_slot = 0UL;
2024 0 : ctx->notified_root = ctx->initial_block_id;
2025 0 : ctx->notified_root_slot = 0UL;
2026 0 : ctx->notified_root_bank = bank;
2027 0 : ctx->published_root_slot = 0UL;
2028 0 : ctx->published_root_bank_idx = 0UL;
2029 0 : if( FD_UNLIKELY( ctx->snapmk.full_interval_blocks ) ) {
2030 0 : ulong interval = ctx->snapmk.full_interval_blocks;
2031 0 : ctx->snapmk.next_full_block_height = ((bank->f.block_height/interval)+1UL)*interval;
2032 0 : }
2033 0 : if( FD_UNLIKELY( ctx->snapmk.incremental_interval_blocks ) ) {
2034 0 : ulong interval = ctx->snapmk.incremental_interval_blocks;
2035 0 : ctx->snapmk.next_incremental_block_height = ((bank->f.block_height/interval)+1UL)*interval;
2036 0 : }
2037 :
2038 0 : ctx->reset_slot = 0UL;
2039 0 : ctx->reset_cmr = ctx->initial_block_id;
2040 0 : ctx->reset_dmr = ctx->initial_block_id;
2041 0 : ctx->reset_timestamp_nanos = fd_clock_tile_now( ctx->clock );
2042 0 : ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, 1UL, ctx->identity_pubkey );
2043 0 : if( FD_LIKELY( ctx->next_leader_slot != ULONG_MAX ) ) {
2044 0 : double slot_duration_ticks = (double)bank->f.slot_params.ns_per_slot_adjusted*ctx->tick_per_ns;
2045 0 : ctx->next_leader_tickcount = (long)((double)(ctx->next_leader_slot-ctx->reset_slot-1UL)*slot_duration_ticks) + fd_tickcount();
2046 0 : } else {
2047 0 : ctx->next_leader_tickcount = LONG_MAX;
2048 0 : }
2049 :
2050 0 : ctx->has_cluster_type = 1;
2051 :
2052 0 : ctx->is_booted = 1;
2053 0 : try_become_leader( ctx, stem );
2054 :
2055 0 : fd_hash_t initial_block_id = ctx->initial_block_id;
2056 0 : fd_reasm_fec_t * fec = fd_reasm_init( ctx->reasm, &initial_block_id, 0 /* genesis slot */ );
2057 0 : fec->bank_idx = (uint)bank->idx;
2058 0 : fec->bank_seq = bank->bank_seq;
2059 0 : store_xinsert( ctx->store, ctx->map_join, &initial_block_id );
2060 :
2061 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ 0 ];
2062 0 : block_id_ele->latest_mr = initial_block_id;
2063 0 : block_id_ele->dmr = initial_block_id;
2064 0 : block_id_ele->slot = 0UL;
2065 0 : block_id_ele->bank_seq = bank->bank_seq;
2066 0 : bank->f.block_id = initial_block_id;
2067 :
2068 0 : FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, block_id_ele, ctx->block_id_arr ) );
2069 :
2070 0 : fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
2071 0 : cost_tracker_snap( bank, slot_info );
2072 :
2073 0 : slot_info->identity_balance = fd_accdb_lamports( ctx->accdb, bank->accdb_fork_id, ctx->identity_pubkey->uc );
2074 :
2075 0 : FD_LOG_INFO(( "replay ready at slot %lu (%.3f s after snapshot done, %.3f s since boot)",
2076 0 : 0UL, 0.0, (double)(fd_log_wallclock()-ctx->boot_timestamp_nanos)/1e9 ));
2077 :
2078 0 : publish_slot_completed( ctx, stem, bank, 1, 0 /* is_leader */, 0, 0 );
2079 0 : publish_root_advanced( ctx, stem, bank );
2080 :
2081 0 : if( FD_LIKELY( ctx->replay_out->idx!=ULONG_MAX ) ) {
2082 0 : fd_poh_reset_t * reset = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
2083 :
2084 0 : reset->bank_idx = bank->idx;
2085 0 : reset->timestamp = ctx->reset_timestamp_nanos;
2086 0 : reset->completed_slot = ctx->reset_slot;
2087 0 : reset->hashcnt_per_tick = bank->f.slot_params.hashes_per_tick;
2088 0 : reset->ticks_per_slot = bank->f.ticks_per_slot;
2089 0 : reset->tick_duration_ns = bank->f.slot_params.ns_per_slot_adjusted/reset->ticks_per_slot;
2090 :
2091 0 : fd_memcpy( reset->completed_cmr, &block_id_ele->latest_mr, sizeof(fd_hash_t) );
2092 :
2093 0 : fd_blockhashes_t const * block_hash_queue = &bank->f.block_hash_queue;
2094 0 : fd_hash_t const * last_hash = fd_blockhashes_peek_last_hash( block_hash_queue );
2095 0 : FD_TEST( last_hash );
2096 0 : fd_memcpy( reset->completed_blockhash, last_hash->uc, sizeof(fd_hash_t) );
2097 :
2098 0 : reset->max_microblocks_in_slot = fd_poh_max_microblocks_per_slot( bank->f.ticks_per_slot, reset->hashcnt_per_tick );
2099 0 : reset->next_leader_slot = ctx->next_leader_slot;
2100 0 : reset->wfs_paused = !ctx->wfs_complete;
2101 :
2102 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() ) );
2103 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 );
2104 0 : }
2105 0 : }
2106 :
2107 : static inline void
2108 0 : maybe_verify_cluster_type( fd_replay_tile_t * ctx ) {
2109 0 : if( FD_UNLIKELY( !ctx->has_cluster_type || !ctx->has_genesis_hash ) ) {
2110 0 : return;
2111 0 : }
2112 :
2113 0 : FD_BASE58_ENCODE_32_BYTES( ctx->genesis_hash->uc, hash_cstr );
2114 0 : ulong cluster = fd_genesis_cluster_identify( hash_cstr );
2115 : /* Map pyth-related clusters to unknown. */
2116 0 : switch( cluster ) {
2117 0 : case FD_CLUSTER_PYTHNET:
2118 0 : case FD_CLUSTER_PYTHTEST:
2119 0 : cluster = FD_CLUSTER_UNKNOWN;
2120 0 : }
2121 :
2122 0 : if( FD_UNLIKELY( cluster!=ctx->cluster_type ) ) {
2123 0 : FD_LOG_ERR(( "Your genesis.bin file at `%s` has a genesis hash of `%s` which means the cluster is %s "
2124 0 : "but the snapshot you loaded is for a different cluster %s. If you are trying to join the "
2125 0 : "%s cluster, you can delete the genesis.bin file and restart the node to download the correct "
2126 0 : "genesis file automatically.",
2127 0 : ctx->genesis_path,
2128 0 : hash_cstr,
2129 0 : fd_genesis_cluster_name( cluster ),
2130 0 : fd_genesis_cluster_name( ctx->cluster_type ),
2131 0 : fd_genesis_cluster_name( cluster ) ));
2132 0 : }
2133 0 : }
2134 :
2135 : static void
2136 : on_snapshot_message( fd_replay_tile_t * ctx,
2137 : fd_stem_context_t * stem,
2138 : ulong in_idx,
2139 : ulong chunk,
2140 0 : ulong sig ) {
2141 0 : ulong msg = fd_ssmsg_sig_message( sig );
2142 0 : if( FD_LIKELY( msg==FD_SSMSG_DONE ) ) {
2143 0 : long snapshot_done_nanos = fd_log_wallclock();
2144 :
2145 : /* An end of message notification indicates the snapshot is loaded.
2146 : Replay is able to start executing from this point onwards. */
2147 : /* TODO: replay should finish booting. Could make replay a
2148 : state machine and set the state here accordingly. */
2149 0 : ctx->is_booted = 1;
2150 :
2151 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, FD_REPLAY_BOOT_BANK_SEQ );
2152 0 : if( FD_UNLIKELY( !bank ) ) {
2153 0 : FD_LOG_CRIT(( "invariant violation: bank is NULL for bank index %lu", FD_REPLAY_BOOT_BANK_SEQ ));
2154 0 : }
2155 :
2156 0 : ulong snapshot_slot = bank->f.slot;
2157 :
2158 0 : fd_hash_t bank_hash = bank->f.bank_hash;
2159 0 : if( FD_UNLIKELY( ctx->wfs_enabled && memcmp( ctx->expected_bank_hash.uc, bank_hash.uc, sizeof(fd_hash_t) ) ) ) {
2160 0 : FD_BASE58_ENCODE_32_BYTES( ctx->expected_bank_hash.uc, expected_bank_hash_cstr );
2161 0 : FD_BASE58_ENCODE_32_BYTES( bank_hash.uc, actual_bank_hash_cstr );
2162 0 : FD_LOG_ERR(( "[consensus.wait_for_supermajority_with_bank_hash] expected_bank_hash=%s does not match snapshot slot"
2163 0 : "=%lu bank_hash=%s. If you are loading a snapshot from the network, check that the slot matches the "
2164 0 : "cluster restart slot. ", expected_bank_hash_cstr, snapshot_slot, actual_bank_hash_cstr ));
2165 0 : }
2166 0 : if( FD_UNLIKELY( ctx->wfs_enabled ) ) {
2167 0 : FD_LOG_NOTICE(( "waiting for supermajority at snapshot slot %lu", snapshot_slot ));
2168 0 : }
2169 :
2170 : /* Manifest message must arrive before DONE */
2171 0 : if( FD_UNLIKELY( !ctx->has_expected_genesis_timestamp ) ) {
2172 0 : FD_LOG_CRIT(( "snapshot DONE received before manifest" ));
2173 0 : }
2174 :
2175 : /* FIXME: This is a hack when the block id of the snapshot slot
2176 : is not provided in the snapshot (Agave versions <4.1). A
2177 : possible solution is to get the block id of the snapshot slot
2178 : from repair. */
2179 0 : fd_hash_t manifest_block_id = ctx->has_manifest_block_id ? ctx->manifest_block_id : ctx->initial_block_id;
2180 :
2181 0 : FD_TEST( fd_sysvar_cache_restore( bank, ctx->accdb ) );
2182 : /* Agave zeroes manifest rent_params; reload from sysvar account */
2183 0 : FD_TEST( fd_sysvar_rent_read( ctx->accdb, bank->accdb_fork_id, &bank->f.rent ) );
2184 :
2185 0 : ctx->consensus_root = manifest_block_id;
2186 0 : ctx->consensus_root_slot = snapshot_slot;
2187 0 : ctx->notified_root = manifest_block_id;
2188 0 : ctx->notified_root_slot = snapshot_slot;
2189 0 : ctx->notified_root_bank = bank;
2190 0 : ctx->published_root_slot = ctx->consensus_root_slot;
2191 0 : ctx->published_root_bank_idx = 0UL;
2192 0 : if( FD_UNLIKELY( ctx->snapmk.full_interval_blocks ) ) {
2193 0 : ulong interval = ctx->snapmk.full_interval_blocks;
2194 0 : ctx->snapmk.next_full_block_height = ((bank->f.block_height/interval)+1UL)*interval;
2195 0 : }
2196 0 : if( FD_UNLIKELY( ctx->snapmk.incremental_interval_blocks ) ) {
2197 0 : ulong interval = ctx->snapmk.incremental_interval_blocks;
2198 0 : ctx->snapmk.next_incremental_block_height = ((bank->f.block_height/interval)+1UL)*interval;
2199 0 : }
2200 :
2201 0 : ctx->reset_slot = snapshot_slot;
2202 0 : ctx->reset_cmr = manifest_block_id;
2203 0 : ctx->reset_dmr = manifest_block_id;
2204 0 : ctx->reset_timestamp_nanos = fd_clock_tile_now( ctx->clock );
2205 :
2206 0 : fd_sched_block_add_done( ctx->sched, bank->idx, ULONG_MAX, snapshot_slot );
2207 0 : FD_TEST( bank->idx==0UL );
2208 :
2209 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ 0 ];
2210 0 : block_id_ele->latest_mr = manifest_block_id;
2211 0 : block_id_ele->dmr = manifest_block_id;
2212 0 : block_id_ele->slot = snapshot_slot;
2213 0 : block_id_ele->bank_seq = bank->bank_seq;
2214 0 : block_id_ele->block_id_seen = 1;
2215 0 : block_id_ele->latest_fec_idx = 0U;
2216 0 : bank->f.block_id = manifest_block_id;
2217 :
2218 0 : if( !ctx->alpenglow ) {
2219 0 : FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, block_id_ele, ctx->block_id_arr ) );
2220 0 : } else {
2221 0 : block_id_ele->block_info = ag_block_id( snapshot_slot, manifest_block_id.uc );
2222 0 : FD_TEST( fd_ag_block_id_map_ele_insert( ctx->ag_block_id_map, block_id_ele, ctx->block_id_arr ) );
2223 0 : }
2224 :
2225 : /* We call this after fd_runtime_read_genesis, which sets up the
2226 : slot_bank needed in blockstore_init. */
2227 0 : init_after_snapshot( ctx, stem );
2228 :
2229 0 : if( FD_LIKELY( !ctx->alpenglow ) ) {
2230 0 : ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, snapshot_slot+1UL, ctx->identity_pubkey );
2231 0 : if( FD_LIKELY( ctx->next_leader_slot != ULONG_MAX ) ) {
2232 0 : double slot_duration_ticks = (double)bank->f.slot_params.ns_per_slot_adjusted*ctx->tick_per_ns;
2233 0 : ctx->next_leader_tickcount = (long)((double)(ctx->next_leader_slot-ctx->reset_slot-1UL)*slot_duration_ticks) + fd_tickcount();
2234 0 : } else {
2235 0 : ctx->next_leader_tickcount = LONG_MAX;
2236 0 : }
2237 0 : }
2238 :
2239 0 : fd_replay_slot_completed_t * slot_info = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
2240 0 : cost_tracker_snap( bank, slot_info );
2241 :
2242 0 : slot_info->identity_balance = fd_accdb_lamports( ctx->accdb, bank->accdb_fork_id, ctx->identity_pubkey->uc );
2243 :
2244 0 : if( ctx->reasm ) {
2245 0 : fd_reasm_fec_t * fec = fd_reasm_init( ctx->reasm, &manifest_block_id, snapshot_slot );
2246 0 : fec->bank_idx = (uint)bank->idx;
2247 0 : fec->bank_seq = bank->bank_seq;
2248 0 : }
2249 0 : store_xinsert( ctx->store, ctx->map_join, &manifest_block_id );
2250 :
2251 0 : long now = fd_log_wallclock();
2252 0 : FD_LOG_INFO(( "replay ready at slot %lu (%.3f s after snapshot done, %.3f s since boot)",
2253 0 : snapshot_slot, (double)(now-snapshot_done_nanos)/1e9, (double)(now-ctx->boot_timestamp_nanos)/1e9 ));
2254 :
2255 0 : publish_slot_completed( ctx, stem, bank, 1, 0 /* is_leader */, 0, 0 );
2256 0 : publish_root_advanced( ctx, stem, bank );
2257 0 : return;
2258 0 : }
2259 :
2260 0 : switch( msg ) {
2261 0 : case FD_SSMSG_MANIFEST_FULL:
2262 0 : case FD_SSMSG_MANIFEST_INCREMENTAL: {
2263 : /* We may either receive a full snapshot manifest or an
2264 : incremental snapshot manifest. Note that this external message
2265 : id is only used temporarily because replay cannot yet receive
2266 : the firedancer-internal snapshot manifest message. */
2267 0 : if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark ) )
2268 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 ));
2269 :
2270 : /* Malformed manifests are rejected recoverably by snapin via
2271 : fd_ssload_manifest_validate. If recover fails here, then the
2272 : bank is partially mutated, and we must abort. */
2273 0 : if( FD_UNLIKELY( fd_ssload_recover( fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ),
2274 0 : ctx->banks,
2275 0 : fd_banks_bank_query( ctx->banks, FD_REPLAY_BOOT_BANK_SEQ ),
2276 0 : ctx->blockhash_seed ) ) ) {
2277 0 : FD_LOG_ERR(( "Snapshot manifest recovery failed, aborting." ));
2278 0 : }
2279 :
2280 0 : ctx->has_cluster_type = 1;
2281 0 : ctx->cluster_type = fd_banks_bank_query( ctx->banks, FD_REPLAY_BOOT_BANK_SEQ )->f.cluster_type;
2282 :
2283 0 : fd_snapshot_manifest_t const * manifest = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
2284 : /* hard_fork_cnt already validated by fd_ssload_recover. */
2285 0 : ctx->hard_fork_cnt = manifest->hard_fork_cnt;
2286 0 : for( ulong i=0UL; i<manifest->hard_fork_cnt; i++ ) {
2287 0 : ctx->hard_forks[ i ] = manifest->hard_forks[ i ];
2288 0 : }
2289 0 : ctx->has_expected_genesis_timestamp = 1;
2290 0 : ctx->expected_genesis_timestamp = manifest->creation_time_seconds;
2291 0 : ctx->has_manifest_block_id = manifest->has_block_id;
2292 0 : if( manifest->has_block_id ) memcpy( ctx->manifest_block_id.uc, manifest->block_id, 32UL );
2293 0 : if( FD_UNLIKELY( msg==FD_SSMSG_MANIFEST_FULL ) ) {
2294 0 : ctx->snapmk.base_slot = manifest->slot;
2295 0 : }
2296 0 : break;
2297 0 : }
2298 0 : default: {
2299 0 : FD_LOG_ERR(( "Received unknown snapshot message with msg %lu", msg ));
2300 0 : return;
2301 0 : }
2302 0 : }
2303 :
2304 0 : return;
2305 0 : }
2306 :
2307 : static void
2308 : dispatch_task( fd_replay_tile_t * ctx,
2309 : fd_stem_context_t * stem,
2310 0 : fd_sched_task_t * task ) {
2311 :
2312 0 : switch( task->task_type ) {
2313 0 : case FD_SCHED_TT_TXN_EXEC: {
2314 0 : fd_txn_p_t * txn_p = fd_sched_get_txn( ctx->sched, task->txn_exec->txn_idx );
2315 :
2316 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->txn_exec->bank_idx );
2317 0 : FD_TEST( bank );
2318 :
2319 : /* Add the transaction to the block dumper if necessary. This
2320 : logic doesn't need to be fork-aware since it's only meant to
2321 : be used in backtest. */
2322 0 : if( FD_UNLIKELY( ctx->dump_proto_ctx && ctx->dump_proto_ctx->dump_block_to_pb ) ) {
2323 0 : fd_dump_block_to_protobuf_collect_tx( ctx->block_dump_ctx, txn_p );
2324 0 : }
2325 :
2326 0 : bank->refcnt++;
2327 :
2328 0 : if( FD_UNLIKELY( !bank->first_transaction_scheduled_nanos ) ) bank->first_transaction_scheduled_nanos = fd_clock_tile_now( ctx->clock );
2329 :
2330 0 : fd_replay_out_link_t * exec_out = ctx->exec_out;
2331 0 : fd_execrp_txn_exec_msg_t * exec_msg = fd_chunk_to_laddr( exec_out->mem, exec_out->chunk );
2332 0 : memcpy( exec_msg->txn, txn_p, sizeof(fd_txn_p_t) );
2333 0 : exec_msg->bank_idx = task->txn_exec->bank_idx;
2334 0 : exec_msg->txn_idx = task->txn_exec->txn_idx;
2335 0 : memcpy( exec_msg->fec_merkle_root, ctx->block_id_arr[ task->txn_exec->bank_idx ].latest_mr.uc, 32UL );
2336 0 : exec_msg->index_in_slot = fd_sched_get_txn_info( ctx->sched, task->txn_exec->txn_idx )->index_in_slot;
2337 0 : if( FD_UNLIKELY( ctx->capture_ctx ) ) {
2338 0 : exec_msg->capture_txn_idx = ctx->capture_ctx->current_txn_idx++;
2339 0 : }
2340 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() ) );
2341 0 : exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*exec_msg), exec_out->chunk0, exec_out->wmark );
2342 0 : break;
2343 0 : }
2344 0 : case FD_SCHED_TT_TXN_SIGVERIFY: {
2345 0 : fd_txn_p_t * txn_p = fd_sched_get_txn( ctx->sched, task->txn_sigverify->txn_idx );
2346 :
2347 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->txn_sigverify->bank_idx );
2348 0 : FD_TEST( bank );
2349 0 : bank->refcnt++;
2350 :
2351 0 : fd_replay_out_link_t * exec_out = ctx->exec_out;
2352 0 : fd_execrp_txn_sigverify_msg_t * exec_msg = fd_chunk_to_laddr( exec_out->mem, exec_out->chunk );
2353 0 : memcpy( exec_msg->txn, txn_p, sizeof(fd_txn_p_t) );
2354 0 : exec_msg->bank_idx = task->txn_sigverify->bank_idx;
2355 0 : exec_msg->txn_idx = task->txn_sigverify->txn_idx;
2356 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 );
2357 0 : exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*exec_msg), exec_out->chunk0, exec_out->wmark );
2358 0 : break;
2359 0 : };
2360 0 : case FD_SCHED_TT_POH_HASH: {
2361 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->poh_hash->bank_idx );
2362 0 : FD_TEST( bank );
2363 0 : bank->refcnt++;
2364 :
2365 0 : fd_replay_out_link_t * exec_out = ctx->exec_out;
2366 0 : fd_execrp_poh_hash_msg_t * exec_msg = fd_chunk_to_laddr( exec_out->mem, exec_out->chunk );
2367 0 : exec_msg->bank_idx = task->poh_hash->bank_idx;
2368 0 : exec_msg->cnt = task->poh_hash->cnt;
2369 0 : exec_msg->hashcnt = task->poh_hash->hashcnt;
2370 0 : FD_STATIC_ASSERT( FD_SCHED_POH_PARA==FD_EXECRP_POH_PARA, poh batch width mismatch );
2371 0 : memcpy( exec_msg->hash, task->poh_hash->hash, task->poh_hash->cnt*sizeof(fd_hash_t) );
2372 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 );
2373 0 : exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*exec_msg), exec_out->chunk0, exec_out->wmark );
2374 0 : break;
2375 0 : };
2376 0 : default: {
2377 0 : FD_LOG_CRIT(( "unexpected task type %lu", task->task_type ));
2378 0 : }
2379 0 : }
2380 0 : }
2381 :
2382 : static void
2383 : mark_bank_dead( fd_replay_tile_t * ctx,
2384 : fd_stem_context_t * stem,
2385 : ulong bank_idx,
2386 : int dead_reason,
2387 0 : int abandoned_reason ) {
2388 0 : ulong dead_idxs[ FD_BANKS_MAX_BANKS ];
2389 0 : ulong dead_idxs_cnt = 0UL;
2390 0 : fd_banks_mark_bank_dead( ctx->banks, bank_idx, dead_idxs, &dead_idxs_cnt );
2391 :
2392 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ bank_idx ];
2393 0 : if( block_id_ele->block_id_seen ) publish_slot_dead( ctx, stem, block_id_ele->slot, ctx->alpenglow ? &block_id_ele->dmr : &block_id_ele->latest_mr );
2394 :
2395 : /* Report each newly dead bank now (dead_idxs excludes already-dead,
2396 : already-reported subtrees): the failing bank with its real reason and
2397 : descendants as parent_dead, or the whole lineage with the caller's
2398 : abandoned flavor. Exactly one of the two reasons is set.
2399 : A previously PRUNABLE (evicted) bank converted by this walk reports
2400 : here like any other: eviction emits no row, and the sched-drain
2401 : emission is gated on the state still being PRUNABLE. Blocks still
2402 : receiving FECs dedup the slot-completion report via dead_reported. */
2403 0 : int abandoned = abandoned_reason!=FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED;
2404 0 : for( ulong i=0UL; i<dead_idxs_cnt; i++ ) {
2405 0 : fd_block_id_ele_t * ele = &ctx->block_id_arr[ dead_idxs[ i ] ];
2406 0 : fd_reasm_fec_t * fec = ctx->reasm ? fd_reasm_query( ctx->reasm, &ele->latest_mr ) : NULL;
2407 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, dead_idxs[ i ] );
2408 0 : if( FD_LIKELY( fec ) ) {
2409 0 : fec->bank_dead = (uchar)(abandoned ? 2U : 1U);
2410 0 : fec->dead_reported = 1;
2411 0 : }
2412 0 : timing_slot_release( ctx, dead_idxs[ i ] );
2413 0 : int dr = abandoned ? FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD
2414 0 : : dead_idxs[ i ]==bank_idx ? dead_reason
2415 0 : : FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_PARENT_DEAD;
2416 0 : fd_hash_t * block_id = ctx->alpenglow && ele->block_id_seen ? &ele->dmr : &ele->latest_mr;
2417 0 : report_block_incomplete( ctx, ele->slot, block_id, bank, dr, abandoned_reason );
2418 0 : }
2419 0 : }
2420 :
2421 : static int
2422 : try_replay( fd_replay_tile_t * ctx,
2423 0 : fd_stem_context_t * stem ) {
2424 :
2425 0 : if( FD_UNLIKELY( !ctx->is_booted ) ) return 0;
2426 :
2427 : /* Hold off executing until the computed shred version is known (except
2428 : in backtest), so footer certs verify under it. */
2429 0 : if( FD_UNLIKELY( ctx->alpenglow && !ctx->shred_version && !ctx->expected_shred_version ) ) return 0;
2430 :
2431 0 : int charge_busy = 0;
2432 0 : fd_sched_task_t task[ 1 ];
2433 0 : if( FD_UNLIKELY( !fd_sched_task_next_ready( ctx->sched, task ) ) ) {
2434 0 : return charge_busy; /* Nothing to execute or do. */
2435 0 : }
2436 :
2437 0 : charge_busy = 1;
2438 :
2439 0 : switch( task->task_type ) {
2440 0 : case FD_SCHED_TT_BLOCK_START: {
2441 0 : replay_block_start( ctx, task->block_start->bank_idx, task->block_start->parent_bank_idx, task->block_start->slot );
2442 0 : fd_sched_task_done( ctx->sched, FD_SCHED_TT_BLOCK_START, ULONG_MAX, ULONG_MAX, NULL );
2443 0 : break;
2444 0 : }
2445 0 : case FD_SCHED_TT_BLOCK_END: {
2446 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->block_end->bank_idx );
2447 0 : int dead = 0;
2448 0 : if( FD_LIKELY( bank->state==FD_BANK_STATE_REPLAYABLE ) ) dead = replay_block_finalize( ctx, stem, bank );
2449 0 : fd_sched_task_done( ctx->sched, FD_SCHED_TT_BLOCK_END, ULONG_MAX, ULONG_MAX, NULL );
2450 0 : if( FD_UNLIKELY( dead ) ) fd_sched_block_abandon( ctx->sched, bank->idx, FD_SCHED_ABANDON_INVALID );
2451 0 : break;
2452 0 : }
2453 0 : case FD_SCHED_TT_TXN_EXEC:
2454 0 : case FD_SCHED_TT_TXN_SIGVERIFY:
2455 0 : case FD_SCHED_TT_POH_HASH: {
2456 : /* Common case: we have a transaction we need to execute. */
2457 0 : dispatch_task( ctx, stem, task );
2458 0 : break;
2459 0 : }
2460 0 : case FD_SCHED_TT_MARK_DEAD: {
2461 0 : int dr = sched_block_dead_reason_to_event( ctx, task->mark_dead->bank_idx );
2462 0 : int ar = dr==FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD ? FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED
2463 0 : : FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED;
2464 0 : mark_bank_dead( ctx, stem, task->mark_dead->bank_idx, dr, ar );
2465 0 : break;
2466 0 : }
2467 0 : default: {
2468 0 : FD_LOG_CRIT(( "unexpected task type %lu", task->task_type ));
2469 0 : }
2470 0 : }
2471 :
2472 0 : return charge_busy;
2473 0 : }
2474 :
2475 : static int
2476 : can_process_fec( fd_replay_tile_t * ctx,
2477 0 : int * evict_banks_out ) {
2478 : /* We can process a FEC set if a few conditions are met:
2479 : - sched has capacity
2480 : - reasm has a FEC in its out queue ready to be processed
2481 : - banks has capacity. Evict if we don't (see below) */
2482 :
2483 0 : if( FD_UNLIKELY( fd_sched_can_ingest_cnt( ctx->sched )==0UL ) ) {
2484 0 : FD_TEST( !fd_sched_is_drained( ctx->sched ) );
2485 0 : ctx->metrics.sched_full++;
2486 0 : return 0;
2487 0 : }
2488 :
2489 0 : fd_reasm_fec_t * fec;
2490 0 : if( FD_UNLIKELY( (fec = fd_reasm_peek( ctx->reasm ))==NULL ) ) {
2491 0 : ctx->metrics.reasm_empty++;
2492 0 : return 0;
2493 0 : }
2494 :
2495 0 : fd_reasm_fec_t * parent = fd_reasm_parent( ctx->reasm, fec );
2496 0 : FD_TEST( parent ); /* FEC must be connected */
2497 :
2498 0 : ctx->metrics.reasm_latest_slot = fec->slot;
2499 0 : ctx->metrics.reasm_latest_fec_idx = fec->fec_set_idx;
2500 :
2501 : /* If the FEC we are building off of is for a prunable bank, we must
2502 : wait to process the FEC until the bank has been evicted. */
2503 0 : fd_bank_t * parent_fec_bank = parent->bank_idx==UINT_MAX ? NULL : fd_banks_bank_query( ctx->banks, parent->bank_idx );
2504 0 : if( FD_UNLIKELY( parent_fec_bank && parent_fec_bank->bank_seq==parent->bank_seq && parent_fec_bank->state==FD_BANK_STATE_PRUNABLE ) ) {
2505 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 ));
2506 0 : return 0;
2507 0 : }
2508 :
2509 0 : if( FD_UNLIKELY( ctx->is_leader &&
2510 0 : fec->fec_set_idx==0U &&
2511 0 : parent->bank_idx==ctx->leader_bank->idx &&
2512 0 : parent->bank_seq==ctx->leader_bank->bank_seq ) ) {
2513 : /* This guards against a rare race where we receive the FEC set for
2514 : the slot right after our leader rotation before we freeze the
2515 : bank for the last slot in our leader rotation. Leader slot
2516 : freezing happens only after if we've received the final PoH hash
2517 : from the poh tile as well as the final FEC set for the leader
2518 : slot. So the race happens when FEC sets are delivered and
2519 : processed sooner than the PoH hash, aka when the
2520 : poh=>shred=>replay path for the block id beats the poh=>replay
2521 : path for the poh hash. To mitigate this race, we must block on
2522 : ingesting the FEC set for the ensuing slot before the leader
2523 : bank freezes, because that would violate ordering invariants in
2524 : banks and sched. Both the bank index and sequence must match
2525 : because bank indices are recycled after eviction. */
2526 0 : FD_TEST( ctx->block_id_arr[ ctx->leader_bank->idx ].block_id_seen );
2527 0 : FD_TEST( !ctx->recv_poh );
2528 0 : ctx->metrics.leader_bid_wait++;
2529 0 : return 0;
2530 0 : }
2531 :
2532 : /* Should we evict banks if there are no more free banks? The answer
2533 : is it depends. Eviction should only happen if we can make no
2534 : forward replay progress. This can only happen if:
2535 : 1. banks are full
2536 : 2. sched is drained: pending txns could complete a block and
2537 : eventually advance the root.
2538 : AND
2539 : 3. next reasm FEC needs a new bank. A fec that chains off of a
2540 : bank that is already allocated can be processed. A FEC can
2541 : require a new bank in three ways:
2542 : - fec_set_idx==0: we don't have any free banks to provision a
2543 : new bank for this FEC.
2544 : - equivocation: a FEC may be in the middle of a block, but if
2545 : it's the first equivocating FEC detected, we need to allocate
2546 : a new bank for the version of the block.
2547 : - backfill: the parent FEC's bank was never created or has been
2548 : evicted and must be reconstructed. */
2549 :
2550 0 : int invalid_parent = !parent_fec_bank || parent_fec_bank->bank_seq!=parent->bank_seq;
2551 0 : if( FD_UNLIKELY( !fd_banks_can_start_bank( ctx->banks ) ) ) {
2552 0 : int is_new_block = fec->fec_set_idx==0U;
2553 0 : int is_eqvoc = fec->eqvoc && !parent->eqvoc;
2554 0 : if( FD_UNLIKELY( is_new_block || is_eqvoc || invalid_parent ) ) {
2555 0 : ctx->metrics.banks_full++;
2556 0 : if( FD_UNLIKELY( fd_sched_is_drained( ctx->sched ) ) ) *evict_banks_out = 1;
2557 0 : return 0;
2558 0 : }
2559 0 : }
2560 :
2561 : /* Otherwise, banks may not be full, so we can always create a new
2562 : bank if needed. Or, if banks are full, the current fec set's
2563 : ancestor (idx 0) already created a bank for this slot. */
2564 0 : return 1;
2565 0 : }
2566 :
2567 0 : #define PROCESS_FEC_WAIT 0 /* Put back on dcache for later retry */
2568 0 : #define PROCESS_FEC_OK 1 /* Process FEC set */
2569 0 : #define PROCESS_FEC_DROP 2 /* Drop FEC set and notify rotor to start redelivery */
2570 0 : #define PROCESS_FEC_SKIP 3 /* Good to skip this FEC set, drop from dcache */
2571 :
2572 : /* live_block_id_ele returns ele if it is backed by a live bank (same
2573 : bank_seq, not PRUNABLE), else NULL. Map entries outlive their banks
2574 : through eviction. TODO if we remove block_id_eles with banks, we
2575 : can get rid of this */
2576 : static fd_block_id_ele_t *
2577 : live_block_id_ele( fd_replay_tile_t * ctx,
2578 0 : fd_block_id_ele_t * ele ) {
2579 0 : if( FD_UNLIKELY( !ele ) ) return NULL;
2580 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, ele ) );
2581 0 : if( FD_UNLIKELY( !bank || bank->bank_seq!=ele->bank_seq || bank->state==FD_BANK_STATE_PRUNABLE ) ) return NULL;
2582 0 : return ele;
2583 0 : }
2584 :
2585 : static int
2586 : can_process_rotor_fec( fd_replay_tile_t * ctx,
2587 : fd_rotor_replay_fec_t * fec,
2588 0 : int * evict_banks_out ) {
2589 : /* We can process a FEC set if a few conditions are met:
2590 : - sched has capacity
2591 : - banks has capacity. Evict if we don't (see below) */
2592 :
2593 0 : if( FD_UNLIKELY( fd_sched_can_ingest_cnt( ctx->sched )==0UL ) ) {
2594 0 : FD_TEST( !fd_sched_is_drained( ctx->sched ) );
2595 0 : ctx->metrics.sched_full++;
2596 0 : return PROCESS_FEC_WAIT;
2597 0 : }
2598 :
2599 : /* Recovery redelivery re-publishes the entire ancestry path from the
2600 : chainer root, but most of those blocks are already replayed. A
2601 : fully replayed block is always keyed by {slot, block_id_real}; a
2602 : turbine block still in flight is keyed by {slot, 0}. Rotor fills
2603 : in block_id on redelivered FECs whenever it knows it, so try the id
2604 : key first and fall back to the slot key for a turbine version. */
2605 0 : {
2606 0 : fd_block_id_ele_t * self = NULL;
2607 0 : if( FD_LIKELY( !fd_hash_check_zero( &fec->block_id ) ) ) {
2608 0 : ag_block_id_t self_key = ag_block_id( fec->slot, fec->block_id.uc );
2609 0 : self = live_block_id_ele( ctx, fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &self_key, NULL, ctx->block_id_arr ) );
2610 0 : }
2611 0 : if( FD_LIKELY( !self && !fec->known_id ) ) {
2612 0 : ag_block_id_t self_key = { .slot = fec->slot };
2613 0 : self = live_block_id_ele( ctx, fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &self_key, NULL, ctx->block_id_arr ) );
2614 0 : }
2615 0 : if( FD_UNLIKELY( self && ( self->block_id_seen || self->latest_fec_idx>=fec->fec_set_idx ) ) ) return PROCESS_FEC_SKIP;
2616 0 : }
2617 :
2618 0 : ulong parent_bank_idx = UINT_MAX;
2619 0 : fd_block_id_ele_t * parent = NULL;
2620 0 : if( FD_UNLIKELY( fec->fec_set_idx==0 ) ) {
2621 0 : ag_block_id_t parent_key = ag_block_id( fec->parent_slot, fec->parent_block_id.uc );
2622 0 : parent = fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &parent_key, NULL, ctx->block_id_arr );
2623 0 : if( FD_UNLIKELY( !parent ) ) {
2624 0 : FD_BASE58_ENCODE_32_BYTES( fec->parent_block_id.uc, parent_key_b58 );
2625 0 : FD_LOG_INFO(( "parent bank not found for slot %lu fec set idx %u, parent slot %lu parent block_id %s", fec->slot, fec->fec_set_idx, fec->parent_slot, parent_key_b58 ));
2626 0 : return PROCESS_FEC_DROP; // either pruned or bank evicted
2627 0 : }
2628 0 : parent_bank_idx = fd_block_id_ele_get_idx( ctx->block_id_arr, parent );
2629 0 : } else {
2630 0 : ag_block_id_t key = { .slot = fec->slot };
2631 0 : if( FD_LIKELY( fec->known_id ) ) key = ag_block_id( fec->slot, fec->block_id.uc );
2632 :
2633 0 : parent = fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &key, NULL, ctx->block_id_arr );
2634 0 : if( FD_UNLIKELY( !parent ) ) {
2635 0 : FD_BASE58_ENCODE_32_BYTES( fec->block_id.uc, block_id_b58 );
2636 0 : FD_LOG_INFO(( "parent bank not found for slot %lu fec set idx %u slot_bid %s. parent slot %lu", fec->slot, fec->fec_set_idx, block_id_b58, fec->parent_slot ));
2637 0 : return PROCESS_FEC_DROP; // either pruned or bank evicted
2638 0 : }
2639 0 : parent_bank_idx = fd_block_id_ele_get_idx( ctx->block_id_arr, parent );
2640 0 : }
2641 :
2642 0 : ctx->metrics.reasm_latest_slot = fec->slot;
2643 0 : ctx->metrics.reasm_latest_fec_idx = fec->fec_set_idx;
2644 :
2645 : /* If the FEC we are building off of is for a prunable bank, we must
2646 : wait to process the FEC until the bank has been evicted. */
2647 0 : fd_bank_t * parent_fec_bank = parent_bank_idx==UINT_MAX ? NULL : fd_banks_bank_query( ctx->banks, parent_bank_idx );
2648 0 : if( FD_UNLIKELY( parent_fec_bank && parent_fec_bank->bank_seq==parent->bank_seq && parent_fec_bank->state==FD_BANK_STATE_PRUNABLE ) ) {
2649 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 ));
2650 0 : return PROCESS_FEC_WAIT;
2651 0 : }
2652 :
2653 0 : if( FD_UNLIKELY( ctx->is_leader &&
2654 0 : fec->fec_set_idx==0U &&
2655 0 : parent_bank_idx==ctx->leader_bank->idx &&
2656 0 : parent->bank_seq==ctx->leader_bank->bank_seq ) ) {
2657 : /* This guards against a rare race where we receive the FEC set for
2658 : the slot right after our leader rotation before we freeze the
2659 : bank for the last slot in our leader rotation. Leader slot
2660 : freezing happens only after if we've received the final PoH hash
2661 : from the poh tile as well as the final FEC set for the leader
2662 : slot. So the race happens when FEC sets are delivered and
2663 : processed sooner than the PoH hash, aka when the
2664 : poh=>shred=>replay path for the block id beats the poh=>replay
2665 : path for the poh hash. To mitigate this race, we must block on
2666 : ingesting the FEC set for the ensuing slot before the leader
2667 : bank freezes, because that would violate ordering invariants in
2668 : banks and sched. Both the bank index and sequence must match
2669 : because bank indices are recycled after eviction. */
2670 0 : FD_TEST( ctx->block_id_arr[ ctx->leader_bank->idx ].block_id_seen );
2671 0 : FD_TEST( !ctx->recv_poh );
2672 0 : ctx->metrics.leader_bid_wait++;
2673 0 : return PROCESS_FEC_WAIT;
2674 0 : }
2675 :
2676 : /* Should we evict banks if there are no more free banks? The answer
2677 : is it depends. Eviction should only happen if we can make no
2678 : forward replay progress. This can only happen if:
2679 : 1. banks are full
2680 : 2. sched is drained: pending txns could complete a block and
2681 : eventually advance the root.
2682 : AND
2683 : 3. next reasm FEC needs a new bank. A fec that chains off of a
2684 : bank that is already allocated can be processed. A FEC can
2685 : require a new bank in three ways:
2686 : - fec_set_idx==0: we don't have any free banks to provision a
2687 : new bank for this FEC.
2688 : - equivocation: a FEC may be in the middle of a block, but if
2689 : it's the first equivocating FEC detected, we need to allocate
2690 : a new bank for the version of the block.
2691 : - backfill: the parent FEC's bank was never created or has been
2692 : evicted and must be reconstructed.
2693 :
2694 :
2695 : - Without reasm there is no backfill: if the parent bank was
2696 : evicted or its bank idx was reused, this FEC can't be
2697 : processed, so drop it and notify rotor to redeliver.
2698 : - Rotor equivocating slots are guaranteed to be delivered starting
2699 : from FEC 0, so even equivocations mid-slot are delivered from
2700 : fec 0 */
2701 :
2702 0 : int invalid_parent = !parent_fec_bank || parent_fec_bank->bank_seq!=parent->bank_seq;
2703 0 : if( FD_UNLIKELY( invalid_parent ) ) {
2704 0 : FD_LOG_INFO(( "parent bank evicted for slot %lu fec set idx %u, parent slot %lu", fec->slot, fec->fec_set_idx, fec->parent_slot ));
2705 0 : return PROCESS_FEC_DROP;
2706 0 : } else if( FD_UNLIKELY( fec->fec_set_idx!=0U && parent->latest_fec_idx!=fec->fec_set_idx - FD_FEC_SHRED_CNT ) ) {
2707 : /* Similar to the very first condition in can_process_rotor_fec,
2708 : but this would hit if we were halfway through replaying a slot
2709 : and then requested redelivery from rotor. Then we can skip
2710 : replaying the first half of the slot. */
2711 0 : FD_LOG_INFO(( "fec redelivered for slot %lu fec set idx %u, parent slot %lu. bank_idx %lu, latest_fec_idx %u", fec->slot, fec->fec_set_idx, fec->parent_slot, fd_block_id_ele_get_idx( ctx->block_id_arr, parent ), parent->latest_fec_idx ));
2712 0 : return PROCESS_FEC_SKIP; // context for slot exists, but this is an earlier or non-contiguous FEC. Safe to skip.
2713 0 : }
2714 :
2715 0 : if( FD_UNLIKELY( !fd_banks_can_start_bank( ctx->banks ) ) ) {
2716 0 : int is_new_block = fec->fec_set_idx==0U;
2717 0 : if( FD_UNLIKELY( is_new_block ) ) {
2718 0 : ctx->metrics.banks_full++;
2719 0 : if( FD_UNLIKELY( fd_sched_is_drained( ctx->sched ) ) ) *evict_banks_out = 1;
2720 0 : return PROCESS_FEC_WAIT;
2721 0 : }
2722 0 : }
2723 :
2724 : /* Otherwise, banks may not be full, so we can always create a new
2725 : bank if needed. Or, if banks are full, the current fec set's
2726 : ancestor (idx 0) already created a bank for this slot. */
2727 0 : return PROCESS_FEC_OK;
2728 0 : }
2729 :
2730 : /* Returns 0 on successful FEC ingestion, 1 if the block got marked
2731 : dead. insert_fec_set assumes that all FECs that are inserted are
2732 : directly connected to a parent FEC. Every block that is replayed
2733 : has initial fec set idx 0 up to and including a FEC with
2734 : slot_complete set. The caller is responsible for ensuring this. */
2735 : static int
2736 : insert_fec_set( fd_replay_tile_t * ctx,
2737 : fd_stem_context_t * stem,
2738 0 : fd_reasm_fec_t * reasm_fec ) {
2739 :
2740 : /* First, read FEC set from the store. If it's not there that means
2741 : that the FEC is on a minority fork which has been pruned away.
2742 : This means we shouldn't have a bank for the corresponding block so
2743 : we should just ignore and discard the FEC set. */
2744 :
2745 0 : ulong work = (ulong)fd_clock_tile_now( ctx->clock );
2746 :
2747 0 : fd_store_fec_t * store_fec = fd_store_query( ctx->map_join, &reasm_fec->key );
2748 0 : ctx->metrics.store_query_cnt++;
2749 0 : ctx->metrics.store_query_mr = reasm_fec->key.ul[0];
2750 0 : if( FD_UNLIKELY( !store_fec && !reasm_fec->is_leader ) ) {
2751 : /* The only case in which a FEC is not found in the store is either
2752 : if the FEC is from our own leader block or after repair has
2753 : notified is if the FEC was on a minority fork that has already
2754 : been published away. In this case we abandon the entire slice
2755 : because it is no longer relevant. If the FEC is from our own
2756 : leader block, process the FEC so we can unbecome leader. */
2757 0 : ctx->metrics.store_query_missing_cnt++;
2758 0 : ctx->metrics.store_query_missing_mr = reasm_fec->key.ul[0];
2759 0 : FD_BASE58_ENCODE_32_BYTES( reasm_fec->key.key, key_b58 );
2760 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 ));
2761 0 : fd_histf_sample( ctx->metrics.store_query_work, (ulong)fd_clock_tile_now( ctx->clock ) - work );
2762 0 : return 1;
2763 0 : }
2764 :
2765 0 : long now = fd_clock_tile_now( ctx->clock );
2766 :
2767 : /* A leader FEC arriving after its slot was aborted (or after a later
2768 : leadership began) has no bank to bind to; drop it. */
2769 0 : if( FD_UNLIKELY( reasm_fec->is_leader &&
2770 0 : ( !ctx->leader_bank || ctx->leader_bank->f.slot!=reasm_fec->slot ) ) ) {
2771 0 : return 0;
2772 0 : }
2773 :
2774 : /* Assign parent bank idx + seq no to the FEC */
2775 0 : reasm_fec->parent_bank_idx = fd_reasm_parent( ctx->reasm, reasm_fec )->bank_idx;
2776 0 : fd_bank_t * parent_bank = fd_banks_bank_query( ctx->banks, reasm_fec->parent_bank_idx );
2777 :
2778 0 : if( FD_UNLIKELY( reasm_fec->fec_set_idx==0U ) ) {
2779 : /* Provision new bank if not leader. Assign bank idx and seq no
2780 : to the FEC. Remove stale block id map entry if any and update
2781 : pool element. */
2782 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 );
2783 :
2784 0 : if( FD_UNLIKELY( reasm_fec->is_leader && ctx->leader_stats.slot==reasm_fec->slot ) ) {
2785 0 : ctx->leader_stats.first_fec_returned_nanos = now;
2786 0 : }
2787 :
2788 0 : reasm_fec->bank_idx = (uint)bank->idx;
2789 0 : reasm_fec->bank_seq = bank->bank_seq;
2790 :
2791 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ reasm_fec->bank_idx ];
2792 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 ) ) {
2793 0 : FD_TEST( fd_block_id_map_ele_remove( ctx->block_id_map, &block_id_ele->latest_mr, NULL, ctx->block_id_arr ) );
2794 0 : }
2795 0 : block_id_ele->block_id_seen = 0;
2796 0 : block_id_ele->slot = reasm_fec->slot;
2797 0 : block_id_ele->bank_seq = bank->bank_seq;
2798 0 : block_id_ele->latest_fec_idx = 0U;
2799 0 : block_id_ele->latest_mr = reasm_fec->key;
2800 0 : block_id_ele->fec_cnt = 0U;
2801 0 : } else { /* FEC for the middle or end of a block */
2802 : /* Assign bank idx + seqno to the FEC. Update block id pool ele. */
2803 0 : reasm_fec->bank_idx = reasm_fec->parent_bank_idx;
2804 0 : reasm_fec->bank_seq = parent_bank->bank_seq;
2805 :
2806 0 : FD_TEST( reasm_fec->bank_idx!=UINT_MAX );
2807 :
2808 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ reasm_fec->bank_idx ];
2809 0 : block_id_ele->latest_fec_idx = reasm_fec->fec_set_idx;
2810 0 : block_id_ele->latest_mr = reasm_fec->key;
2811 0 : }
2812 :
2813 0 : if( FD_UNLIKELY( ctx->report_runtime_diffs ) ) {
2814 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ reasm_fec->bank_idx ];
2815 0 : if( FD_LIKELY( block_id_ele->fec_cnt<FD_FEC_BLK_MAX ) ) {
2816 0 : ctx->fec_chain[ reasm_fec->bank_idx*FD_FEC_BLK_MAX + block_id_ele->fec_cnt ] = reasm_fec->key;
2817 0 : }
2818 0 : block_id_ele->fec_cnt++;
2819 0 : }
2820 :
2821 : /* If the FEC set is a slot complete, this means we have finally seen
2822 : the block id (block's last mr). */
2823 0 : if( FD_UNLIKELY( reasm_fec->slot_complete ) ) {
2824 0 : fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ reasm_fec->bank_idx ];
2825 0 : block_id_ele->block_id_seen = 1;
2826 0 : block_id_ele->latest_mr = reasm_fec->key;
2827 0 : block_id_ele->latest_fec_idx = reasm_fec->fec_set_idx;
2828 : /* If we are re-replaying a block, we want to remove the first
2829 : version of the block that we have presumably evicted. */
2830 0 : if( FD_UNLIKELY( fd_block_id_map_ele_remove( ctx->block_id_map, &block_id_ele->latest_mr, NULL, ctx->block_id_arr ) ) ) {
2831 0 : FD_LOG_DEBUG(( "finished re-replaying evicted bank (slot=%lu, bank_idx=%u)", reasm_fec->slot, reasm_fec->bank_idx ));
2832 0 : }
2833 0 : FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, block_id_ele, ctx->block_id_arr ) );
2834 0 : }
2835 :
2836 : /* For leader FECs, don't insert the FEC into the scheduler. */
2837 0 : if( FD_UNLIKELY( reasm_fec->is_leader ) ) {
2838 0 : return 0;
2839 0 : }
2840 :
2841 : /* Forks form a partial ordering over FEC sets. The Repair tile
2842 : delivers FEC sets in-order per fork, but FEC set ordering across
2843 : forks is arbitrary */
2844 0 : fd_sched_fec_t sched_fec[ 1 ];
2845 :
2846 : # if DEBUG_LOGGING
2847 : FD_BASE58_ENCODE_32_BYTES( reasm_fec->key.key, key_b58 );
2848 : FD_BASE58_ENCODE_32_BYTES( reasm_fec->cmr.key, cmr_b58 );
2849 : 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 ));
2850 : # endif
2851 :
2852 0 : sched_fec->shred_cnt = reasm_fec->data_cnt;
2853 0 : sched_fec->is_last_in_batch = !!reasm_fec->data_complete;
2854 0 : sched_fec->is_last_in_block = !!reasm_fec->slot_complete;
2855 0 : sched_fec->bank_idx = reasm_fec->bank_idx;
2856 0 : sched_fec->parent_bank_idx = reasm_fec->parent_bank_idx;
2857 0 : sched_fec->slot = reasm_fec->slot;
2858 0 : sched_fec->parent_slot = reasm_fec->slot - reasm_fec->parent_off;
2859 0 : sched_fec->is_first_in_block = reasm_fec->fec_set_idx==0U;
2860 0 : sched_fec->fec = store_fec;
2861 0 : fd_store_fec_data_view_t data_view[ 1 ];
2862 0 : FD_TEST( !fd_store_fec_data_view( ctx->store, ctx->store_disk_fd, store_fec, data_view ) );
2863 :
2864 0 : sched_fec->data = data_view->data;
2865 0 : sched_fec->completed_ns = (long)reasm_fec->fec_completed_ts_nanos;
2866 0 : sched_fec->alut_ctx->fork_id = fd_banks_bank_query( ctx->banks, ctx->published_root_bank_idx )->accdb_fork_id;
2867 0 : sched_fec->alut_ctx->accdb = ctx->accdb;
2868 0 : sched_fec->alut_ctx->els = ctx->published_root_slot;
2869 :
2870 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, sched_fec->bank_idx );
2871 0 : if( sched_fec->is_first_in_block ) {
2872 0 : bank->refcnt++;
2873 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for sched", bank->idx, sched_fec->slot, bank->refcnt ));
2874 0 : }
2875 :
2876 0 : int ingested = fd_sched_fec_ingest( ctx->sched, sched_fec );
2877 0 : fd_store_fec_data_view_release( ctx->store, data_view );
2878 :
2879 0 : if( FD_UNLIKELY( !ingested ) ) {
2880 0 : int dr = sched_block_dead_reason_to_event( ctx, sched_fec->bank_idx );
2881 0 : int ar = dr==FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD ? FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED
2882 0 : : FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED;
2883 0 : mark_bank_dead( ctx, stem, sched_fec->bank_idx, dr, ar );
2884 0 : return 1;
2885 0 : }
2886 :
2887 0 : fd_histf_sample( ctx->metrics.store_query_work, (ulong)fd_clock_tile_now( ctx->clock ) - work );
2888 0 : return 0;
2889 0 : }
2890 :
2891 : static void
2892 : backfill_fec_sets( fd_replay_tile_t * ctx,
2893 : fd_stem_context_t * stem,
2894 0 : fd_reasm_fec_t * reasm_fec ) {
2895 0 : fd_reasm_fec_t * parent = fd_reasm_parent( ctx->reasm, reasm_fec );
2896 0 : FD_TEST( !!parent );
2897 :
2898 0 : fd_reasm_fec_t ** path = ctx->backfill_path;
2899 0 : ulong path_max = ctx->max_shreds_per_block/FD_FEC_SHRED_CNT;
2900 0 : ulong path_cnt = 0UL;
2901 0 : ulong path_slot = reasm_fec->slot;
2902 :
2903 : /* Walk backward from the candidate FEC until we find one with an
2904 : associated bank that we consider 'valid'. A FEC is considered
2905 : valid to backfill off of if the bank matches the seq we expect and
2906 : if its latest mr matches. We must check the latest MR in the case
2907 : of equivocation. */
2908 0 : fd_bank_t * base_bank = NULL;
2909 0 : fd_reasm_fec_t * base_fec = NULL;
2910 0 : for( fd_reasm_fec_t * curr = reasm_fec;; ) {
2911 0 : fd_bank_t * curr_bank = curr->bank_idx==UINT_MAX ? NULL : fd_banks_bank_query( ctx->banks, curr->bank_idx );
2912 0 : fd_block_id_ele_t * block_id_ele = curr_bank ? &ctx->block_id_arr[ curr_bank->idx ] : NULL;
2913 0 : if( FD_LIKELY( curr_bank &&
2914 0 : curr_bank->bank_seq==curr->bank_seq &&
2915 0 : curr_bank->state!=FD_BANK_STATE_PRUNABLE &&
2916 0 : block_id_ele->bank_seq==curr->bank_seq &&
2917 0 : fd_hash_eq( &block_id_ele->latest_mr, &curr->key ) ) ) { base_bank = curr_bank; base_fec = curr; break; }
2918 :
2919 0 : if( FD_UNLIKELY( curr->slot!=path_slot ) ) {
2920 0 : path_cnt = 0UL;
2921 0 : path_slot = curr->slot;
2922 0 : }
2923 :
2924 0 : FD_TEST( path_cnt<path_max );
2925 0 : path[ path_cnt++ ] = curr;
2926 :
2927 0 : curr = fd_reasm_parent( ctx->reasm, curr );
2928 0 : FD_TEST( curr );
2929 0 : }
2930 :
2931 0 : if( FD_UNLIKELY( base_bank->state==FD_BANK_STATE_DEAD ) ) {
2932 0 : uchar bank_dead = fd_uchar_if( base_fec->bank_dead==2U, 2U, 1U );
2933 0 : for( ulong i=0UL; i<path_cnt; i++ ) {
2934 0 : path[ i ]->bank_dead = bank_dead;
2935 0 : path[ i ]->dead_reported = 0UL; /* new version: no row yet */
2936 0 : }
2937 0 : reasm_fec->bank_dead = bank_dead;
2938 0 : if( FD_UNLIKELY( reasm_fec->slot_complete ) ) {
2939 0 : publish_slot_dead( ctx, stem, reasm_fec->slot, &reasm_fec->key );
2940 0 : int abandoned = bank_dead==2U;
2941 0 : report_block_incomplete( ctx, reasm_fec->slot, &reasm_fec->key, NULL,
2942 0 : abandoned ? FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD : FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_PARENT_DEAD,
2943 0 : abandoned ? FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED : FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED );
2944 0 : reasm_fec->dead_reported = 1UL;
2945 0 : }
2946 0 : return;
2947 0 : }
2948 :
2949 : /* Now that we have queued up the potential path of FECs to backfill,
2950 : ingest as much as sched can allow. */
2951 0 : fd_reasm_fec_t * first = path[ path_cnt-1UL ];
2952 0 : fd_reasm_fec_t * last = path[ 0 ];
2953 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 ));
2954 :
2955 0 : ulong sched_capacity = fd_sched_can_ingest_cnt( ctx->sched );
2956 0 : ulong path_idx_min = path_cnt - fd_ulong_min( sched_capacity, path_cnt );
2957 0 : for( ulong i=path_cnt; i>path_idx_min; i-- ) {
2958 0 : if( FD_UNLIKELY( insert_fec_set( ctx, stem, path[ i-1UL ] ) ) ) return;
2959 0 : }
2960 0 : }
2961 :
2962 : static void
2963 : process_fec_set( fd_replay_tile_t * ctx,
2964 : fd_stem_context_t * stem,
2965 0 : fd_reasm_fec_t * reasm_fec ) {
2966 :
2967 0 : fd_reasm_fec_t * parent = fd_reasm_parent( ctx->reasm, reasm_fec );
2968 0 : if( FD_UNLIKELY( parent->bank_dead ) ) {
2969 : /* Inherit the dead flag from the parent (1: dead lineage, 2:
2970 : abandoned lineage). If a dead slot is completed, we publish the
2971 : slot as dead. Don't insert FECs for dead slots. */
2972 0 : reasm_fec->bank_dead = parent->bank_dead;
2973 0 : reasm_fec->dead_reported = ( parent->slot==reasm_fec->slot && reasm_fec->xid_next==UINT_MAX )
2974 0 : ? parent->dead_reported : 0UL;
2975 0 : if( FD_UNLIKELY( reasm_fec->slot_complete ) ) {
2976 0 : publish_slot_dead( ctx, stem, reasm_fec->slot, &reasm_fec->key );
2977 0 : if( !reasm_fec->dead_reported ) {
2978 0 : int abandoned = reasm_fec->bank_dead==2UL;
2979 0 : report_block_incomplete( ctx, reasm_fec->slot, &reasm_fec->key, NULL,
2980 0 : abandoned ? FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD : FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_PARENT_DEAD,
2981 0 : abandoned ? FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED : FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED );
2982 0 : }
2983 0 : }
2984 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 ));
2985 0 : return;
2986 0 : }
2987 :
2988 : /* An invariant from reasm is that if we receive a FEC set that is
2989 : both with eqvoc and confirmed set, we know that we must replay the
2990 : slot associated with this FEC. equivocation when fec_set_idx == 0
2991 : gets handled cleanly. */
2992 0 : int eqvoc_detected = reasm_fec->fec_set_idx!=0 && (reasm_fec->eqvoc && !parent->eqvoc);
2993 0 : if( FD_UNLIKELY( eqvoc_detected ) ) FD_TEST( reasm_fec->confirmed && parent->confirmed );
2994 :
2995 : /* We can detect if a bank has not replayed if the bank index tagged
2996 : to the FEC set is no longer valid or the bank sequence number for
2997 : the same bank is different (the bank has been recycled). This is
2998 : either due to the parent bank being evicted, or in reasm, the
2999 : parent is marked eqvoc (and not replayed), but the child gets
3000 : confirmed and delivered. */
3001 0 : fd_bank_t * parent_fec_bank = parent->bank_idx==UINT_MAX ? NULL : fd_banks_bank_query( ctx->banks, parent->bank_idx );
3002 0 : int parent_bank_invalid = !parent_fec_bank || parent_fec_bank->bank_seq!=parent->bank_seq;
3003 :
3004 : /* If the upcoming FEC is either the start of an equivocating chain,
3005 : chains off of a bank that was evicted, OR is the child of an
3006 : equivocating chain whose parent was gated from getting replayed, we
3007 : must backfill any FECs into the scheduler. This backfill must
3008 : start from a FEC with fec_set_idx==0 with a parent FEC
3009 : corresponding to a valid bank. */
3010 0 : if( FD_LIKELY( !parent_bank_invalid && !eqvoc_detected ) ) {
3011 0 : insert_fec_set( ctx, stem, reasm_fec );
3012 0 : } else {
3013 0 : backfill_fec_sets( ctx, stem, reasm_fec );
3014 0 : }
3015 0 : }
3016 :
3017 : static int
3018 : try_notify_consensus_root( fd_replay_tile_t * ctx,
3019 0 : fd_stem_context_t * stem ) {
3020 :
3021 : /* Other tiles cannot prune while we are leader - replay may still be
3022 : waiting for the remaining FEC sets of our own slots to be
3023 : delivered, and if the upstream tiles prune too early, they may fail
3024 : to forward our own leader FECs. Then we would be permanently stuck
3025 : in the leader state. */
3026 0 : if( FD_UNLIKELY( ctx->is_leader ) ) return 0;
3027 :
3028 0 : if( FD_LIKELY( ctx->notified_root_slot==ctx->consensus_root_slot &&
3029 0 : fd_hash_eq( &ctx->notified_root, &ctx->consensus_root ) ) ) return 0;
3030 :
3031 0 : fd_block_id_ele_t * block_id_ele = fd_block_id_ele_query( ctx, &ctx->consensus_root, ctx->consensus_root_slot );
3032 0 : if( FD_UNLIKELY( !block_id_ele ) ) return 0;
3033 :
3034 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) );
3035 0 : if( FD_UNLIKELY( !bank ||
3036 0 : bank->bank_seq!=block_id_ele->bank_seq ||
3037 0 : !fd_hash_eq( &bank->f.block_id, &ctx->consensus_root ) ||
3038 0 : bank->state==FD_BANK_STATE_PRUNABLE ) ) return 0;
3039 :
3040 0 : fd_sched_root_notify( ctx->sched, bank->idx );
3041 0 : publish_root_advanced( ctx, stem, bank );
3042 :
3043 0 : ctx->notified_root = ctx->consensus_root;
3044 0 : ctx->notified_root_slot = ctx->consensus_root_slot;
3045 0 : ctx->notified_root_bank = bank;
3046 0 : return 1;
3047 0 : }
3048 :
3049 : /* Periodic snapshots use block height, matching Agave. Manually
3050 : scheduled snapshots remain slot based. */
3051 :
3052 : static int
3053 : snapshot_due_for_root( fd_replay_tile_t * ctx,
3054 : ulong published_root_block_height,
3055 : ulong advanceable_root_slot,
3056 : ulong advanceable_root_block_height,
3057 : ulong consensus_root_block_height,
3058 0 : int * out_incremental ) {
3059 0 : *out_incremental = 0;
3060 0 : if( FD_LIKELY( !ctx->snapmk.supported || ctx->snapmk.active ) ) return 0;
3061 :
3062 0 : int caught_up = ctx->caught_up; /* suspend periodic snaps until caught up */
3063 0 : ulong full_target = ULONG_MAX;
3064 0 : ulong interval = ctx->snapmk.full_interval_blocks;
3065 0 : if( FD_UNLIKELY( interval && caught_up ) ) {
3066 0 : if( FD_UNLIKELY( ctx->snapmk.next_full_block_height==ULONG_MAX ) ) {
3067 0 : ctx->snapmk.next_full_block_height = ((published_root_block_height/interval)+1UL)*interval;
3068 0 : }
3069 :
3070 : /* If snapshot production fell behind by more than one interval,
3071 : skip ahead to the latest due interval. */
3072 0 : full_target = fd_ulong_max( ctx->snapmk.next_full_block_height,
3073 0 : (consensus_root_block_height/interval)*interval );
3074 0 : }
3075 :
3076 : /* Manual slot scheduling always requests a full snapshot. */
3077 0 : if( FD_UNLIKELY( advanceable_root_slot>=ctx->snapmk.scheduled_at_slot ) ) return 1;
3078 :
3079 : /* An incremental snapshot is only possible once a full snapshot
3080 : exists to serve as its base. */
3081 0 : ulong incremental_target = ULONG_MAX;
3082 0 : ulong incr_interval = ctx->snapmk.incremental_interval_blocks;
3083 0 : if( FD_UNLIKELY( incr_interval && caught_up && ctx->snapmk.base_slot!=ULONG_MAX ) ) {
3084 0 : if( FD_UNLIKELY( ctx->snapmk.next_incremental_block_height==ULONG_MAX ) ) {
3085 0 : ctx->snapmk.next_incremental_block_height = ((published_root_block_height/incr_interval)+1UL)*incr_interval;
3086 0 : }
3087 0 : incremental_target = fd_ulong_max( ctx->snapmk.next_incremental_block_height,
3088 0 : (consensus_root_block_height/incr_interval)*incr_interval );
3089 0 : }
3090 :
3091 : /* A full snapshot due at the same block height supersedes the
3092 : incremental snapshot. */
3093 0 : ulong target = full_target;
3094 0 : int incremental = incremental_target<full_target;
3095 0 : if( FD_UNLIKELY( incremental ) ) target = incremental_target;
3096 0 : if( FD_LIKELY( advanceable_root_block_height<target ) ) return 0;
3097 0 : *out_incremental = incremental;
3098 0 : return 1;
3099 0 : }
3100 :
3101 : static void
3102 : snapmk_start( fd_replay_tile_t * ctx,
3103 : fd_stem_context_t * stem,
3104 : int incremental );
3105 :
3106 : static int
3107 : try_advance_published_root( fd_replay_tile_t * ctx,
3108 0 : fd_stem_context_t * stem ) {
3109 :
3110 0 : if( FD_LIKELY( ctx->published_root_slot==ctx->consensus_root_slot ) ) return 0;
3111 :
3112 : /* accdb pauses advance_root while producing a snapshot, so submitting
3113 : one would stall the next wait_cmd until the snapshot completes. */
3114 0 : if( FD_UNLIKELY( ctx->snapmk.active ) ) return 0;
3115 :
3116 : /* If the new root is not available because the bank is/has been
3117 : evicted, we can't advance the root. Try again later. */
3118 :
3119 0 : fd_block_id_ele_t * block_id_ele = fd_block_id_ele_query( ctx, &ctx->consensus_root, ctx->consensus_root_slot );
3120 0 : if( FD_UNLIKELY( !block_id_ele ) ) return 0;
3121 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 ) );
3122 0 : if( FD_UNLIKELY( !target_bank ||
3123 0 : target_bank->bank_seq!=block_id_ele->bank_seq ||
3124 0 : !fd_hash_eq( &target_bank->f.block_id, &ctx->consensus_root ) ||
3125 0 : target_bank->state==FD_BANK_STATE_PRUNABLE ) ) {
3126 0 : return 0;
3127 0 : }
3128 :
3129 0 : fd_bank_t * published_root_bank = fd_banks_bank_query( ctx->banks, ctx->published_root_bank_idx );
3130 0 : FD_TEST( published_root_bank );
3131 0 : ulong published_root_block_height = published_root_bank->f.block_height;
3132 0 : ulong consensus_root_block_height = target_bank->f.block_height;
3133 :
3134 : /* If the identity vote has been seen on a bank that should be rooted,
3135 : then we are now ready to produce blocks. */
3136 0 : if( FD_UNLIKELY( !ctx->identity_vote_rooted ) ) {
3137 0 : if( target_bank->f.identity_vote_idx==ctx->identity_idx ) ctx->identity_vote_rooted = 1;
3138 0 : }
3139 :
3140 0 : ulong advanceable_root_idx = ULONG_MAX;
3141 0 : if( FD_UNLIKELY( !fd_banks_advance_root_prepare( ctx->banks, target_bank->idx, &advanceable_root_idx ) ) ) {
3142 0 : ctx->metrics.storage_root_behind++;
3143 0 : return 0;
3144 0 : }
3145 :
3146 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, advanceable_root_idx );
3147 0 : FD_TEST( bank );
3148 :
3149 0 : if( FD_UNLIKELY( advanceable_root_idx>=ctx->block_id_len ) ) {
3150 0 : FD_LOG_CRIT(( "invariant violation: advanceable root ele out of bounds [0, %lu) index %lu", ctx->block_id_len, advanceable_root_idx ));
3151 0 : }
3152 0 : fd_block_id_ele_t * advanceable_root_ele = &ctx->block_id_arr[ advanceable_root_idx ];
3153 :
3154 : /* Telemetry: banks still mid-replay (started but never completed) on
3155 : forks not descending from the new root are about to be cancelled by
3156 : fd_banks_advance_root without ever emitting a block_completed row.
3157 : Emit them as abandoned with reason `pruned`: the block was not
3158 : invalid, it lost the fork race. Every live bank is linked in the
3159 : bank tree under the current root, so one preorder pass that skips
3160 : the new root's subtree visits exactly the banks about to go. */
3161 0 : if( FD_UNLIKELY( fd_event_tl ) ) {
3162 0 : ulong root_idx = fd_banks_root( ctx->banks )->idx;
3163 0 : ulong s = root_idx;
3164 0 : fd_bank_t * b = fd_banks_root( ctx->banks );
3165 0 : for(;;) {
3166 0 : if( FD_LIKELY( s!=advanceable_root_idx ) ) {
3167 0 : if( FD_UNLIKELY( b->state==FD_BANK_STATE_INIT || b->state==FD_BANK_STATE_REPLAYABLE ) ) {
3168 0 : fd_block_id_ele_t * ele = &ctx->block_id_arr[ s ];
3169 0 : report_block_incomplete( ctx, ele->slot, ctx->alpenglow && ele->block_id_seen ? &ele->dmr : &ele->latest_mr, b, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED );
3170 0 : }
3171 0 : if( FD_LIKELY( b->child_idx!=ULONG_MAX ) ) { s = b->child_idx; b = fd_banks_bank_query( ctx->banks, s ); continue; }
3172 0 : }
3173 0 : while( s!=root_idx && b->sibling_idx==ULONG_MAX ) { s = b->parent_idx; b = fd_banks_bank_query( ctx->banks, s ); }
3174 0 : if( s==root_idx ) break;
3175 0 : s = b->sibling_idx; b = fd_banks_bank_query( ctx->banks, s );
3176 0 : }
3177 0 : }
3178 :
3179 0 : ulong advanceable_root_slot = bank->f.slot;
3180 0 : fd_txncache_advance_root( ctx->txncache, bank->txncache_fork_id );
3181 0 : fd_progcache_advance_root( ctx->progcache, bank->progcache_fork_id );
3182 0 : fd_accdb_advance_root( ctx->accdb, bank->accdb_fork_id );
3183 0 : fd_sched_advance_root( ctx->sched, advanceable_root_idx );
3184 0 : fd_banks_advance_root( ctx->banks, advanceable_root_idx );
3185 0 : if( ctx->reasm ) fd_reasm_publish( ctx->reasm, &advanceable_root_ele->latest_mr, ctx->store, ctx->map_join );
3186 :
3187 0 : for( ulong b=0UL; b<ctx->max_live_slots; b++ ) {
3188 0 : if( FD_UNLIKELY( ctx->timing_slot_of_bank[ b ]!=fd_timing_slot_pool_idx_null( ctx->timing_slot_pool ) && !fd_banks_bank_query( ctx->banks, b ) ) ) timing_slot_release( ctx, b );
3189 0 : }
3190 :
3191 0 : int snap_incremental;
3192 0 : int snap_due = snapshot_due_for_root( ctx,
3193 0 : published_root_block_height,
3194 0 : advanceable_root_slot,
3195 0 : bank->f.block_height,
3196 0 : consensus_root_block_height,
3197 0 : &snap_incremental );
3198 :
3199 0 : ctx->published_root_slot = advanceable_root_slot;
3200 0 : ctx->published_root_bank_idx = advanceable_root_idx;
3201 :
3202 0 : if( FD_UNLIKELY( snap_due ) ) {
3203 0 : snapmk_start( ctx, stem, snap_incremental );
3204 0 : if( FD_UNLIKELY( !snap_incremental ) ) {
3205 0 : if( FD_UNLIKELY( ctx->snapmk.full_interval_blocks &&
3206 0 : bank->f.block_height>=ctx->snapmk.next_full_block_height ) ) {
3207 0 : ulong interval = ctx->snapmk.full_interval_blocks;
3208 0 : ctx->snapmk.next_full_block_height = ((bank->f.block_height/interval)+1UL)*interval;
3209 0 : }
3210 0 : if( FD_UNLIKELY( advanceable_root_slot>=ctx->snapmk.scheduled_at_slot ) ) {
3211 0 : ctx->snapmk.scheduled_at_slot = ULONG_MAX;
3212 0 : }
3213 0 : }
3214 0 : if( FD_UNLIKELY( ctx->snapmk.incremental_interval_blocks ) ) {
3215 0 : ulong interval = ctx->snapmk.incremental_interval_blocks;
3216 0 : ctx->snapmk.next_incremental_block_height = ((bank->f.block_height/interval)+1UL)*interval;
3217 0 : }
3218 0 : }
3219 :
3220 0 : return 1;
3221 0 : }
3222 :
3223 : static int
3224 0 : try_prune_sched( fd_replay_tile_t * ctx ) {
3225 0 : ulong bank_idx;
3226 0 : int pruned = 0;
3227 0 : while( (bank_idx=fd_sched_pruned_block_next( ctx->sched ) )!=ULONG_MAX ) {
3228 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, bank_idx );
3229 0 : FD_TEST( bank );
3230 : /* An evicted bank drains here with its verdict final: every
3231 : in-flight task has resolved, so still PRUNABLE means none ruled
3232 : it invalid (that would have converted it to DEAD and reported a
3233 : dead row), and this is the block's only row. A frozen victim
3234 : already reported a completed row. */
3235 0 : if( FD_UNLIKELY( bank->state==FD_BANK_STATE_PRUNABLE && !bank->block_completed_nanos ) ) {
3236 0 : fd_block_id_ele_t * ele = &ctx->block_id_arr[ bank_idx ];
3237 0 : report_block_incomplete( ctx, ele->slot, ctx->alpenglow && ele->block_id_seen ? &ele->dmr : &ele->latest_mr, bank,
3238 0 : FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD,
3239 0 : FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_EVICTED );
3240 0 : }
3241 0 : bank->refcnt--;
3242 0 : FD_LOG_DEBUG(( "bank (idx=%lu) refcnt decremented to %lu for sched", bank->idx, bank->refcnt ));
3243 0 : pruned = 1;
3244 0 : }
3245 0 : return pruned;
3246 0 : }
3247 :
3248 : static int
3249 0 : try_prune_bank( fd_replay_tile_t * ctx ) {
3250 0 : fd_banks_prune_cancel_info_t cancel_info[ 1 ];
3251 :
3252 0 : int pruned = fd_banks_prune_one_bank( ctx->banks, cancel_info );
3253 0 : switch( pruned ) {
3254 0 : case 2: { /* pruning bank + cancellation is needed */
3255 0 : fd_txncache_cancel_fork( ctx->txncache, cancel_info->txncache_fork_id );
3256 0 : fd_progcache_cancel_fork( ctx->progcache, cancel_info->progcache_fork_id );
3257 0 : fd_accdb_purge( ctx->accdb, cancel_info->accdb_fork_id );
3258 0 : __attribute__((fallthrough));
3259 0 : }
3260 0 : case 1: { /* pruning bank + no cancellation is needed */
3261 : /* A sched block exists, and can be marked dead, for a bank as
3262 : soon as its first FEC has been ingested, which can happen
3263 : before the bank ever set itself up for actual execution (e.g. a
3264 : block that parses as bad on its very first FEC). So always
3265 : instruct sched to prune the block whenever banks prunes the
3266 : bank. The txncache/progcache/accdb forks, on the other hand,
3267 : are only created once the bank started actual execution. */
3268 0 : fd_sched_cancel( ctx->sched, cancel_info->bank_idx );
3269 0 : timing_slot_release( ctx, cancel_info->bank_idx );
3270 0 : return 1;
3271 0 : }
3272 0 : case 0: /* no bank to prune */
3273 0 : return 0;
3274 0 : default:
3275 0 : FD_LOG_ERR(( "unreachable" ));
3276 0 : }
3277 0 : }
3278 :
3279 : static int
3280 : try_evict_reasm( fd_replay_tile_t * ctx,
3281 0 : fd_stem_context_t * stem ) {
3282 :
3283 : /* if reasm_evicted is set, publish starting from reasm_evicted down
3284 : to the leaf node to repair so repair can re-request for it.
3285 : reasm_evicted gets set when reasm tries to insert a FEC and there
3286 : is no remaining capacity. */
3287 0 : if( FD_LIKELY( !ctx->reasm_evicted ) ) return 0;
3288 :
3289 : /* Publish a notification to the repair tile that the Replay tile no
3290 : longer has the FEC that was evicted. This will make sure that the
3291 : repair tile will re-request the FEC if it eventually gets
3292 : confirmed so that Replay can still make forward progress. */
3293 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 };
3294 0 : fd_memcpy( fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk ), &evicted, sizeof(fd_replay_fec_evicted_t) );
3295 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() ) );
3296 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 );
3297 :
3298 : /* eviction policy only evicts chains of nodes until there is a
3299 : fork, so guaranteed that the evict path is always the left-child
3300 : TODO: This should be abstracted away. */
3301 0 : fd_reasm_pool_release( ctx->reasm, ctx->reasm_evicted );
3302 0 : ctx->reasm_evicted = fd_reasm_child( ctx->reasm, ctx->reasm_evicted ); /* indexes into pool, safe to use */
3303 0 : return 1;
3304 0 : }
3305 :
3306 : static int
3307 : try_process_fec( fd_replay_tile_t * ctx,
3308 0 : fd_stem_context_t * stem ) {
3309 :
3310 : /* If the reassembler has a fec that is ready, we should process it
3311 : and pass it to the scheduler.
3312 :
3313 : We would also like to pace FEC ingestion such that we keep the exec
3314 : tiles busy. If there's a pending frag from one of the exec tiles,
3315 : we would like to know about that asap, because that could unblock
3316 : dispatching. So we ingest FEC sets only if we are sure that there
3317 : are no more exec tile notifications to process. This delays FEC
3318 : ingestion just enough so as to keep the exec tiles as busy as we
3319 : can, and prevents us from being stuck ingesting a backlog of FEC
3320 : sets, especially when there is a pending completion notification
3321 : about a single-transaction chokepoint in the replay dispatcher DAG.
3322 : Except that when we are leader or the reasm buffer is getting full,
3323 : we prioritize FEC processing. In the leader case, this is so we
3324 : can get to the leader FEC sets asap and freeze the leader bank on
3325 : time. In the reasm full case, this is so we don't prematurely
3326 : trigger eviction. */
3327 0 : int evict_banks = 0;
3328 0 : if( FD_LIKELY( (ctx->execrp_idle_cnt>=2UL*ctx->in_cnt || ctx->is_leader || fd_reasm_free( ctx->reasm )<=1UL) &&
3329 0 : can_process_fec( ctx, &evict_banks ) ) ) {
3330 0 : fd_reasm_fec_t * fec = fd_reasm_pop( ctx->reasm );
3331 0 : process_fec_set( ctx, stem, fec );
3332 0 : ctx->execrp_idle_cnt = 0UL;
3333 0 : return 1;
3334 0 : }
3335 :
3336 : /* If we need to evict banks, gather one evictable bank. The bank is
3337 : marked prunable by fd_banks_get_evictable_bank and pruned once refs
3338 : drain. */
3339 0 : if( FD_UNLIKELY( evict_banks ) ) {
3340 0 : ulong evictable_bank_idx = fd_banks_get_evictable_bank( ctx->banks, ctx->notified_root_bank );
3341 0 : if( FD_UNLIKELY( evictable_bank_idx==ULONG_MAX ) ) {
3342 0 : FD_LOG_DEBUG(( "replay has no banks to mark as prunable, it's possible that there is one bank already marked as prunable" ));
3343 0 : return 0;
3344 0 : }
3345 :
3346 0 : FD_LOG_WARNING(( "banks full, evicting bank (idx=%lu)", evictable_bank_idx ));
3347 :
3348 0 : timing_slot_release( ctx, evictable_bank_idx );
3349 :
3350 0 : if( FD_UNLIKELY( fd_sched_block_is_discarded( ctx->sched, evictable_bank_idx ) ) ) {
3351 0 : fd_block_id_ele_t * ele = &ctx->block_id_arr[ evictable_bank_idx ];
3352 0 : report_block_incomplete( ctx, ele->slot, ctx->alpenglow && ele->block_id_seen ? &ele->dmr : &ele->latest_mr, fd_banks_bank_query( ctx->banks, evictable_bank_idx ),
3353 0 : FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED );
3354 0 : }
3355 :
3356 : /* Send a notification to other tiles to drop a reference to the
3357 : evictable bank. The RPC tile is the only tile which holds onto
3358 : non-rooted banks, non-transiently. */
3359 0 : fd_replay_drop_bank_ref_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
3360 0 : fd_sched_block_abandon( ctx->sched, evictable_bank_idx, FD_SCHED_ABANDON_DISCARDED );
3361 0 : msg->bank_idx = evictable_bank_idx;
3362 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() ) );
3363 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 );
3364 :
3365 0 : return 1;
3366 0 : }
3367 :
3368 0 : return 0;
3369 0 : }
3370 :
3371 : static void
3372 : after_credit( fd_replay_tile_t * ctx,
3373 : fd_stem_context_t * stem,
3374 : int * opt_poll_in,
3375 0 : int * charge_busy ) {
3376 0 : if( FD_UNLIKELY( !ctx->is_booted || !ctx->wfs_complete ) ) return;
3377 :
3378 : /* The overall priority for the replay tile in order is:
3379 : 1. Make sure replay has room to progress:
3380 : a. evicting pending FECs from the reassembler
3381 : b. queueing up evictable banks for pruning if needed
3382 : c. clearing any pending bank eviction victims.
3383 : 2. Drain outstanding bank references from the scheduler. This
3384 : happens after a block gets completed or a fork gets pruned.
3385 : 3. Notify sched and bank consumers of a new consensus root, then
3386 : advance the storage root once old references drain.
3387 : 4. Replay. If there is work to do for replay, do it. This is
3388 : more important than ingesting more FEC sets.
3389 : 5. If replay has nothing to do, ingest more FEC sets.
3390 : WARNING: The ordering here is VERY load bearing and it should not
3391 : be changed without extreme caution. */
3392 :
3393 0 : if( FD_UNLIKELY( try_evict_reasm( ctx, stem ) ) ) {
3394 0 : *charge_busy = 1;
3395 0 : *opt_poll_in = 0;
3396 0 : return;
3397 0 : }
3398 :
3399 0 : if( FD_UNLIKELY( try_prune_sched( ctx ) ) ) {
3400 0 : *charge_busy = 1;
3401 0 : *opt_poll_in = 0;
3402 0 : return;
3403 0 : }
3404 :
3405 0 : if( FD_UNLIKELY( try_notify_consensus_root( ctx, stem ) ) ) {
3406 0 : *charge_busy = 1;
3407 0 : *opt_poll_in = 0;
3408 0 : return;
3409 0 : }
3410 :
3411 0 : if( FD_UNLIKELY( try_prune_bank( ctx ) ) ) {
3412 0 : *charge_busy = 1;
3413 0 : *opt_poll_in = 0;
3414 0 : return;
3415 0 : }
3416 :
3417 0 : if( FD_UNLIKELY( !ctx->alpenglow && try_become_leader( ctx, stem ) ) ) {
3418 0 : *charge_busy = 1;
3419 0 : *opt_poll_in = 0;
3420 0 : return;
3421 0 : }
3422 :
3423 0 : if( FD_UNLIKELY( ctx->alpenglow && try_become_leader_ag( ctx, stem ) ) ) {
3424 0 : *charge_busy = 1;
3425 0 : *opt_poll_in = 0;
3426 0 : return;
3427 0 : }
3428 :
3429 0 : if( FD_UNLIKELY( try_fini_leader( ctx, stem ) ) ) {
3430 0 : *charge_busy = 1;
3431 0 : *opt_poll_in = 0;
3432 0 : return;
3433 0 : }
3434 :
3435 0 : if( FD_UNLIKELY( try_advance_published_root( ctx, stem ) ) ) {
3436 0 : *charge_busy = 1;
3437 0 : *opt_poll_in = 0;
3438 0 : return;
3439 0 : }
3440 :
3441 0 : if( FD_LIKELY( try_replay( ctx, stem ) ) ) {
3442 0 : *charge_busy = 1;
3443 0 : *opt_poll_in = 0;
3444 0 : return;
3445 0 : }
3446 :
3447 0 : if( FD_LIKELY( !ctx->alpenglow && try_process_fec( ctx, stem ) ) ) {
3448 0 : *charge_busy = 1;
3449 0 : *opt_poll_in = 0;
3450 0 : return;
3451 0 : }
3452 :
3453 0 : ctx->execrp_idle_cnt++;
3454 0 : }
3455 :
3456 : static int
3457 : before_frag( fd_replay_tile_t * ctx,
3458 : ulong in_idx,
3459 : ulong seq FD_PARAM_UNUSED,
3460 0 : ulong sig ) {
3461 :
3462 0 : if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP_OUT && sig!=FD_GOSSIP_UPDATE_TAG_WFS_DONE ) ) return 1;
3463 0 : return 0;
3464 0 : }
3465 :
3466 : static void
3467 : process_exec_task_done( fd_replay_tile_t * ctx,
3468 : fd_stem_context_t * stem,
3469 : fd_execrp_task_done_msg_t * msg,
3470 0 : ulong sig ) {
3471 :
3472 0 : ulong exec_tile_idx = sig&0xFFFFFFFFUL;
3473 :
3474 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, msg->bank_idx );
3475 0 : FD_TEST( bank );
3476 0 : bank->refcnt--;
3477 :
3478 0 : switch( sig>>32 ) {
3479 0 : case FD_EXECRP_TT_TXN_EXEC: {
3480 0 : ulong txn_idx = msg->txn_exec->txn_idx;
3481 0 : if( FD_UNLIKELY( !ctx->identity_vote_rooted ) ) {
3482 : /* Query the txn signature against our recently generated vote
3483 : txn signatures. If the query is successful, then we have
3484 : seen our own vote transaction land and this should be marked
3485 : in the bank. We go through this exercise until we've seen
3486 : our vote rooted. */
3487 0 : fd_txn_p_t * txn_p = fd_sched_get_txn( ctx->sched, txn_idx );
3488 :
3489 0 : fd_pubkey_t * identity_pubkey_out = NULL;
3490 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 ) ) {
3491 0 : bank->f.identity_vote_idx = ctx->identity_idx;
3492 0 : }
3493 0 : }
3494 0 : if( FD_UNLIKELY( !msg->txn_exec->is_committable && bank->state!=FD_BANK_STATE_DEAD) ) {
3495 : /* Every transaction in a valid block has to execute.
3496 : Otherwise, we should mark the block as dead. Non-committable
3497 : means the txn failed before account loading or failed the cost
3498 : tracker; txn_err distinguishes the cases for telemetry. */
3499 0 : int dead_reason;
3500 0 : switch( msg->txn_exec->txn_err ) {
3501 0 : case FD_RUNTIME_TXN_ERR_WOULD_EXCEED_MAX_BLOCK_COST_LIMIT:
3502 0 : dead_reason = FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_BLOCK_COST_LIMIT;
3503 0 : break;
3504 0 : case FD_RUNTIME_TXN_ERR_WOULD_EXCEED_MAX_ACCOUNT_COST_LIMIT:
3505 0 : dead_reason = FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ACCOUNT_COST_LIMIT;
3506 0 : break;
3507 0 : case FD_RUNTIME_TXN_ERR_WOULD_EXCEED_MAX_VOTE_COST_LIMIT:
3508 0 : dead_reason = FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_VOTE_COST_LIMIT;
3509 0 : break;
3510 0 : case FD_RUNTIME_TXN_ERR_WOULD_EXCEED_ACCOUNT_DATA_BLOCK_LIMIT:
3511 0 : case FD_RUNTIME_TXN_ERR_WOULD_EXCEED_ACCOUNT_DATA_TOTAL_LIMIT:
3512 0 : dead_reason = FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_ACCOUNT_DATA_LIMIT;
3513 0 : break;
3514 0 : case FD_RUNTIME_TXN_ERR_ACCOUNT_LOADED_TWICE: /* exec-path duplicate, same taxonomy as the chkdup ruling */
3515 0 : dead_reason = FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_DUPLICATE_ACCOUNT;
3516 0 : break;
3517 0 : default:
3518 0 : dead_reason = FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_TXN_FAILED_TO_LOAD;
3519 0 : }
3520 0 : mark_bank_dead( ctx, stem, bank->idx, dead_reason, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED );
3521 0 : fd_sched_block_abandon( ctx->sched, bank->idx, FD_SCHED_ABANDON_INVALID );
3522 0 : }
3523 0 : int res = fd_sched_task_done( ctx->sched, FD_SCHED_TT_TXN_EXEC, txn_idx, exec_tile_idx, NULL );
3524 0 : FD_TEST( res==0 );
3525 0 : fd_sched_txn_info_t * txn_info = fd_sched_get_txn_info( ctx->sched, txn_idx );
3526 0 : txn_info->flags |= FD_SCHED_TXN_EXEC_DONE;
3527 0 : txn_info->is_simple_vote = msg->txn_exec->is_simple_vote;
3528 0 : txn_info->bank_seq = msg->txn_exec->bank_seq;
3529 :
3530 0 : txn_info->tick_load_start = msg->txn_exec->tick_load_start;
3531 0 : txn_info->tick_check_start = msg->txn_exec->tick_check_start;
3532 0 : txn_info->tick_exec_start = msg->txn_exec->tick_exec_start;
3533 0 : txn_info->tick_commit_start = msg->txn_exec->tick_commit_start;
3534 0 : txn_info->tick_commit_end = msg->txn_exec->tick_commit_end;
3535 :
3536 0 : txn_info->compute_units_consumed = msg->txn_exec->compute_units_consumed;
3537 0 : if( FD_LIKELY( bank->cost_tracker_pool_idx!=ULONG_MAX ) ) {
3538 0 : fd_cost_tracker_t const * cost_tracker = fd_bank_cost_tracker_query( bank );
3539 0 : txn_info->max_compute_units = cost_tracker->block_cost_limit ? cost_tracker->block_cost_limit : ULONG_MAX;
3540 0 : }
3541 0 : txn_info->transaction_fee = msg->txn_exec->transaction_fee;
3542 0 : txn_info->priority_fee = msg->txn_exec->priority_fee;
3543 0 : txn_info->tips = msg->txn_exec->tips;
3544 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. */
3545 0 : txn_info->txn_err = msg->txn_exec->txn_err;
3546 0 : txn_info->flags |= fd_ulong_if( msg->txn_exec->is_committable, FD_SCHED_TXN_IS_COMMITTABLE, 0UL );
3547 0 : txn_info->flags |= fd_ulong_if( msg->txn_exec->is_fees_only, FD_SCHED_TXN_IS_FEES_ONLY, 0UL );
3548 0 : txn_info->flags |= fd_ulong_if( msg->txn_exec->is_noop, FD_SCHED_TXN_IS_NOOP, 0UL );
3549 0 : }
3550 0 : if( FD_UNLIKELY( (txn_info->flags&FD_SCHED_TXN_REPLAY_DONE)==FD_SCHED_TXN_REPLAY_DONE ) ) { /* UNLIKELY because generally exec happens before sigverify. */
3551 0 : publish_txn_executed( ctx, stem, bank->idx, txn_idx );
3552 0 : }
3553 0 : break;
3554 0 : }
3555 0 : case FD_EXECRP_TT_TXN_SIGVERIFY: {
3556 0 : ulong txn_idx = msg->txn_sigverify->txn_idx;
3557 0 : fd_sched_txn_info_t * txn_info = fd_sched_get_txn_info( ctx->sched, txn_idx );
3558 0 : txn_info->flags |= FD_SCHED_TXN_SIGVERIFY_DONE;
3559 0 : if( FD_UNLIKELY( msg->txn_sigverify->err ) ) {
3560 0 : txn_info->txn_err = FD_RUNTIME_TXN_ERR_SIGNATURE_FAILURE;
3561 0 : txn_info->flags &= ~FD_SCHED_TXN_IS_COMMITTABLE;
3562 0 : txn_info->flags &= ~FD_SCHED_TXN_IS_FEES_ONLY;
3563 0 : txn_info->flags &= ~FD_SCHED_TXN_IS_NOOP;
3564 0 : }
3565 0 : if( FD_UNLIKELY( msg->txn_sigverify->err && bank->state!=FD_BANK_STATE_DEAD ) ) {
3566 : /* Every transaction in a valid block has to sigverify.
3567 : Otherwise, we should mark the block as dead. Also freeze the
3568 : bank if possible. */
3569 0 : mark_bank_dead( ctx, stem, bank->idx, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_SIGVERIFY_FAILED, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED );
3570 0 : fd_sched_block_abandon( ctx->sched, bank->idx, FD_SCHED_ABANDON_INVALID );
3571 0 : }
3572 0 : int res = fd_sched_task_done( ctx->sched, FD_SCHED_TT_TXN_SIGVERIFY, txn_idx, exec_tile_idx, NULL );
3573 0 : FD_TEST( res==0 );
3574 0 : if( FD_LIKELY( (txn_info->flags&FD_SCHED_TXN_REPLAY_DONE)==FD_SCHED_TXN_REPLAY_DONE ) ) {
3575 0 : publish_txn_executed( ctx, stem, bank->idx, txn_idx );
3576 0 : }
3577 0 : break;
3578 0 : }
3579 0 : case FD_EXECRP_TT_POH_HASH: {
3580 0 : int res = fd_sched_task_done( ctx->sched, FD_SCHED_TT_POH_HASH, ULONG_MAX, exec_tile_idx, msg->poh_hash );
3581 0 : if( FD_UNLIKELY( res && bank->state!=FD_BANK_STATE_DEAD ) ) {
3582 0 : mark_bank_dead( ctx, stem, bank->idx, sched_dead_reason_to_event( res ), FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED );
3583 0 : }
3584 0 : break;
3585 0 : }
3586 0 : default: FD_LOG_CRIT(( "unexpected sig 0x%lx", sig ));
3587 0 : }
3588 :
3589 : /* Reference counter just decreased, and an exec tile just got freed
3590 : up. If there's a need to be more aggressively pruning, we could
3591 : check here if more slots just became publishable and publish. Not
3592 : publishing here shouldn't bloat the fork tree too much though. We
3593 : mark minority forks dead as soon as we can, and execution dispatch
3594 : stops on dead blocks. So shortly afterwards, dead blocks should be
3595 : eligible for pruning as in-flight transactions retire from the
3596 : execution pipeline. */
3597 :
3598 0 : }
3599 :
3600 : static void
3601 : process_tower_slot_done( fd_replay_tile_t * ctx,
3602 : fd_stem_context_t * stem,
3603 : fd_tower_slot_done_t const * msg,
3604 0 : ulong seq ) {
3605 :
3606 : /* This frag from tower tells us to:
3607 : - Release an outstanding refernence on the replayed bank
3608 : - Advance the consensus root if one has been supplied
3609 : - Update the reset block */
3610 :
3611 0 : fd_bank_t * replay_bank = fd_banks_bank_query( ctx->banks, msg->replay_bank_idx );
3612 0 : if( FD_UNLIKELY( !replay_bank ) ) FD_LOG_CRIT(( "invariant violation: bank not found for bank index %lu", msg->replay_bank_idx ));
3613 0 : replay_bank->refcnt--;
3614 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt decremented to %lu for tower", replay_bank->idx, msg->replay_slot, replay_bank->refcnt ));
3615 :
3616 0 : if( FD_LIKELY( msg->root_slot!=ULONG_MAX ) ) {
3617 0 : FD_TEST( msg->root_slot>=ctx->consensus_root_slot );
3618 0 : ctx->consensus_root_slot = msg->root_slot;
3619 0 : ctx->consensus_root = msg->root_block_id;
3620 0 : }
3621 :
3622 0 : if( FD_UNLIKELY( fd_hash_eq( &msg->reset_block_id, &ctx->reset_cmr ) ) ) return;
3623 :
3624 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 );
3625 0 : if( FD_UNLIKELY( !block_id_ele ) ) {
3626 0 : FD_LOG_WARNING(( "ignoring reset block update from tower because block has been evicted (slot=%lu)", msg->reset_slot ));
3627 0 : return;
3628 0 : }
3629 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) );
3630 0 : if( FD_UNLIKELY( !bank || bank->bank_seq!=block_id_ele->bank_seq || bank->state==FD_BANK_STATE_PRUNABLE ) ) {
3631 0 : FD_LOG_WARNING(( "ignoring reset block update from tower because bank has been evicted (slot=%lu)", msg->reset_slot ));
3632 0 : return;
3633 0 : }
3634 :
3635 0 : ctx->reset_cmr = msg->reset_block_id;
3636 0 : ctx->reset_slot = msg->reset_slot;
3637 0 : ctx->reset_timestamp_nanos = fd_clock_tile_now( ctx->clock );
3638 0 : if( FD_LIKELY( msg->root_slot!=ULONG_MAX ) ) FD_TEST( msg->root_slot<=msg->reset_slot );
3639 :
3640 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 ) );
3641 0 : ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, min_leader_slot, ctx->identity_pubkey );
3642 0 : if( FD_LIKELY( ctx->next_leader_slot != ULONG_MAX ) ) {
3643 0 : double slot_duration_ticks = (double)bank->f.slot_params.ns_per_slot_adjusted*ctx->tick_per_ns;
3644 0 : ctx->next_leader_tickcount = (long)((double)(ctx->next_leader_slot-ctx->reset_slot-1UL)*slot_duration_ticks) + fd_tickcount();
3645 0 : } else {
3646 0 : ctx->next_leader_tickcount = LONG_MAX;
3647 0 : }
3648 :
3649 0 : if( FD_LIKELY( ctx->replay_out->idx!=ULONG_MAX ) ) {
3650 0 : fd_poh_reset_t * reset = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
3651 :
3652 0 : reset->bank_idx = bank->idx;
3653 0 : reset->timestamp = ctx->reset_timestamp_nanos;
3654 0 : reset->completed_slot = ctx->reset_slot;
3655 0 : reset->hashcnt_per_tick = bank->f.slot_params.hashes_per_tick;
3656 0 : reset->ticks_per_slot = bank->f.ticks_per_slot;
3657 0 : reset->tick_duration_ns = bank->f.slot_params.ns_per_slot_adjusted/reset->ticks_per_slot;
3658 :
3659 0 : fd_memcpy( reset->completed_cmr, &block_id_ele->latest_mr, sizeof(fd_hash_t) );
3660 :
3661 0 : fd_blockhashes_t const * block_hash_queue = &bank->f.block_hash_queue;
3662 0 : fd_hash_t const * last_hash = fd_blockhashes_peek_last_hash( block_hash_queue );
3663 0 : FD_TEST( last_hash );
3664 0 : fd_memcpy( reset->completed_blockhash, last_hash->uc, sizeof(fd_hash_t) );
3665 :
3666 0 : reset->max_microblocks_in_slot = fd_poh_max_microblocks_per_slot( bank->f.ticks_per_slot, reset->hashcnt_per_tick );
3667 0 : reset->next_leader_slot = ctx->next_leader_slot;
3668 0 : reset->wfs_paused = !ctx->wfs_complete;
3669 :
3670 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() ) );
3671 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 );
3672 0 : }
3673 :
3674 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 ));
3675 0 : try_become_leader( ctx, stem );
3676 :
3677 0 : ulong distance = 0UL;
3678 0 : fd_bank_t * parent = bank;
3679 0 : while( parent ) {
3680 0 : if( FD_UNLIKELY( fd_hash_eq( &parent->f.block_id, &ctx->consensus_root ) ) ) break;
3681 0 : parent = fd_banks_get_parent( ctx->banks, parent );
3682 0 : distance++;
3683 0 : }
3684 :
3685 0 : FD_MGAUGE_SET( REPLAY, ROOT_DISTANCE, distance );
3686 :
3687 0 : }
3688 :
3689 : static void
3690 : process_fec_complete( fd_replay_tile_t * ctx,
3691 : ulong sig,
3692 0 : fd_repair_fec_complete_t * complete_msg ) {
3693 0 : fd_shred_t const * shred = &complete_msg->fec.last_shred_hdr;
3694 :
3695 0 : fd_hash_t const * merkle_root = &complete_msg->fec.merkle_root;
3696 0 : fd_hash_t const * chained_merkle_root = &complete_msg->fec.chained_merkle_root;
3697 0 : int is_leader_fec = sig == REPAIR_SIG_FEC_LEADER;
3698 0 : int data_complete = !!( shred->data.flags & FD_SHRED_DATA_FLAG_DATA_COMPLETE );
3699 0 : int slot_complete = !!( shred->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE );
3700 :
3701 0 : if( FD_UNLIKELY( sig==REPAIR_SIG_FEC_INVALID ) ) {
3702 : /* FEC set detected as invalid based on duplicate confirmations.
3703 : Nothing to do except remove from store. If the FEC set is not in
3704 : reasm, we can directly remove from store. If the FEC set is in
3705 : reasm, then we let reasm_publish handle it. */
3706 0 : if( FD_LIKELY( !fd_reasm_query( ctx->reasm, merkle_root ) ) ) {
3707 0 : fd_store_remove( ctx->store, ctx->map_join, merkle_root );
3708 0 : }
3709 0 : return;
3710 0 : }
3711 :
3712 : /* Track the cluster tip: the highest slot seen in FEC sets from the
3713 : network (leader FECs are our own blocks, not evidence of the tip). */
3714 0 : if( FD_LIKELY( !is_leader_fec && ( ctx->catch_up_max_fec_slot==ULONG_MAX || shred->slot>ctx->catch_up_max_fec_slot ) ) ) {
3715 0 : ctx->catch_up_max_fec_slot = shred->slot;
3716 0 : ctx->catch_up_tip_advance_cnt++;
3717 0 : }
3718 :
3719 0 : if( FD_UNLIKELY( shred->slot - shred->data.parent_off == fd_reasm_slot0( ctx->reasm ) && shred->fec_set_idx == 0) ) {
3720 0 : chained_merkle_root = &fd_reasm_root( ctx->reasm )->key;
3721 0 : }
3722 :
3723 0 : if( FD_UNLIKELY( fd_reasm_query( ctx->reasm, merkle_root ) ) ) return;
3724 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->map_join, &ctx->reasm_evicted );
3725 :
3726 0 : if( FD_UNLIKELY( !fec ) ) {
3727 : /* reasm failed to insert. We don't want to just put this back on
3728 : the returnable_frag queue because it's unclear whether this FEC
3729 : is truly something we want to process. Therefore our best option
3730 : is to punt it and "go around." Either the FEC was invalid and
3731 : was rejected or reasm_insert populates its last pool element with
3732 : the data of the failed insert, so we make sure to publish the
3733 : failed insert data to repair in after_credit. */
3734 0 : fd_store_remove( ctx->store, ctx->map_join, merkle_root );
3735 0 : return;
3736 0 : }
3737 :
3738 0 : fec->fec_completed_ts_nanos = complete_msg->metrics.fec_completed_ts_nanos;
3739 0 : if( FD_LIKELY( complete_msg->metrics.stats_valid ) ) {
3740 : /* Repair builds these cumulative snapshots from fd_forest_blk_t,
3741 : which is keyed by slot rather than block identity. Retain only
3742 : the newest valid snapshot for that slot. */
3743 0 : fd_reception_stats_t * stats = &ctx->reception_stats[ fec->slot % ctx->reception_stats_cnt ];
3744 0 : stats->slot = fec->slot;
3745 0 : stats->fec_set_idx = fec->fec_set_idx;
3746 0 : stats->metrics = complete_msg->metrics;
3747 0 : }
3748 0 : }
3749 :
3750 : /* Essentially an inlined version of try_process_fec that reads off the
3751 : dcache directly, instead of the reasm out_queue.
3752 : All FECs processed by this function must be safe to be forever
3753 : removed from the dcache. */
3754 : static void
3755 : process_rotor_fec( fd_replay_tile_t * ctx,
3756 : fd_stem_context_t * stem,
3757 0 : fd_rotor_replay_fec_t * fec ) {
3758 0 : if( FD_LIKELY( !fec->is_leader && ( ctx->catch_up_max_fec_slot==ULONG_MAX || fec->slot>ctx->catch_up_max_fec_slot ) ) ) {
3759 0 : ctx->catch_up_max_fec_slot = fec->slot;
3760 0 : ctx->catch_up_tip_advance_cnt++;
3761 0 : }
3762 :
3763 : /* A leader FEC arriving after its slot was aborted (or after a later
3764 : leadership began) has no bank to bind to; drop it. */
3765 0 : if( FD_UNLIKELY( fec->is_leader && ( !ctx->leader_bank || ctx->leader_bank->f.slot!=fec->slot ) ) ) return;
3766 :
3767 0 : ulong parent_bank_idx = ULONG_MAX;
3768 0 : if( FD_UNLIKELY( fec->fec_set_idx==0 ) ) {
3769 0 : ag_block_id_t parent_key = ag_block_id( fec->parent_slot, fec->parent_block_id.uc );
3770 0 : fd_block_id_ele_t * parent = fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &parent_key, NULL, ctx->block_id_arr );
3771 0 : FD_TEST( parent );
3772 0 : parent_bank_idx = fd_block_id_ele_get_idx( ctx->block_id_arr, parent );
3773 0 : } else {
3774 : /* mid-slot FEC: rotor tile promises to populate block_id
3775 : correctly for blocks with known block id. If we are processing
3776 : the last FEC set of a slot received through turbine though, the
3777 : block_id_map will still be keyed with {0}, but the block_id on
3778 : the FEC will have the computed DMR. We first try to replay
3779 : this FEC on a bank keyed with the computed DMR - if it doesn't
3780 : exist, it must belong to the original turbine version. */
3781 0 : ag_block_id_t key = { .slot = fec->slot };
3782 0 : if( FD_LIKELY( fec->known_id ) ) {
3783 0 : key = ag_block_id( fec->slot, fec->block_id.uc );
3784 0 : }
3785 :
3786 0 : fd_block_id_ele_t * parent = fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &key, NULL, ctx->block_id_arr );
3787 0 : FD_TEST( parent );
3788 0 : parent_bank_idx = fd_block_id_ele_get_idx( ctx->block_id_arr, parent );
3789 0 : }
3790 :
3791 0 : fd_bank_t * parent_bank = fd_banks_bank_query( ctx->banks, parent_bank_idx );
3792 0 : FD_TEST( parent_bank );
3793 :
3794 0 : if( FD_UNLIKELY( parent_bank->state==FD_BANK_STATE_DEAD ) ) {
3795 0 : FD_LOG_WARNING(( "parent bank is dead for slot %lu, fec set idx %u, parent slot %lu, dropping", fec->slot, fec->fec_set_idx, fec->parent_slot ));
3796 0 : return;
3797 0 : }
3798 :
3799 0 : ulong work = (ulong)fd_log_wallclock();
3800 :
3801 0 : fd_store_fec_data_view_t data_view[1];
3802 0 : fd_rwlock_read( &ctx->store->fec_lock );
3803 0 : fd_store_fec_t * store_fec = fd_store_query( ctx->map_join, &fec->mr );
3804 0 : ctx->metrics.store_query_cnt++;
3805 0 : int view_err = store_fec ? fd_store_fec_data_view( ctx->store, ctx->store_disk_fd, store_fec, data_view ) : -1;
3806 0 : fd_rwlock_unread( &ctx->store->fec_lock );
3807 :
3808 : /* A missing store entry is expected: rotor (the store publisher)
3809 : removes FEC sets on publish, so a FEC delivered for a slice that has
3810 : since been pruned/rooted is no longer in the store. The lookup and
3811 : payload pin are one lifetime transaction under fec_lock: remove may
3812 : unlink the key only after the view has pinned the pool element. */
3813 0 : if( FD_UNLIKELY( !store_fec || view_err ) ) {
3814 0 : ctx->metrics.store_query_missing_cnt++;
3815 0 : ctx->metrics.store_query_missing_mr = fec->mr.ul[0];
3816 0 : FD_BASE58_ENCODE_32_BYTES( fec->mr.key, key_b58 );
3817 0 : FD_LOG_INFO(( "store fec for slot: %lu unavailable (pruned by publish); abandoning slice. root: %lu. merkle: %s", fec->slot, ctx->consensus_root_slot, key_b58 ));
3818 0 : return;
3819 0 : }
3820 :
3821 0 : long now = fd_log_wallclock();
3822 0 : fd_block_id_ele_t * block_id_ele;
3823 0 : fd_bank_t * bank;
3824 0 : if( FD_UNLIKELY( fec->fec_set_idx==0U ) ) {
3825 0 : bank = fec->is_leader ? ctx->leader_bank : fd_banks_new_bank( ctx->banks, parent_bank_idx, now, 0 );
3826 0 : if( FD_UNLIKELY( fec->is_leader && ctx->leader_stats.slot==fec->slot ) ) {
3827 0 : ctx->leader_stats.first_fec_returned_nanos = now;
3828 0 : }
3829 :
3830 0 : block_id_ele = &ctx->block_id_arr[ bank->idx ];
3831 : /* Clear the previous occupant of this reused bank idx */
3832 0 : if( FD_LIKELY( fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr )==block_id_ele ) ) {
3833 0 : FD_TEST( fd_ag_block_id_map_ele_remove( ctx->ag_block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr ) );
3834 0 : }
3835 :
3836 0 : block_id_ele->block_id_seen = 0;
3837 0 : block_id_ele->bank_seq = bank->bank_seq;
3838 0 : block_id_ele->slot = fec->slot;
3839 0 : block_id_ele->latest_fec_idx = 0U;
3840 0 : block_id_ele->fec_cnt = 0U;
3841 0 : block_id_ele->block_info = fec->known_id ? ag_block_id( fec->slot, fec->block_id.uc ) : (ag_block_id_t){ .slot = fec->slot };
3842 :
3843 : /* If this fec 0 is rebuilding an evicted block (recovery redelivery),
3844 : the evicted bank's stale map entry may still hold this key at a
3845 : DIFFERENT idx. */
3846 0 : fd_ag_block_id_map_ele_remove( ctx->ag_block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr ); // evicted / stale entry
3847 0 : FD_TEST( fd_ag_block_id_map_ele_insert( ctx->ag_block_id_map, block_id_ele, ctx->block_id_arr ) );
3848 0 : } else { /* FEC for the middle or end of a block */
3849 : /* Assign bank idx + seqno to the FEC. Update block id pool ele.
3850 : The block stays keyed by {slot, 0} in the compound map. */
3851 0 : block_id_ele = &ctx->block_id_arr[ parent_bank_idx ];
3852 0 : block_id_ele->latest_fec_idx = fec->fec_set_idx;
3853 0 : bank = fd_banks_bank_query( ctx->banks, parent_bank_idx );
3854 0 : }
3855 :
3856 0 : block_id_ele->latest_mr = fec->mr;
3857 :
3858 0 : if( FD_UNLIKELY( ctx->report_runtime_diffs ) ) {
3859 0 : if( FD_LIKELY( block_id_ele->fec_cnt<FD_FEC_BLK_MAX ) ) {
3860 0 : ctx->fec_chain[ bank->idx*FD_FEC_BLK_MAX + block_id_ele->fec_cnt ] = fec->mr;
3861 0 : }
3862 0 : block_id_ele->fec_cnt++;
3863 0 : }
3864 :
3865 0 : if( FD_UNLIKELY( fec->slot_complete ) ) {
3866 0 : FD_BASE58_ENCODE_32_BYTES( fec->block_id.uc, block_id_b58 );
3867 0 : FD_LOG_INFO(( "slot %lu fec set idx %u slot_bid %s is complete, inserted to map", fec->slot, fec->fec_set_idx, block_id_b58 ));
3868 0 : block_id_ele->block_id_seen = 1;
3869 0 : block_id_ele->dmr = fec->block_id;
3870 :
3871 : /* block is complete and DMR block id is now known so re-key. The
3872 : map does not support duplicate keys: rotor guarantees at most one
3873 : delivery stream per block (a turbine copy of a slot with a
3874 : votor-driven version is abandoned in the chainer, see
3875 : fd_rotor_tile.h), so {slot, block_id} can never already be
3876 : occupied by another LIVE bank. It can however be occupied by a
3877 : STALE entry: a previously-completed copy of this block whose bank
3878 : was evicted (eviction does not remove map entries), now being
3879 : rebuilt through recovery redelivery. Evict the stale entry before
3880 : inserting. */
3881 0 : FD_TEST( fd_ag_block_id_map_ele_remove( ctx->ag_block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr )==block_id_ele );
3882 0 : ag_block_id_t key = ag_block_id( fec->slot, fec->block_id.uc );
3883 0 : block_id_ele->block_info = key;
3884 0 : fd_ag_block_id_map_ele_remove( ctx->ag_block_id_map, &key, NULL, ctx->block_id_arr ); // evicted / stale entry
3885 0 : FD_TEST( fd_ag_block_id_map_ele_insert( ctx->ag_block_id_map, block_id_ele, ctx->block_id_arr ) );
3886 0 : }
3887 :
3888 : /* For leader FECs, don't insert the FEC into the scheduler. */
3889 0 : if( FD_UNLIKELY( fec->is_leader ) ) {
3890 0 : fd_store_fec_data_view_release( ctx->store, data_view );
3891 0 : return;
3892 0 : }
3893 :
3894 : /* Forks form a partial ordering over FEC sets. The Rotor tile
3895 : delivers FEC sets in-order per fork, but FEC set ordering across
3896 : forks is arbitrary */
3897 0 : fd_sched_fec_t sched_fec[ 1 ];
3898 0 : sched_fec->shred_cnt = FD_FEC_SHRED_CNT;
3899 0 : sched_fec->is_last_in_batch = !!fec->data_complete;
3900 0 : sched_fec->is_last_in_block = !!fec->slot_complete;
3901 0 : sched_fec->bank_idx = bank->idx;
3902 0 : sched_fec->parent_bank_idx = bank->parent_idx;
3903 0 : sched_fec->slot = fec->slot;
3904 0 : sched_fec->parent_slot = fec->parent_slot;
3905 0 : sched_fec->is_first_in_block = fec->fec_set_idx==0U;
3906 0 : sched_fec->fec = store_fec;
3907 0 : sched_fec->data = data_view->data;
3908 0 : sched_fec->alut_ctx->fork_id = fd_banks_bank_query( ctx->banks, ctx->published_root_bank_idx )->accdb_fork_id;
3909 0 : sched_fec->alut_ctx->accdb = ctx->accdb;
3910 0 : sched_fec->alut_ctx->els = ctx->published_root_slot;
3911 0 : sched_fec->completed_ns = now; // TODO deliver data with rotor
3912 :
3913 0 : if( sched_fec->is_first_in_block ) {
3914 0 : bank->refcnt++;
3915 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for sched", bank->idx, sched_fec->slot, bank->refcnt ));
3916 0 : }
3917 :
3918 0 : int ingested = fd_sched_fec_ingest( ctx->sched, sched_fec );
3919 0 : fd_store_fec_data_view_release( ctx->store, data_view );
3920 :
3921 0 : if( FD_UNLIKELY( !ingested ) ) {
3922 0 : int dr = sched_block_dead_reason_to_event( ctx, sched_fec->bank_idx );
3923 0 : int ar = dr==FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD ? FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED
3924 0 : : FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED;
3925 0 : mark_bank_dead( ctx, stem, sched_fec->bank_idx, dr, ar );
3926 0 : return;
3927 0 : }
3928 :
3929 0 : fd_histf_sample( ctx->metrics.store_query_work, (ulong)fd_log_wallclock() - work );
3930 0 : ctx->execrp_idle_cnt = 0UL;
3931 0 : return;
3932 0 : }
3933 :
3934 : static void
3935 0 : process_resolv_slot_completed( fd_replay_tile_t * ctx, ulong bank_idx ) {
3936 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, bank_idx );
3937 0 : FD_TEST( bank );
3938 0 : bank->refcnt--;
3939 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt decremented to %lu for resolv", bank->idx, bank->f.slot, bank->refcnt ));
3940 0 : }
3941 :
3942 : static void
3943 : process_vote_txn_sent( fd_replay_tile_t * ctx,
3944 0 : fd_txn_m_t * txnm ) {
3945 : /* The send tile has signed and sent a vote. Add this vote to the
3946 : vote tracker. We go through this exercise until the client has
3947 : seen a vote corresponding to the current identity rooted. */
3948 0 : if( FD_UNLIKELY( !ctx->identity_vote_rooted ) ) {
3949 0 : uchar * payload = (uchar *)txnm + sizeof(fd_txn_m_t);
3950 0 : uchar txn_mem[ FD_TXN_MAX_SZ ] __attribute__((aligned(alignof(fd_txn_t))));
3951 0 : fd_txn_t * txn = (fd_txn_t *)txn_mem;
3952 0 : if( FD_UNLIKELY( !fd_txn_parse( payload, txnm->payload_sz, txn_mem, NULL ) ) ) {
3953 0 : FD_LOG_CRIT(( "Could not parse txn from send tile" ));
3954 0 : }
3955 : /* The identity of the validator that the signed the vote will
3956 : always be the first signer in the vote transaction. */
3957 0 : fd_pubkey_t * vote_identity = fd_type_pun( payload+txn->acct_addr_off );
3958 0 : fd_vote_tracker_insert( ctx->vote_tracker, vote_identity, fd_type_pun_const( payload+txn->signature_off ) );
3959 0 : }
3960 0 : }
3961 :
3962 : static inline void
3963 0 : maybe_verify_shred_version( fd_replay_tile_t * ctx ) {
3964 0 : if( FD_LIKELY( ctx->has_genesis_hash && ctx->hard_fork_cnt!=ULONG_MAX ) ) {
3965 0 : ctx->shred_version = compute_shred_version( ctx->genesis_hash->uc, ctx->hard_forks, ctx->hard_fork_cnt );
3966 0 : }
3967 :
3968 0 : if( FD_LIKELY( ctx->expected_shred_version && ctx->ipecho_shred_version ) ) {
3969 0 : if( FD_UNLIKELY( ctx->expected_shred_version!=ctx->ipecho_shred_version ) ) {
3970 0 : FD_LOG_ERR(( "shred version mismatch: expected %u but got %u from ipecho", ctx->expected_shred_version, ctx->ipecho_shred_version ) );
3971 0 : }
3972 0 : }
3973 :
3974 : /* During a cluster restart, the configured shred version is the post-
3975 : restart value advertised by gossip. Defer comparing it against the
3976 : snapshot's hard fork list until wait-for-supermajority completes. */
3977 0 : if( FD_UNLIKELY( ctx->wfs_enabled && !ctx->wfs_complete && ctx->expected_shred_version ) ) return;
3978 :
3979 0 : if( FD_LIKELY( ctx->shred_version && (ctx->expected_shred_version || ctx->ipecho_shred_version) ) ) {
3980 0 : ushort expected_shred_version = ctx->expected_shred_version ? ctx->expected_shred_version : ctx->ipecho_shred_version;
3981 0 : ushort actual_shred_version = ctx->shred_version;
3982 :
3983 0 : if( FD_UNLIKELY( expected_shred_version!=actual_shred_version ) ) {
3984 0 : FD_BASE58_ENCODE_32_BYTES( ctx->genesis_hash->uc, genesis_hash_b58 );
3985 0 : FD_LOG_ERR(( "Your genesis.bin file at `%s` combined with the hard_forks from the loaded snapshot have produced "
3986 0 : "a shred version of %hu but the entrypoint you connected to on boot reported a shred version of %hu. "
3987 0 : "This likely means that the genesis.bin file you have is for a different cluster than the one you "
3988 0 : "are trying to connect to, you can delete it and restart the node to download the correct genesis "
3989 0 : "file automatically.", ctx->genesis_path, actual_shred_version, expected_shred_version ));
3990 0 : }
3991 0 : }
3992 0 : }
3993 :
3994 : static inline void
3995 0 : maybe_verify_genesis_timestamp( fd_replay_tile_t * ctx ) {
3996 0 : if( FD_LIKELY( !ctx->has_expected_genesis_timestamp || !ctx->has_genesis_timestamp ) ) return;
3997 0 : if( FD_LIKELY( ctx->genesis_timestamp==ctx->expected_genesis_timestamp ) ) return;
3998 :
3999 0 : FD_LOG_ERR(( "Your genesis.bin file at `%s` has a genesis timestamp of %lu but the snapshot you loaded has a genesis "
4000 0 : "timestamp of %lu. This either means that the genesis.bin file you have is for a different cluster than "
4001 0 : "the one you are trying to connect to, or you have loaded a snapshot for the wrong cluster. In either "
4002 0 : "case, you can delete the problematic file and restart the node to download the correct one automatically.",
4003 0 : ctx->genesis_path, ctx->genesis_timestamp, ctx->expected_genesis_timestamp ));
4004 0 : }
4005 :
4006 : static void
4007 : update_metric_identity_balance( fd_replay_tile_t * ctx,
4008 : fd_accdb_fork_id_t fork_id,
4009 0 : fd_pubkey_t const * identity ) {
4010 0 : ulong identity_balance = fd_accdb_lamports( ctx->accdb, fork_id, identity->uc );
4011 0 : FD_MGAUGE_SET( REPLAY, IDENTITY_BALANCE_LAMPORTS, identity_balance );
4012 0 : }
4013 :
4014 : static void
4015 : update_metric_epoch_credits( fd_replay_tile_t * ctx,
4016 : fd_bank_t const * bank,
4017 : fd_accdb_fork_id_t fork_id,
4018 0 : fd_pubkey_t const * vote_key ) {
4019 0 : ulong epoch_credits = 0UL;
4020 0 : fd_acc_t ro = fd_accdb_read_one( ctx->accdb, fork_id, vote_key->uc );
4021 0 : if( FD_LIKELY( ro.lamports ) ) {
4022 0 : fd_vote_state_versioned_t vsv[1];
4023 0 : if( FD_LIKELY( fd_vote_state_versioned_deserialize( vsv, ro.data, ro.data_len ) ) ) {
4024 0 : fd_vote_epoch_credits_t const * ec = fd_vsv_get_epoch_credits( vsv );
4025 0 : if( !deq_fd_vote_epoch_credits_t_empty( ec ) ) {
4026 0 : fd_vote_epoch_credits_t const * last_ec = deq_fd_vote_epoch_credits_t_peek_tail_const( ec );
4027 0 : if( last_ec->epoch==bank->f.epoch ) {
4028 0 : epoch_credits = last_ec->credits;
4029 0 : }
4030 0 : }
4031 0 : }
4032 0 : }
4033 0 : fd_accdb_unread_one( ctx->accdb, &ro );
4034 :
4035 0 : FD_MGAUGE_SET( REPLAY, EPOCH_CREDITS, epoch_credits );
4036 0 : }
4037 :
4038 : static void
4039 : update_metric_active_stake( fd_bank_t const * bank,
4040 0 : fd_pubkey_t const * vote_key ) {
4041 0 : ulong my_active_stake = 0UL;
4042 0 : ulong tot_active_stake = bank->f.total_epoch_stake;
4043 :
4044 0 : ulong stake = 0UL;
4045 0 : fd_vote_stakes_query_t_1( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, vote_key, NULL, &stake, NULL );
4046 0 : my_active_stake = stake;
4047 :
4048 0 : FD_MGAUGE_SET( REPLAY, ACTIVE_STAKE_LAMPORTS, my_active_stake );
4049 0 : FD_MGAUGE_SET( REPLAY, CLUSTER_ACTIVE_STAKE_LAMPORTS, tot_active_stake );
4050 0 : }
4051 :
4052 : static void
4053 : update_metric_balances( fd_replay_tile_t * ctx,
4054 0 : fd_bank_t * bank ) {
4055 0 : fd_accdb_fork_id_t fork_id = bank->accdb_fork_id;
4056 0 : fd_node_info_t node_info[1]; fd_node_info_read( node_info, ctx->node_info );
4057 0 : if( !fd_pubkey_check_zero( &node_info->identity ) ) {
4058 0 : update_metric_identity_balance( ctx, fork_id, &node_info->identity );
4059 0 : }
4060 :
4061 0 : if( !fd_pubkey_check_zero( &node_info->vote_account ) ) {
4062 0 : update_metric_epoch_credits( ctx, bank, fork_id, &node_info->vote_account );
4063 0 : update_metric_active_stake ( bank, &node_info->vote_account );
4064 0 : }
4065 0 : }
4066 :
4067 : static void
4068 : process_tower_optimistic_confirmed( fd_replay_tile_t * ctx,
4069 : fd_stem_context_t * stem,
4070 0 : fd_tower_slot_confirmed_t const * msg ) {
4071 :
4072 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 );
4073 0 : if( FD_UNLIKELY( !block_id_ele ) ) {
4074 0 : FD_BASE58_ENCODE_32_BYTES( msg->block_id.key, block_id_b58 );
4075 0 : FD_LOG_WARNING(( "missing bank for confirmed block_id: %s level %d", block_id_b58, msg->level ));
4076 0 : return;
4077 0 : }
4078 :
4079 0 : ulong bank_idx = fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele );
4080 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, bank_idx );
4081 :
4082 0 : if( FD_UNLIKELY( !bank ||
4083 0 : bank->bank_seq!=block_id_ele->bank_seq ||
4084 0 : bank->state!=FD_BANK_STATE_FROZEN ) ) {
4085 0 : FD_BASE58_ENCODE_32_BYTES( msg->block_id.key, block_id_cstr );
4086 0 : FD_LOG_WARNING(( "failed to query optimistically confirmed bank for block id %s", block_id_cstr ));
4087 0 : return;
4088 0 : }
4089 :
4090 0 : if( ctx->rpc_enabled ) {
4091 0 : bank->refcnt++;
4092 0 : FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for rpc", bank->idx, bank->f.slot, bank->refcnt ));
4093 0 : }
4094 :
4095 0 : fd_replay_oc_advanced_t * replay_msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
4096 0 : replay_msg->bank_idx = bank_idx;
4097 0 : replay_msg->bank_seq = bank->bank_seq;
4098 0 : replay_msg->slot = msg->slot;
4099 :
4100 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() ) );
4101 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 );
4102 :
4103 0 : update_metric_balances( ctx, bank );
4104 0 : }
4105 :
4106 : /* snapmk_start instructs the snapmk tile to start producing a snapshot. */
4107 :
4108 : static void
4109 : snapmk_start( fd_replay_tile_t * ctx,
4110 : fd_stem_context_t * stem,
4111 0 : int incremental ) {
4112 :
4113 0 : FD_CHECK_CRIT( !ctx->snapmk.active, "snapshot creation already in progress" );
4114 :
4115 : /* pin current produced bank */
4116 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, ctx->published_root_bank_idx );
4117 0 : FD_CHECK_CRIT( bank, "invalid published_root_bank_idx" );
4118 :
4119 0 : if( FD_UNLIKELY( incremental ) ) {
4120 0 : FD_CHECK_CRIT( ctx->snapmk.base_slot!=ULONG_MAX, "incremental snapshot without a base full snapshot" );
4121 0 : FD_CHECK_CRIT( bank->f.slot>ctx->snapmk.base_slot, "incremental snapshot at or below its base slot" );
4122 0 : }
4123 :
4124 0 : bank->refcnt++;
4125 0 : ctx->snapmk.bank_idx = bank->idx;
4126 0 : ctx->snapmk.incremental = !!incremental;
4127 :
4128 : /* Send SNAP_START message to snapmk. */
4129 0 : fd_replay_snap_start_t * msg = fd_chunk_to_laddr( ctx->snapmk_out->mem, ctx->snapmk_out->chunk );
4130 0 : *msg = (fd_replay_snap_start_t) {
4131 0 : .bank_idx = ctx->published_root_bank_idx,
4132 0 : .base_slot = incremental ? ctx->snapmk.base_slot : bank->f.slot,
4133 0 : .slot = bank->f.slot
4134 0 : };
4135 0 : ulong out_idx = ctx->snapmk_out->idx;
4136 0 : ulong sig = REPLAY_SIG_SNAP_START;
4137 0 : ulong chunk = ctx->snapmk_out->chunk;
4138 0 : ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
4139 0 : ulong sz = sizeof(fd_replay_snap_start_t);
4140 0 : ulong seq = fd_stem_publish( stem, out_idx, sig, chunk, sz, 0UL, 0UL, tspub );
4141 0 : ctx->snapmk_out->chunk = fd_dcache_compact_next( ctx->snapmk_out->chunk, sz, ctx->snapmk_out->chunk0, ctx->snapmk_out->wmark );
4142 :
4143 : /* wake up the snapmk tile */
4144 0 : fd_frag_meta_t * replay_snapmk = stem->mcaches[ out_idx ];
4145 0 : ulong * snap_sync = fd_mcache_seq_laddr( replay_snapmk );
4146 0 : fd_mcache_seq_update( snap_sync, fd_seq_inc( seq, 1UL ) );
4147 0 : long ret = syscall( SYS_futex, snap_sync, FUTEX_WAKE, 1 );
4148 0 : if( FD_UNLIKELY( ret<0 ) ) {
4149 0 : FD_LOG_ERR(( "FUTEX_WAKE(snap_sync,seq=%u) failed (%i-%s)", (uint)seq, errno, fd_io_strerror( errno ) ));
4150 0 : }
4151 :
4152 : /* update internal state */
4153 0 : ctx->snapmk.active = 1;
4154 0 : }
4155 :
4156 : /* snapmk_done reacts to the snapmk tile reporting completion. */
4157 :
4158 : static void
4159 : snapmk_done( fd_replay_tile_t * ctx,
4160 : fd_stem_context_t * stem,
4161 0 : int success ) {
4162 0 : (void)stem;
4163 :
4164 0 : FD_CHECK_CRIT( ctx->snapmk.active, "spurious snap complete msg (not creating snapshot)" );
4165 :
4166 : /* release bank */
4167 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, ctx->snapmk.bank_idx );
4168 0 : FD_CHECK_CRIT( bank, "invalid snapmk.bank_idx" );
4169 0 : FD_CHECK_CRIT( bank->refcnt > 0UL, "invalid snapmk.bank_idx refcnt" );
4170 :
4171 : /* A completed full snapshot becomes the base of later incrementals. */
4172 0 : if( FD_LIKELY( success && !ctx->snapmk.incremental ) ) {
4173 0 : ctx->snapmk.base_slot = bank->f.slot;
4174 0 : }
4175 :
4176 0 : bank->refcnt--;
4177 0 : ctx->snapmk.active = 0;
4178 0 : }
4179 :
4180 : static void
4181 : msg_snapmk( fd_replay_tile_t * ctx,
4182 : fd_stem_context_t * stem,
4183 0 : ulong msg_type ) {
4184 0 : switch( msg_type ) {
4185 0 : case FD_SNAPMK_MSG_CREATED:
4186 0 : snapmk_done( ctx, stem, 1 );
4187 0 : break;
4188 0 : case FD_SNAPMK_MSG_FAILED:
4189 0 : snapmk_done( ctx, stem, 0 );
4190 0 : break;
4191 0 : default:
4192 0 : break;
4193 0 : }
4194 0 : }
4195 :
4196 : /* admin command handlers
4197 : every admin command must trigger one response frag */
4198 :
4199 : static void
4200 : admin_respond( fd_replay_tile_t * ctx,
4201 : fd_stem_context_t * stem,
4202 : ulong orig,
4203 0 : ulong err ) {
4204 0 : ulong ctl = fd_frag_meta_ctl( orig, 0, 0, !!err );
4205 0 : ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() );
4206 0 : fd_stem_publish( stem, ctx->admin_out_idx, err, 0UL, 0UL, ctl, 0UL, tspub );
4207 0 : }
4208 :
4209 : static void
4210 : admin_snap_create( fd_replay_tile_t * ctx,
4211 : fd_stem_context_t * stem,
4212 0 : ulong sig ) {
4213 0 : ulong target_slot = sig;
4214 :
4215 0 : if( FD_UNLIKELY( !ctx->snapmk.supported ) ) {
4216 0 : FD_LOG_WARNING(( "admin requested snapshot creation, but current config cannot create snapshots. increase [layout.snapzp_tile_count]?" ));
4217 0 : admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_ADMINCTL_RESULT_UNSUPPORTED );
4218 0 : return;
4219 0 : }
4220 :
4221 0 : if( FD_UNLIKELY( !ctx->is_booted ) ) {
4222 0 : FD_LOG_WARNING(( "admin requested snapshot creation, but client has not yet started" ));
4223 0 : admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_SNAPSHOT_CREATE_RESULT_NOT_READY );
4224 0 : return;
4225 0 : }
4226 :
4227 0 : if( FD_UNLIKELY( target_slot ) ) {
4228 0 : if( FD_UNLIKELY( target_slot<=ctx->published_root_slot ) ) {
4229 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 ));
4230 0 : admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_SNAPSHOT_CREATE_RESULT_SLOT_IN_PAST );
4231 0 : return;
4232 0 : }
4233 :
4234 :
4235 0 : if( FD_UNLIKELY( ctx->snapmk.scheduled_at_slot!=ULONG_MAX &&
4236 0 : ctx->snapmk.scheduled_at_slot!=target_slot ) ) {
4237 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_slot ));
4238 0 : admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_SNAPSHOT_CREATE_RESULT_BUSY );
4239 0 : return;
4240 0 : }
4241 :
4242 0 : ctx->snapmk.scheduled_at_slot = target_slot;
4243 0 : FD_LOG_NOTICE(( "snapshot creation scheduled at slot %lu", target_slot ));
4244 0 : admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_ADMINCTL_RESULT_SUCCESS );
4245 0 : return;
4246 0 : }
4247 :
4248 0 : if( FD_UNLIKELY( ctx->snapmk.active ) ) {
4249 0 : FD_LOG_WARNING(( "admin requested snapshot creation, but currently busy creating another snapshot. ignoring ..." ));
4250 0 : admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_SNAPSHOT_CREATE_RESULT_BUSY );
4251 0 : return;
4252 0 : }
4253 :
4254 0 : snapmk_start( ctx, stem, 0 );
4255 0 : admin_respond( ctx, stem, FD_ADMINCTL_CMD_SNAP_CREATE, FD_ADMINCTL_RESULT_SUCCESS );
4256 0 : }
4257 :
4258 : static void
4259 : msg_admin( fd_replay_tile_t * ctx,
4260 : fd_stem_context_t * stem,
4261 : ulong orig,
4262 0 : ulong sig ) {
4263 0 : switch( orig ) {
4264 0 : case FD_ADMINCTL_CMD_SNAP_CREATE:
4265 0 : admin_snap_create( ctx, stem, sig );
4266 0 : break;
4267 0 : default:
4268 0 : FD_LOG_CRIT(( "unknown admin cmd (orig=%lu, sig=%lu)", orig, sig ));
4269 0 : }
4270 0 : }
4271 :
4272 : static inline int
4273 : returnable_frag( fd_replay_tile_t * ctx,
4274 : ulong in_idx,
4275 : ulong seq,
4276 : ulong sig,
4277 : ulong chunk,
4278 : ulong sz,
4279 : ulong ctl,
4280 : ulong tsorig,
4281 : ulong tspub,
4282 0 : fd_stem_context_t * stem ) {
4283 0 : (void)seq;
4284 0 : (void)ctl;
4285 0 : (void)tsorig;
4286 0 : (void)tspub;
4287 :
4288 0 : if( FD_UNLIKELY( sz!=0UL && (chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) ) )
4289 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 ));
4290 :
4291 0 : switch( ctx->in_kind[in_idx] ) {
4292 0 : case IN_KIND_GENESIS: {
4293 0 : fd_genesis_meta_t const * meta = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
4294 0 : ctx->has_genesis_hash = 1;
4295 0 : ctx->has_genesis_timestamp = 1;
4296 0 : ctx->genesis_timestamp = meta->creation_time_seconds;
4297 0 : *ctx->genesis_hash = meta->genesis_hash;
4298 0 : fd_node_info_write_begin( ctx->node_info );
4299 0 : ctx->node_info->info.genesis_hash = *ctx->genesis_hash;
4300 0 : fd_node_info_write_end( ctx->node_info );
4301 0 : if( FD_LIKELY( meta->bootstrap ) ) {
4302 0 : boot_genesis( ctx, stem, meta );
4303 0 : } else {
4304 0 : uchar const * genesis_blob = (uchar const *)( meta+1 );
4305 0 : FD_TEST( fd_genesis_parse( ctx->genesis, genesis_blob, meta->blob_sz ) );
4306 0 : }
4307 0 : ctx->has_genesis_timestamp = 1;
4308 0 : ctx->genesis_timestamp = ctx->genesis->creation_time;
4309 :
4310 0 : maybe_verify_cluster_type( ctx );
4311 0 : maybe_verify_shred_version( ctx );
4312 0 : maybe_verify_genesis_timestamp( ctx );
4313 0 : break;
4314 0 : }
4315 0 : case IN_KIND_IPECHO: {
4316 0 : FD_TEST( sig && sig<=USHORT_MAX );
4317 0 : ctx->ipecho_shred_version = (ushort)sig;
4318 0 : maybe_verify_shred_version( ctx );
4319 0 : break;
4320 0 : }
4321 0 : case IN_KIND_SNAP: {
4322 0 : on_snapshot_message( ctx, stem, in_idx, chunk, sig );
4323 0 : maybe_verify_cluster_type( ctx );
4324 0 : maybe_verify_shred_version( ctx );
4325 0 : maybe_verify_genesis_timestamp( ctx );
4326 0 : break;
4327 0 : }
4328 0 : case IN_KIND_EXECRP: {
4329 0 : process_exec_task_done( ctx, stem, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ), sig );
4330 0 : ctx->execrp_idle_cnt = 0UL;
4331 0 : break;
4332 0 : }
4333 0 : case IN_KIND_POH: {
4334 0 : process_poh_message( ctx, stem, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
4335 0 : break;
4336 0 : }
4337 0 : case IN_KIND_RESOLV: {
4338 0 : fd_resolv_slot_exchanged_t * exchanged_slot = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
4339 0 : process_resolv_slot_completed( ctx, exchanged_slot->bank_idx );
4340 0 : break;
4341 0 : }
4342 0 : case IN_KIND_TOWER: {
4343 0 : if( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_DONE ) ) {
4344 0 : process_tower_slot_done( ctx, stem, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ), seq );
4345 0 : } else if( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_CONFIRMED ) ) {
4346 0 : fd_tower_slot_confirmed_t const * msg = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
4347 0 : if( msg->level==FD_TOWER_SLOT_CONFIRMED_OPTIMISTIC && !msg->fwd ) process_tower_optimistic_confirmed( ctx, stem, msg );
4348 0 : if( msg->level==FD_TOWER_SLOT_CONFIRMED_DUPLICATE ) fd_reasm_confirm( ctx->reasm, &msg->block_id );
4349 0 : } else if( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_IGNORED ) ) {
4350 0 : fd_tower_slot_ignored_t const * msg = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
4351 0 : fd_tower_slot_done_t ignored = {
4352 0 : .replay_slot = msg->slot,
4353 0 : .replay_bank_idx = msg->bank_idx,
4354 0 : .vote_slot = ULONG_MAX,
4355 0 : .reset_slot = ctx->reset_slot, /* Use most recent reset slot */
4356 0 : .reset_block_id = ctx->reset_cmr,
4357 0 : .root_slot = ULONG_MAX
4358 0 : };
4359 0 : process_tower_slot_done( ctx, stem, &ignored, seq );
4360 0 : }
4361 0 : break;
4362 0 : }
4363 0 : case IN_KIND_VOTOR: {
4364 0 : if( FD_LIKELY( sig==FD_VOTOR_SIG_ROOTED ) ) {
4365 0 : fd_votor_rooted_t const * msg = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
4366 0 : FD_TEST( msg->slot>ctx->consensus_root_slot );
4367 0 : ctx->consensus_root_slot = msg->slot;
4368 0 : ctx->consensus_root = msg->block_id;
4369 0 : if( FD_UNLIKELY( ctx->next_leader_slot!=ULONG_MAX && msg->slot>ctx->votor_leader->parent_slot ) ) ctx->next_leader_slot = ULONG_MAX;
4370 0 : } else if( FD_UNLIKELY( sig==FD_VOTOR_SIG_LEADER ) ) {
4371 0 : fd_votor_leader_t const * leader = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
4372 0 : *ctx->votor_leader = *leader;
4373 0 : ctx->next_leader_slot = leader->slot;
4374 0 : try_become_leader_ag( ctx, stem );
4375 0 : } else if( FD_UNLIKELY( sig==FD_VOTOR_SIG_CERTED ) ) {
4376 0 : fd_votor_certed_t const * certed = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
4377 0 : fd_votor_certed_t * fin = ctx->votor_final;
4378 0 : switch( certed->kind ) {
4379 0 : case AG_CERT_KIND_FINAL:
4380 0 : if( fin->slot==ULONG_MAX || certed->slot>fin->slot ) *fin = *certed;
4381 0 : break;
4382 0 : case AG_CERT_KIND_FAST_FINAL: /* fast beats slow at the same slot */
4383 0 : if( fin->slot==ULONG_MAX || certed->slot>fin->slot || ( certed->slot==fin->slot && fin->kind==AG_CERT_KIND_FINAL ) ) *fin = *certed;
4384 0 : break;
4385 0 : default: break;
4386 0 : }
4387 0 : } else if( FD_UNLIKELY( sig==FD_VOTOR_SIG_REWARD ) ) {
4388 0 : fd_votor_reward_t const * reward = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
4389 0 : fd_votor_reward_t * ring = &ctx->votor_reward[ reward->slot%(FD_NUM_SLOTS_FOR_REWARD+AG_SLOTS_PER_WINDOW+1UL) ];
4390 0 : if( ring->slot==ULONG_MAX || reward->slot>=ring->slot ) *ring = *reward;
4391 0 : }
4392 0 : break;
4393 0 : }
4394 0 : case IN_KIND_REPAIR: {
4395 : /* Store and reasm follow the invariant that any FEC in the
4396 : shred->out link, repair->out link, or reasm must be present in
4397 : store. If any FEC is rejected at this point, it must be
4398 : removed from store. See topology.c for more details. */
4399 0 : if( FD_UNLIKELY( sig==REPAIR_SIG_FEC || sig==REPAIR_SIG_FEC_LEADER || sig==REPAIR_SIG_FEC_INVALID ) ) {
4400 0 : process_fec_complete( ctx, sig, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
4401 0 : }
4402 0 : if( FD_UNLIKELY( sig!=ROTOR_SIG_FEC_REPLAY ) ) break;
4403 :
4404 : /* process rotor incoming FECs. 1 to keep frag for retry, returning 0 consumes it. */
4405 :
4406 0 : fd_rotor_replay_fec_t * fec = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
4407 0 : if( FD_UNLIKELY( !( ctx->execrp_idle_cnt>=2UL*ctx->in_cnt || ctx->is_leader ) ) ) return 1; /* pace behind exec notifications, retry */
4408 :
4409 0 : int evict_banks = 0;
4410 0 : int res = can_process_rotor_fec( ctx, fec, &evict_banks );
4411 :
4412 : /* drain_rotor_fecs: a bank eviction broke the replayable chain of
4413 : FECs delivered from rotor (we lost a parent we needed to
4414 : replay off of). While draining, ignore DROP/SKIP FECs until
4415 : one is replayable again (PROCESS_FEC_OK, i.e. its parent
4416 : context is present), then resume normal processing. WAIT must
4417 : fall through to the retry/eviction logic below: WAIT's contract
4418 : is keep-and-retry, and the redelivered FEC that rebuilds the
4419 : evicted block can itself return WAIT+evict_banks (banks full) --
4420 : swallowing it would consume the frag, never queue the eviction,
4421 : and wedge recovery. */
4422 0 : if( FD_UNLIKELY( ctx->drain_rotor_fecs ) ) {
4423 0 : if( FD_LIKELY( res==PROCESS_FEC_OK ) ) {
4424 0 : ctx->drain_rotor_fecs = 0; /* chain re-established, resume */
4425 0 : }
4426 0 : else if( res!=PROCESS_FEC_WAIT ) return 0; /* swallow DROPs/SKIPs, do evict_banks if needed */
4427 0 : }
4428 :
4429 0 : switch( res ) {
4430 0 : case PROCESS_FEC_OK: {
4431 0 : process_rotor_fec( ctx, stem, fec );
4432 0 : return 0;
4433 0 : }
4434 0 : case PROCESS_FEC_DROP: {
4435 : /* enter drain state */
4436 0 : ctx->drain_rotor_fecs = 1;
4437 0 : fd_stem_publish( stem, ctx->replay_out->idx, REPLAY_SIG_MISSING_FEC, ctx->replay_out->chunk, 0, 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) );
4438 0 : ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, 0, ctx->replay_out->chunk0, ctx->replay_out->wmark );
4439 0 : return 0;
4440 0 : }
4441 0 : case PROCESS_FEC_SKIP: { return 0; }
4442 0 : case PROCESS_FEC_WAIT: {
4443 : /* queue an eviction, then retry the frag. */
4444 0 : if( FD_UNLIKELY( evict_banks ) ) {
4445 0 : ulong evictable_bank_idx = fd_banks_get_evictable_bank( ctx->banks, ctx->notified_root_bank );
4446 0 : if( FD_UNLIKELY( evictable_bank_idx==ULONG_MAX ) ) {
4447 0 : FD_LOG_DEBUG(( "replay has no banks to mark as prunable, it's possible that there is one bank already marked as prunable" ));
4448 0 : return 1;
4449 0 : }
4450 :
4451 0 : FD_LOG_WARNING(( "banks full, evicting bank (idx=%lu)", evictable_bank_idx ));
4452 :
4453 0 : timing_slot_release( ctx, evictable_bank_idx );
4454 :
4455 0 : if( FD_UNLIKELY( fd_sched_block_is_discarded( ctx->sched, evictable_bank_idx ) ) ) {
4456 0 : fd_block_id_ele_t * ele = &ctx->block_id_arr[ evictable_bank_idx ];
4457 0 : report_block_incomplete( ctx, ele->slot, ctx->alpenglow && ele->block_id_seen ? &ele->dmr : &ele->latest_mr, fd_banks_bank_query( ctx->banks, evictable_bank_idx ),
4458 0 : FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED );
4459 0 : }
4460 :
4461 : /* Send a notification to other tiles to drop a reference to the
4462 : evictable bank. The RPC tile is the only tile which holds onto
4463 : non-rooted banks, non-transiently. */
4464 0 : fd_replay_drop_bank_ref_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk );
4465 0 : fd_sched_block_abandon( ctx->sched, evictable_bank_idx, FD_SCHED_ABANDON_DISCARDED );
4466 0 : msg->bank_idx = evictable_bank_idx;
4467 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() ) );
4468 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 );
4469 0 : }
4470 0 : return 1;
4471 0 : }
4472 0 : default:
4473 0 : FD_LOG_CRIT(( "unhandled process_rotor_fec result: %d", res ));
4474 0 : }
4475 0 : break;
4476 0 : }
4477 0 : case IN_KIND_TXSEND: {
4478 0 : process_vote_txn_sent( ctx, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
4479 0 : break;
4480 0 : }
4481 0 : case IN_KIND_GOSSIP_OUT: {
4482 0 : FD_TEST( sig==FD_GOSSIP_UPDATE_TAG_WFS_DONE );
4483 0 : ctx->wfs_complete = 1;
4484 0 : maybe_verify_shred_version( ctx );
4485 :
4486 : /* Recalculate next_leader_tickcount relative to now. The
4487 : original value was computed at boot time (in boot_genesis or
4488 : on_snapshot_message). */
4489 0 : ctx->next_leader_tickcount = LONG_MAX;
4490 0 : if( FD_LIKELY( ctx->next_leader_slot!=ULONG_MAX ) ) {
4491 0 : fd_block_id_ele_t * block_id_ele = fd_block_id_ele_query( ctx, ctx->alpenglow ? &ctx->reset_dmr : &ctx->reset_cmr, ctx->reset_slot );
4492 0 : if( FD_LIKELY( block_id_ele ) ) {
4493 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 ) );
4494 0 : if( FD_LIKELY( reset_bank && reset_bank->bank_seq==block_id_ele->bank_seq && reset_bank->state!=FD_BANK_STATE_PRUNABLE ) ) {
4495 0 : double slot_duration_ticks = (double)reset_bank->f.slot_params.ns_per_slot_adjusted*ctx->tick_per_ns;
4496 0 : ctx->next_leader_tickcount = (long)((double)(ctx->next_leader_slot-ctx->reset_slot-1UL)*slot_duration_ticks) + fd_tickcount();
4497 0 : }
4498 0 : }
4499 0 : }
4500 :
4501 0 : FD_LOG_NOTICE(( "Done waiting for supermajority. More than 80 percent of cluster stake has joined." ));
4502 0 : if( FD_LIKELY( ctx->replay_out->idx!=ULONG_MAX ) ) {
4503 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() ) );
4504 0 : }
4505 0 : break;
4506 0 : }
4507 0 : case IN_KIND_RPC: {
4508 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, sig );
4509 0 : FD_TEST( bank );
4510 0 : bank->refcnt--;
4511 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" ));
4512 0 : break;
4513 0 : }
4514 0 : case IN_KIND_SNAPMK:
4515 0 : msg_snapmk( ctx, stem, sig );
4516 0 : break;
4517 0 : case IN_KIND_ADMIN:
4518 0 : msg_admin( ctx, stem, fd_frag_meta_ctl_orig( ctl ), sig );
4519 0 : break;
4520 0 : default:
4521 0 : FD_LOG_ERR(( "unhandled kind %d", ctx->in_kind[ in_idx ] ));
4522 0 : }
4523 :
4524 0 : return 0;
4525 0 : }
4526 :
4527 : #undef PROCESS_FEC_DROP
4528 : #undef PROCESS_FEC_WAIT
4529 : #undef PROCESS_FEC_SKIP
4530 : #undef PROCESS_FEC_OK
4531 :
4532 : static inline fd_replay_out_link_t
4533 : out1( fd_topo_t const * topo,
4534 : fd_topo_tile_t const * tile,
4535 0 : char const * name ) {
4536 0 : ulong idx = ULONG_MAX;
4537 :
4538 0 : for( ulong i=0UL; i<tile->out_cnt; i++ ) {
4539 0 : fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ];
4540 0 : if( !strcmp( link->name, name ) ) {
4541 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 ));
4542 0 : idx = i;
4543 0 : }
4544 0 : }
4545 :
4546 0 : if( FD_UNLIKELY( idx==ULONG_MAX ) ) return (fd_replay_out_link_t){ .idx = ULONG_MAX, .mem = NULL, .chunk0 = 0, .wmark = 0, .chunk = 0 };
4547 :
4548 0 : void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp;
4549 0 : ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache );
4550 0 : ulong wmark = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, topo->links[ tile->out_link_id[ idx ] ].mtu );
4551 :
4552 0 : return (fd_replay_out_link_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0 };
4553 0 : }
4554 :
4555 : static void
4556 : privileged_init( fd_topo_t const * topo,
4557 0 : fd_topo_tile_t const * tile ) {
4558 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
4559 :
4560 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
4561 0 : fd_replay_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_tile_t), sizeof(fd_replay_tile_t) );
4562 :
4563 0 : if( FD_UNLIKELY( !strcmp( tile->replay.identity_key_path, "" ) ) ) FD_LOG_ERR(( "identity_key_path not set" ));
4564 :
4565 0 : ctx->identity_pubkey[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->replay.identity_key_path, /* pubkey only: */ 1 ) );
4566 0 : ctx->identity_idx = 0UL;
4567 0 : ctx->identity_dirty = 0;
4568 :
4569 0 : ctx->metrics.voted_slot = ULONG_MAX;
4570 :
4571 0 : ctx->has_vote_account = tile->replay.alpenglow && !!tile->replay.vote_account_path[ 0 ];
4572 0 : if( FD_LIKELY( ctx->has_vote_account ) ) {
4573 0 : if( FD_UNLIKELY( !fd_base58_decode_32( tile->replay.vote_account_path, ctx->vote_account->uc ) ) ) {
4574 0 : uchar const * vote_key = fd_keyload_load( tile->replay.vote_account_path, /* pubkey only: */ 1 );
4575 0 : fd_memcpy( ctx->vote_account->uc, vote_key, sizeof(fd_pubkey_t) );
4576 0 : }
4577 0 : }
4578 :
4579 0 : ctx->bundle.enabled = tile->replay.bundle.enabled;
4580 0 : if( FD_UNLIKELY( !tile->replay.bundle.vote_account_path[0] ) ) {
4581 0 : ctx->bundle.enabled = 0;
4582 0 : }
4583 :
4584 0 : if( FD_UNLIKELY( ctx->bundle.enabled ) ) {
4585 0 : if( FD_UNLIKELY( !fd_base58_decode_32( tile->replay.bundle.vote_account_path, ctx->bundle.vote_account.uc ) ) ) {
4586 0 : const uchar * vote_key = fd_keyload_load( tile->replay.bundle.vote_account_path, /* pubkey only: */ 1 );
4587 0 : fd_memcpy( ctx->bundle.vote_account.uc, vote_key, 32UL );
4588 0 : }
4589 0 : }
4590 :
4591 0 : FD_TEST( fd_rng_secure( &ctx->rng_seed, sizeof(ctx->rng_seed) ) );
4592 0 : FD_TEST( fd_rng_secure( &ctx->blockhash_seed, sizeof(ulong) ) );
4593 0 : FD_TEST( fd_rng_secure( &ctx->reasm_seed, sizeof(ulong) ) );
4594 0 : FD_TEST( fd_rng_secure( &ctx->vote_tracker_seed, sizeof(ulong) ) );
4595 0 : FD_TEST( fd_rng_secure( &ctx->block_id_map_seed, sizeof(ulong) ) );
4596 0 : FD_TEST( fd_rng_secure( &ctx->ag_block_id_map_seed, sizeof(ulong) ) );
4597 0 : FD_TEST( fd_rng_secure( &ctx->initial_block_id, sizeof(fd_hash_t) ) );
4598 0 : FD_TEST( fd_rng_secure( &ctx->runtime_stack_seed, sizeof(ulong) ) );
4599 :
4600 0 : ctx->store_disk_fd = -1;
4601 0 : ulong store_obj_id = fd_pod_query_ulong( topo->props, "store", ULONG_MAX );
4602 0 : if( FD_LIKELY( store_obj_id!=ULONG_MAX ) ) {
4603 0 : fd_store_t * store = fd_store_join( fd_topo_obj_laddr( topo, store_obj_id ) );
4604 0 : FD_TEST( store && store->magic==FD_STORE_MAGIC );
4605 0 : ctx->store_disk_fd = FD_STORE_FD_RO;
4606 0 : if( FD_UNLIKELY( fcntl( ctx->store_disk_fd, F_GETFD )<0 ) )
4607 0 : FD_LOG_ERR(( "store file descriptor was not inherited (%i-%s)", errno, fd_io_strerror( errno ) ));
4608 0 : }
4609 0 : }
4610 :
4611 : static void
4612 : unprivileged_init( fd_topo_t const * topo,
4613 0 : fd_topo_tile_t const * tile ) {
4614 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
4615 0 : void * reasm_mem = NULL;
4616 0 : void * block_id_map_mem = NULL;
4617 0 : ulong chain_cnt = fd_block_id_map_chain_cnt_est( tile->replay.max_live_slots );
4618 :
4619 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
4620 0 : fd_replay_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_tile_t), sizeof(fd_replay_tile_t) );
4621 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 ) );
4622 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 );
4623 0 : void * fec_chain_mem = tile->replay.report_runtime_diffs ?
4624 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_hash_t), sizeof(fd_hash_t) * FD_FEC_BLK_MAX * tile->replay.max_live_slots ) : NULL;
4625 0 : void * slot_diffs_mem = tile->replay.report_runtime_diffs ?
4626 0 : FD_SCRATCH_ALLOC_APPEND( l, 8UL, FD_EVENT_RUNTIME_SLOT_DIFFS_FOOTPRINT * tile->replay.max_live_slots ) : NULL;
4627 0 : if( !tile->replay.alpenglow ) {
4628 0 : block_id_map_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_block_id_map_align(), fd_block_id_map_footprint( chain_cnt ) );
4629 0 : } else {
4630 0 : block_id_map_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_ag_block_id_map_align(), fd_ag_block_id_map_footprint( chain_cnt ) );
4631 0 : }
4632 0 : void * _txncache = FD_SCRATCH_ALLOC_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->replay.max_live_slots ) );
4633 0 : void * _accdb = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->replay.max_live_slots ) );
4634 0 : if( !tile->replay.alpenglow ) {
4635 0 : reasm_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_reasm_align(), fd_reasm_footprint( tile->replay.fec_max ) );
4636 0 : }
4637 0 : void * recp_stats_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_reception_stats_t), sizeof(fd_reception_stats_t)*tile->replay.max_live_slots );
4638 0 : void * sched_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_sched_align(), fd_sched_footprint( tile->replay.sched_depth, tile->replay.max_live_slots, tile->replay.max_shreds_per_block, tile->replay.max_txn_per_slot ) );
4639 0 : void * vote_tracker_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_tracker_align(), fd_vote_tracker_footprint() );
4640 0 : void * _capture_ctx = FD_SCRATCH_ALLOC_APPEND( l, fd_capture_ctx_align(), fd_capture_ctx_footprint() );
4641 0 : void * dump_proto_ctx_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_dump_proto_ctx_t), sizeof(fd_dump_proto_ctx_t) );
4642 0 : void * block_completed_ev = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_event_block_completed_t), sizeof(fd_event_block_completed_t) );
4643 0 : void * timing_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_timing_slot_pool_align(), fd_timing_slot_pool_footprint( FD_REPLAY_TXN_TIMING_SLOTS ) );
4644 0 : void * timing_rec_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_txn_timing_t), FD_REPLAY_TXN_TIMING_SLOTS*tile->replay.max_txn_per_slot*sizeof(fd_replay_txn_timing_t) );
4645 0 : void * timing_of_bank_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), tile->replay.max_live_slots*sizeof(ulong) );
4646 0 : void * backfill_path_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_reasm_fec_t *), (tile->replay.max_shreds_per_block/FD_FEC_SHRED_CNT)*sizeof(fd_reasm_fec_t *) );
4647 0 : void * block_dump_ctx = NULL;
4648 0 : if( FD_UNLIKELY( tile->replay.dump_block_to_pb ) ) {
4649 0 : block_dump_ctx = FD_SCRATCH_ALLOC_APPEND( l, fd_block_dump_context_align(), fd_block_dump_context_footprint() );
4650 0 : }
4651 :
4652 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 ) );
4653 0 : FD_TEST( ctx->runtime_stack );
4654 :
4655 0 : ctx->wksp = topo->workspaces[ topo->objs[ tile->tile_obj_id ].wksp_id ].wksp;
4656 :
4657 0 : ulong store_obj_id = fd_pod_query_ulong( topo->props, "store", ULONG_MAX );
4658 0 : FD_TEST( store_obj_id!=ULONG_MAX );
4659 0 : ctx->store = fd_store_join( fd_topo_obj_laddr( topo, store_obj_id ) );
4660 0 : FD_TEST( ctx->store );
4661 0 : FD_TEST( fd_store_map_ljoin( ctx->store, ctx->map_join ) );
4662 :
4663 0 : ulong banks_obj_id = fd_pod_query_ulong( topo->props, "banks", ULONG_MAX );
4664 0 : FD_TEST( banks_obj_id!=ULONG_MAX );
4665 :
4666 0 : ctx->banks = fd_banks_join( fd_topo_obj_laddr( topo, banks_obj_id ) );
4667 0 : FD_TEST( ctx->banks );
4668 :
4669 0 : ctx->leader_txn_timing = NULL;
4670 0 : ulong ldr_tt_obj_id = fd_pod_query_ulong( topo->props, "ldr_tt", ULONG_MAX );
4671 0 : if( FD_LIKELY( ldr_tt_obj_id!=ULONG_MAX ) ) ctx->leader_txn_timing = fd_topo_obj_laddr( topo, ldr_tt_obj_id );
4672 :
4673 0 : ulong node_info_obj_id = fd_pod_query_ulong( topo->props, "node_info", ULONG_MAX );
4674 0 : FD_TEST( node_info_obj_id!=ULONG_MAX );
4675 0 : ctx->node_info = fd_node_info_box_join( fd_topo_obj_laddr( topo, node_info_obj_id ) );
4676 0 : FD_TEST( ctx->node_info );
4677 0 : fd_node_info_write_begin( ctx->node_info );
4678 0 : ctx->node_info->info.identity = *ctx->identity_pubkey;
4679 0 : fd_node_info_write_end( ctx->node_info );
4680 :
4681 0 : FD_MGAUGE_SET( REPLAY, BANK_LIVE_MAX, fd_banks_pool_max_cnt( ctx->banks ) );
4682 :
4683 0 : ctx->consensus_root_slot = ULONG_MAX;
4684 0 : ctx->consensus_root = ctx->initial_block_id;
4685 0 : ctx->notified_root_slot = ULONG_MAX;
4686 0 : ctx->notified_root = ctx->initial_block_id;
4687 0 : ctx->notified_root_bank = NULL;
4688 0 : ctx->published_root_slot = ULONG_MAX;
4689 :
4690 0 : ctx->expected_shred_version = tile->replay.expected_shred_version;
4691 0 : ctx->ipecho_shred_version = 0;
4692 0 : ctx->shred_version = 0;
4693 0 : fd_memcpy( ctx->genesis_path, tile->replay.genesis_path, sizeof(ctx->genesis_path) );
4694 0 : ctx->has_genesis_hash = 0;
4695 0 : ctx->has_cluster_type = 0;
4696 0 : ctx->has_genesis_timestamp = 0;
4697 0 : ctx->has_expected_genesis_timestamp = 0;
4698 0 : ctx->cluster_type = FD_CLUSTER_UNKNOWN;
4699 0 : ctx->hard_fork_cnt = ULONG_MAX;
4700 0 : ctx->has_manifest_block_id = 0;
4701 :
4702 0 : if( FD_UNLIKELY( ctx->bundle.enabled ) ) {
4703 0 : if( FD_UNLIKELY( !fd_bundle_crank_gen_init( ctx->bundle.gen,
4704 0 : (fd_acct_addr_t const *)tile->replay.bundle.tip_distribution_program_addr,
4705 0 : (fd_acct_addr_t const *)tile->replay.bundle.tip_payment_program_addr,
4706 0 : (fd_acct_addr_t const *)ctx->bundle.vote_account.uc,
4707 0 : (fd_acct_addr_t const *)ctx->bundle.vote_account.uc, "NAN", 0UL ) ) ) {
4708 0 : FD_LOG_ERR(( "failed to initialize bundle crank gen" ));
4709 0 : }
4710 0 : }
4711 :
4712 0 : FD_TEST( tile->replay.enable_features_cnt<=sizeof(ctx->enable_features)/sizeof(ctx->enable_features[0]) );
4713 0 : ctx->enable_features_cnt = tile->replay.enable_features_cnt;
4714 0 : for( ulong i=0UL; i<tile->replay.enable_features_cnt; i++ ) {
4715 0 : fd_memcpy( ctx->enable_features[ i ], tile->replay.enable_features[ i ], FD_BASE58_ENCODED_32_SZ );
4716 0 : }
4717 :
4718 0 : ulong progcache_obj_id; FD_TEST( (progcache_obj_id = fd_pod_query_ulong( topo->props, "progcache", ULONG_MAX ) )!=ULONG_MAX );
4719 0 : FD_TEST( fd_progcache_shmem_join( ctx->progcache, fd_topo_obj_laddr( topo, progcache_obj_id ) ) );
4720 :
4721 0 : void * _txncache_shmem = fd_topo_obj_laddr( topo, tile->replay.txncache_obj_id );
4722 0 : fd_txncache_shmem_t * txncache_shmem = fd_txncache_shmem_join( _txncache_shmem );
4723 0 : FD_TEST( txncache_shmem );
4724 0 : ctx->txncache = fd_txncache_join( fd_txncache_new( _txncache, txncache_shmem ) );
4725 0 : FD_TEST( ctx->txncache );
4726 :
4727 0 : void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->replay.accdb_obj_id );
4728 0 : fd_accdb_shmem_t * accdb_shmem = fd_accdb_shmem_join( _accdb_shmem );
4729 0 : FD_TEST( accdb_shmem );
4730 0 : ctx->accdb = fd_accdb_join( fd_accdb_new( _accdb, accdb_shmem, FD_ACCDB_FD_RW, 0UL, NULL ) );
4731 0 : FD_TEST( ctx->accdb );
4732 :
4733 0 : ctx->capture_ctx = NULL;
4734 0 : if( FD_UNLIKELY( strcmp( "", tile->replay.solcap_capture ) ) ) {
4735 0 : ctx->capture_ctx = fd_capture_ctx_join( fd_capture_ctx_new( _capture_ctx ) );
4736 0 : ctx->capture_ctx->solcap_start_slot = tile->replay.capture_start_slot;
4737 0 : ctx->capture_ctx->capture_solcap = 1;
4738 0 : }
4739 :
4740 0 : ctx->block_completed_event = block_completed_ev;
4741 :
4742 0 : ctx->timing_slot_pool = fd_timing_slot_pool_join( fd_timing_slot_pool_new( timing_pool_mem, FD_REPLAY_TXN_TIMING_SLOTS ) );
4743 0 : FD_TEST( ctx->timing_slot_pool );
4744 0 : ctx->timing_rec = timing_rec_mem;
4745 0 : ctx->timing_slot_of_bank = timing_of_bank_mem;
4746 0 : for( ulong i=0UL; i<tile->replay.max_live_slots; i++ ) ctx->timing_slot_of_bank[ i ] = fd_timing_slot_pool_idx_null( ctx->timing_slot_pool );
4747 0 : ctx->backfill_path = backfill_path_mem;
4748 :
4749 0 : ctx->dump_proto_ctx = NULL;
4750 0 : if( FD_UNLIKELY( strcmp( "", tile->replay.dump_proto_dir ) ) ) {
4751 0 : ctx->dump_proto_ctx = dump_proto_ctx_mem;
4752 0 : ctx->dump_proto_ctx->dump_proto_output_dir = tile->replay.dump_proto_dir;
4753 0 : if( FD_LIKELY( tile->replay.dump_block_to_pb ) ) {
4754 0 : ctx->dump_proto_ctx->dump_block_to_pb = !!tile->replay.dump_block_to_pb;
4755 0 : }
4756 0 : }
4757 :
4758 0 : if( FD_UNLIKELY( tile->replay.dump_block_to_pb ) ) {
4759 0 : ctx->block_dump_ctx = fd_block_dump_context_join( fd_block_dump_context_new( block_dump_ctx ) );
4760 0 : } else {
4761 0 : ctx->block_dump_ctx = NULL;
4762 0 : }
4763 :
4764 0 : ctx->is_booted = 0;
4765 :
4766 0 : ctx->tick_per_ns = fd_tempo_tick_per_ns( NULL );
4767 :
4768 0 : fd_clock_tile_init( ctx->clock );
4769 :
4770 0 : ctx->max_txn_per_slot = tile->replay.max_txn_per_slot;
4771 0 : ctx->max_shreds_per_block = tile->replay.max_shreds_per_block;
4772 :
4773 0 : FD_TEST( fd_rng_new( ctx->rng, ctx->rng_seed, 0UL ) );
4774 :
4775 0 : if( !tile->replay.alpenglow ) {
4776 0 : ctx->reasm = fd_reasm_join( fd_reasm_new( reasm_mem, tile->replay.fec_max, ctx->reasm_seed ) );
4777 0 : } else {
4778 0 : ctx->reasm = NULL;
4779 0 : }
4780 0 : ctx->reception_stats = recp_stats_mem;
4781 0 : ctx->reception_stats_cnt = tile->replay.max_live_slots;
4782 0 : FD_TEST( ctx->reception_stats_cnt );
4783 0 : for( ulong i=0UL; i<ctx->reception_stats_cnt; i++ ) ctx->reception_stats[ i ].slot = ULONG_MAX;
4784 0 : ctx->reasm_evicted = NULL;
4785 :
4786 0 : ctx->leader_stats.slot = ULONG_MAX;
4787 0 : ctx->alpenglow = tile->replay.alpenglow;
4788 0 : ctx->sched = fd_sched_join( fd_sched_new( sched_mem, ctx->rng, tile->replay.sched_depth, tile->replay.max_live_slots, ctx->max_shreds_per_block, ctx->max_txn_per_slot, fd_topo_tile_name_cnt( topo, "execrp" ), ctx->alpenglow ) );
4789 0 : FD_TEST( ctx->sched );
4790 0 : FD_TEST( ctx->alpenglow || ctx->reasm );
4791 :
4792 0 : ctx->in_cnt = tile->in_cnt;
4793 0 : ctx->execrp_idle_cnt = 0UL;
4794 :
4795 0 : ctx->vote_tracker = fd_vote_tracker_join( fd_vote_tracker_new( vote_tracker_mem, ctx->vote_tracker_seed ) );
4796 0 : FD_TEST( ctx->vote_tracker );
4797 :
4798 0 : ctx->identity_vote_rooted = 0;
4799 :
4800 0 : ctx->wait_for_vote_to_start_leader = tile->replay.wait_for_vote_to_start_leader;
4801 :
4802 0 : ctx->wfs_enabled = memcmp( tile->replay.wait_for_supermajority_with_bank_hash.uc, ((fd_pubkey_t){ 0 }).uc, sizeof(fd_pubkey_t) );
4803 0 : ctx->expected_bank_hash = tile->replay.wait_for_supermajority_with_bank_hash;
4804 0 : ctx->wfs_complete = !ctx->wfs_enabled;
4805 :
4806 0 : ctx->mleaders = fd_multi_epoch_leaders_join( fd_multi_epoch_leaders_new( ctx->mleaders_mem ) );
4807 0 : FD_TEST( ctx->mleaders );
4808 :
4809 0 : ctx->is_leader = 0;
4810 0 : ctx->drain_rotor_fecs = 0;
4811 0 : ctx->supports_leader = fd_topo_find_tile( topo, "pack", 0UL )!=ULONG_MAX;
4812 0 : ctx->snapmk.active = 0;
4813 0 : ctx->snapmk.supported = fd_topo_find_tile( topo, "snapmk", 0UL )!=ULONG_MAX;
4814 0 : ctx->snapmk.scheduled_at_slot = ULONG_MAX;
4815 0 : ctx->snapmk.full_interval_blocks = tile->replay.full_snapshot_interval_blocks;
4816 0 : ctx->snapmk.next_full_block_height = ULONG_MAX;
4817 0 : ctx->snapmk.incremental_interval_blocks = tile->replay.incremental_snapshot_interval_blocks;
4818 0 : ctx->snapmk.next_incremental_block_height = ULONG_MAX;
4819 0 : ctx->snapmk.base_slot = ULONG_MAX;
4820 0 : if( FD_UNLIKELY( !ctx->snapmk.supported ) ) {
4821 0 : ctx->snapmk.full_interval_blocks = 0UL;
4822 0 : ctx->snapmk.incremental_interval_blocks = 0UL;
4823 0 : }
4824 0 : ctx->reset_slot = 0UL;
4825 0 : ctx->reset_cmr = ctx->initial_block_id;
4826 0 : ctx->reset_dmr = ctx->initial_block_id;
4827 0 : ctx->reset_timestamp_nanos = 0UL;
4828 0 : ctx->next_leader_slot = ULONG_MAX;
4829 0 : ctx->next_leader_tickcount = LONG_MAX;
4830 0 : ctx->highwater_leader_slot = ULONG_MAX;
4831 :
4832 0 : ctx->votor_final->slot = ULONG_MAX;
4833 0 : for( ulong i=0UL; i<FD_NUM_SLOTS_FOR_REWARD+AG_SLOTS_PER_WINDOW+1UL; i++ ) ctx->votor_reward[ i ].slot = ULONG_MAX;
4834 :
4835 0 : ctx->caught_up = 0;
4836 0 : ctx->catch_up_max_fec_slot = ULONG_MAX;
4837 0 : ctx->catch_up_tip_advance_cnt = 0UL;
4838 0 : ctx->boot_timestamp_nanos = tile->replay.boot_timestamp_nanos;
4839 0 : ctx->leader_bank = NULL;
4840 :
4841 0 : ctx->block_id_len = tile->replay.max_live_slots;
4842 0 : ctx->max_live_slots = tile->replay.max_live_slots;
4843 0 : ctx->block_id_arr = (fd_block_id_ele_t *)block_id_arr_mem;
4844 :
4845 0 : ctx->fec_chain = (fd_hash_t *)fec_chain_mem;
4846 0 : if( FD_UNLIKELY( slot_diffs_mem ) ) fd_event_runtime_slot_diffs_init( slot_diffs_mem, tile->replay.max_live_slots );
4847 :
4848 0 : if( !tile->replay.alpenglow ) {
4849 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 ) );
4850 0 : FD_TEST( ctx->block_id_map );
4851 0 : ctx->ag_block_id_map = NULL;
4852 0 : } else {
4853 0 : ctx->ag_block_id_map = fd_ag_block_id_map_join( fd_ag_block_id_map_new( block_id_map_mem, chain_cnt, ctx->ag_block_id_map_seed ) );
4854 0 : FD_TEST( ctx->ag_block_id_map );
4855 0 : ctx->block_id_map = NULL;
4856 0 : }
4857 :
4858 0 : for( ulong i=0UL; i<tile->replay.max_live_slots; i++ ) {
4859 0 : ctx->block_id_arr[ i ].block_id_seen = 0;
4860 0 : memset( &ctx->block_id_arr[ i ].block_info, 0, sizeof(ag_block_id_t) );
4861 0 : }
4862 :
4863 0 : ctx->resolv_tile_cnt = fd_topo_tile_name_cnt( topo, "resolv" );
4864 :
4865 0 : ctx->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
4866 0 : FD_TEST( ctx->keyswitch );
4867 0 : ctx->halt_leader = 0;
4868 :
4869 0 : FD_TEST( tile->in_cnt<=sizeof(ctx->in)/sizeof(ctx->in[0]) );
4870 0 : for( ulong i=0UL; i<tile->in_cnt; i++ ) {
4871 0 : fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
4872 0 : fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
4873 :
4874 0 : if( FD_LIKELY( link->dcache ) ) {
4875 0 : ctx->in[ i ].mem = link_wksp->wksp;
4876 0 : ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
4877 0 : ctx->in[ i ].wmark = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
4878 0 : ctx->in[ i ].mtu = link->mtu;
4879 0 : }
4880 :
4881 0 : if( !strcmp( link->name, "genesi_out" ) ) ctx->in_kind[ i ] = IN_KIND_GENESIS;
4882 0 : else if( !strcmp( link->name, "ipecho_out" ) ) ctx->in_kind[ i ] = IN_KIND_IPECHO;
4883 0 : else if( !strcmp( link->name, "snapin_manif" ) ) ctx->in_kind[ i ] = IN_KIND_SNAP;
4884 0 : else if( !strcmp( link->name, "execrp_replay" ) ) ctx->in_kind[ i ] = IN_KIND_EXECRP;
4885 0 : else if( !strcmp( link->name, "poh_replay" ) ) ctx->in_kind[ i ] = IN_KIND_POH;
4886 0 : else if( !strcmp( link->name, "resolv_replay" ) ) ctx->in_kind[ i ] = IN_KIND_RESOLV;
4887 0 : else if( !strcmp( link->name, "shred_out" ) ) ctx->in_kind[ i ] = IN_KIND_REPAIR;
4888 0 : else if( !strcmp( link->name, "repair_out" ) ) ctx->in_kind[ i ] = IN_KIND_REPAIR;
4889 0 : else if( !strcmp( link->name, "txsend_out" ) ) ctx->in_kind[ i ] = IN_KIND_TXSEND;
4890 0 : else if( !strcmp( link->name, "rpc_replay" ) ) ctx->in_kind[ i ] = IN_KIND_RPC;
4891 0 : else if( !strcmp( link->name, "gossip_out" ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP_OUT;
4892 0 : else if( !strcmp( link->name, "snapmk_out" ) ) ctx->in_kind[ i ] = IN_KIND_SNAPMK;
4893 0 : else if( !strcmp( link->name, "admin_replay" ) ) ctx->in_kind[ i ] = IN_KIND_ADMIN;
4894 0 : else if( !strcmp( link->name, "tower_out" ) ) ctx->in_kind[ i ] = IN_KIND_TOWER;
4895 0 : else if( !strcmp( link->name, "votor_out" ) ) ctx->in_kind[ i ] = IN_KIND_VOTOR;
4896 0 : else FD_LOG_ERR(( "unexpected input link name %s", link->name ));
4897 :
4898 0 : if( ctx->in_kind[ i ]==IN_KIND_ADMIN ) {
4899 0 : FD_TEST( ( ctx->admin_out_idx = fd_topo_find_tile_out_link( topo, tile, "replay_admin", 0UL ) )!=ULONG_MAX );
4900 0 : }
4901 0 : }
4902 :
4903 0 : *ctx->epoch_out = out1( topo, tile, "replay_epoch" ); FD_TEST( ctx->epoch_out->idx!=ULONG_MAX );
4904 0 : *ctx->replay_out = out1( topo, tile, "replay_out" ); FD_TEST( ctx->replay_out->idx!=ULONG_MAX );
4905 0 : *ctx->snapmk_out = out1( topo, tile, "replay_snapmk" ); FD_TEST( ctx->snapmk.supported == (ctx->snapmk_out->idx!=ULONG_MAX) );
4906 0 : *ctx->exec_out = out1( topo, tile, "replay_execrp" ); FD_TEST( ctx->exec_out->idx!=ULONG_MAX );
4907 :
4908 0 : ctx->rpc_enabled = fd_topo_find_tile( topo, "rpc", 0UL )!=ULONG_MAX;
4909 :
4910 0 : if( FD_UNLIKELY( strcmp( "", tile->replay.solcap_capture ) ) ) {
4911 0 : ulong idx = fd_topo_find_tile_out_link( topo, tile, "cap_repl", 0UL );
4912 0 : FD_TEST( idx!=ULONG_MAX );
4913 0 : fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ idx ] ];
4914 :
4915 :
4916 0 : fd_capture_link_buf_t * cap_repl_out = ctx->cap_repl_out;
4917 0 : cap_repl_out->base.vt = &fd_capture_link_buf_vt;
4918 0 : cap_repl_out->idx = idx;
4919 0 : cap_repl_out->mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
4920 0 : cap_repl_out->chunk0 = fd_dcache_compact_chunk0( cap_repl_out->mem, link->dcache );
4921 0 : cap_repl_out->wmark = fd_dcache_compact_wmark( cap_repl_out->mem, link->dcache, link->mtu );
4922 0 : cap_repl_out->chunk = cap_repl_out->chunk0;
4923 0 : cap_repl_out->mcache = link->mcache;
4924 0 : cap_repl_out->depth = fd_mcache_depth( link->mcache );
4925 0 : cap_repl_out->seq = 0UL;
4926 :
4927 0 : ctx->capture_ctx->capctx_type.buf = cap_repl_out;
4928 0 : ctx->capture_ctx->capture_link = &cap_repl_out->base;
4929 0 : ctx->capture_ctx->current_txn_idx = 0UL;
4930 :
4931 :
4932 0 : ulong consumer_tile_idx = fd_topo_find_tile( topo, "solcap", 0UL );
4933 0 : fd_topo_tile_t const * consumer_tile = &topo->tiles[ consumer_tile_idx ];
4934 0 : cap_repl_out->fseq = NULL;
4935 0 : for( ulong j = 0UL; j < consumer_tile->in_cnt; j++ ) {
4936 0 : if( FD_UNLIKELY( consumer_tile->in_link_id[ j ] == link->id ) ) {
4937 0 : cap_repl_out->fseq = fd_fseq_join( fd_topo_obj_laddr( topo, consumer_tile->in_link_fseq_obj_id[ j ] ) );
4938 0 : FD_TEST( cap_repl_out->fseq );
4939 0 : break;
4940 0 : }
4941 0 : }
4942 0 : }
4943 :
4944 0 : fd_memset( &ctx->metrics, 0, sizeof(ctx->metrics) );
4945 :
4946 0 : fd_histf_join( fd_histf_new( ctx->metrics.store_query_work, FD_MHIST_SECONDS_MIN( REPLAY, STORE_QUERY_WORK_SECONDS ),
4947 0 : FD_MHIST_SECONDS_MAX( REPLAY, STORE_QUERY_WORK_SECONDS ) ) );
4948 :
4949 : /* Ensure precompiles are available, crash fast otherwise */
4950 0 : fd_precompiles();
4951 :
4952 0 : ctx->report_runtime_diffs = tile->replay.report_runtime_diffs;
4953 0 : ctx->banks->report_runtime_diffs = tile->replay.report_runtime_diffs;
4954 :
4955 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
4956 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
4957 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
4958 0 : }
4959 :
4960 : static ulong
4961 : populate_allowed_seccomp( fd_topo_t const * topo,
4962 : fd_topo_tile_t const * tile,
4963 : ulong out_cnt,
4964 0 : struct sock_filter * out ) {
4965 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
4966 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
4967 0 : fd_replay_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_tile_t), sizeof(fd_replay_tile_t) );
4968 0 : populate_sock_filter_policy_fd_replay_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), FD_ACCDB_FD_RW, (uint)ctx->store_disk_fd );
4969 0 : return sock_filter_policy_fd_replay_tile_instr_cnt;
4970 0 : }
4971 :
4972 : static ulong
4973 : populate_allowed_fds( fd_topo_t const * topo,
4974 : fd_topo_tile_t const * tile,
4975 : ulong out_fds_cnt,
4976 0 : int * out_fds ) {
4977 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
4978 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
4979 0 : fd_replay_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_tile_t), sizeof(fd_replay_tile_t) );
4980 0 : if( FD_UNLIKELY( out_fds_cnt<4UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
4981 :
4982 0 : ulong out_cnt = 0UL;
4983 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
4984 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
4985 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
4986 0 : out_fds[ out_cnt++ ] = FD_ACCDB_FD_RW; /* accounts db */
4987 0 : if( FD_LIKELY( ctx->store_disk_fd>=0 ) )
4988 0 : out_fds[ out_cnt++ ] = ctx->store_disk_fd;
4989 :
4990 0 : return out_cnt;
4991 0 : }
4992 :
4993 : static inline void
4994 0 : during_housekeeping( fd_replay_tile_t * ctx ) {
4995 0 : if( FD_UNLIKELY( fd_clock_tile_recal_due( ctx->clock ) ) ) fd_clock_tile_recal( ctx->clock );
4996 :
4997 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
4998 0 : FD_CHECK_CRIT( ctx->halt_leader, "state machine corruption" );
4999 0 : FD_LOG_DEBUG(( "keyswitch: unhalting leader" ));
5000 0 : ctx->halt_leader = 0;
5001 0 : fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
5002 0 : }
5003 :
5004 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
5005 0 : FD_LOG_DEBUG(( "keyswitch: halting leader" ));
5006 0 : ctx->halt_leader = 1;
5007 0 : if( !ctx->is_leader ) maybe_switch_identity( ctx );
5008 0 : }
5009 0 : }
5010 :
5011 : #undef DEBUG_LOGGING
5012 :
5013 : /* counting carefully, after_credit can generate at most 8 frags and
5014 : returnable_frag boot_genesis can generate at most 7 frags, so 15 is a
5015 : conservative bound. */
5016 0 : #define STEM_BURST (15UL)
5017 :
5018 : /* fd_tempo_lazy_default( 16384 ) where 16384 is the minimum out-link
5019 : depth (i.e. cr_max) but excludes replay_epoch, which is so infrequent
5020 : credit availability is a non-issue. */
5021 0 : #define STEM_LAZY ((long)36865)
5022 :
5023 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_replay_tile_t
5024 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_replay_tile_t)
5025 :
5026 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
5027 0 : #define STEM_CALLBACK_AFTER_CREDIT after_credit
5028 0 : #define STEM_CALLBACK_BEFORE_FRAG before_frag
5029 0 : #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
5030 0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
5031 :
5032 : #include "../../disco/stem/fd_stem.c"
5033 :
5034 : static ulong
5035 0 : max_event_sz( fd_topo_tile_t const * tile ) {
5036 : /* replay always emits block_completed; runtime_block (the largest
5037 : runtime event) is added when runtime diffs are on. */
5038 0 : ulong sz = sizeof(fd_event_block_completed_t);
5039 0 : if( tile->replay.report_runtime_diffs && sizeof(fd_event_runtime_block_t)>sz ) sz = sizeof(fd_event_runtime_block_t);
5040 0 : return sz;
5041 0 : }
5042 :
5043 : fd_topo_run_tile_t fd_tile_replay = {
5044 : .name = "replay",
5045 : .max_event_sz = max_event_sz,
5046 : .populate_allowed_seccomp = populate_allowed_seccomp,
5047 : .populate_allowed_fds = populate_allowed_fds,
5048 : .scratch_align = scratch_align,
5049 : .scratch_footprint = scratch_footprint,
5050 : .privileged_init = privileged_init,
5051 : .unprivileged_init = unprivileged_init,
5052 : .run = stem_run,
5053 : };
|