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