Line data Source code
1 : #include "fd_snapin_tile_private.h"
2 : #include "utils/fd_ssctrl.h"
3 : #include "utils/fd_ssmsg.h"
4 : #include "utils/fd_vinyl_io_wd.h"
5 :
6 : #include "../../disco/topo/fd_topo.h"
7 : #include "../../disco/metrics/fd_metrics.h"
8 : #include "../../disco/gui/fd_gui_config_parse.h"
9 : #include "../../flamenco/accdb/fd_accdb_admin_v1.h"
10 : #include "../../flamenco/accdb/fd_accdb_impl_v1.h"
11 : #include "../../flamenco/runtime/fd_txncache.h"
12 : #include "../../flamenco/runtime/fd_system_ids.h"
13 : #include "../../flamenco/runtime/sysvar/fd_sysvar_slot_history.h"
14 : #include "../../flamenco/runtime/fd_hashes.h"
15 : #include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h"
16 : #include "../../flamenco/types/fd_types.h"
17 : #include "../../util/pod/fd_pod.h"
18 :
19 : #include "generated/fd_snapin_tile_seccomp.h"
20 :
21 : #define NAME "snapin"
22 :
23 : /* The snapin tile is a state machine that parses and loads a full
24 : and optionally an incremental snapshot. It is currently responsible
25 : for loading accounts into an in-memory database, though this may
26 : change. */
27 :
28 : /* 300 root slots in the slot deltas array, and each one references all
29 : 151 prior blockhashes that it's able to. */
30 : #define FD_SNAPIN_MAX_SLOT_DELTA_GROUPS (300UL*151UL)
31 :
32 : struct fd_blockhash_entry {
33 : fd_hash_t blockhash;
34 :
35 : struct {
36 : ulong prev;
37 : ulong next;
38 : } map;
39 : };
40 :
41 : typedef struct fd_blockhash_entry fd_blockhash_entry_t;
42 :
43 : #define MAP_NAME blockhash_map
44 0 : #define MAP_KEY blockhash
45 : #define MAP_KEY_T fd_hash_t
46 : #define MAP_ELE_T fd_blockhash_entry_t
47 0 : #define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1), sizeof(fd_hash_t)))
48 0 : #define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t)))
49 0 : #define MAP_PREV map.prev
50 0 : #define MAP_NEXT map.next
51 : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
52 : #include "../../util/tmpl/fd_map_chain.c"
53 :
54 : static inline int
55 0 : should_shutdown( fd_snapin_tile_t * ctx ) {
56 0 : if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_SHUTDOWN && !ctx->use_vinyl ) ) {
57 : /* This only needs to be logged under funk. When vinyl is enabled,
58 : snapwm will log instead. */
59 0 : ulong accounts_dup = ctx->metrics.accounts_ignored + ctx->metrics.accounts_replaced;
60 0 : ulong accounts = ctx->metrics.accounts_loaded - accounts_dup;
61 0 : long elapsed_ns = fd_log_wallclock() - ctx->boot_timestamp;
62 0 : FD_LOG_NOTICE(( "loaded %.1fM accounts (%.1fM dups) from snapshot in %.3f seconds",
63 0 : (double)accounts/1e6,
64 0 : (double)accounts_dup/1e6,
65 0 : (double)elapsed_ns/1e9 ));
66 0 : }
67 0 : return ctx->state==FD_SNAPSHOT_STATE_SHUTDOWN;
68 0 : }
69 :
70 : static ulong
71 0 : scratch_align( void ) {
72 0 : return 512UL;
73 0 : }
74 :
75 : static ulong
76 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
77 0 : (void)tile;
78 0 : ulong l = FD_LAYOUT_INIT;
79 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_snapin_tile_t), sizeof(fd_snapin_tile_t) );
80 0 : l = FD_LAYOUT_APPEND( l, fd_ssparse_align(), fd_ssparse_footprint( 1UL<<24UL ) );
81 0 : l = FD_LAYOUT_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->snapin.max_live_slots ) );
82 0 : l = FD_LAYOUT_APPEND( l, fd_ssmanifest_parser_align(), fd_ssmanifest_parser_footprint() );
83 0 : l = FD_LAYOUT_APPEND( l, fd_slot_delta_parser_align(), fd_slot_delta_parser_footprint() );
84 0 : l = FD_LAYOUT_APPEND( l, alignof(blockhash_group_t), sizeof(blockhash_group_t)*FD_SNAPIN_MAX_SLOT_DELTA_GROUPS );
85 0 : if( !tile->snapin.use_vinyl ) {
86 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_sstxncache_entry_t), sizeof(fd_sstxncache_entry_t)*FD_SNAPIN_TXNCACHE_MAX_ENTRIES );
87 0 : }
88 0 : return FD_LAYOUT_FINI( l, scratch_align() );
89 0 : }
90 :
91 : static void
92 0 : metrics_write( fd_snapin_tile_t * ctx ) {
93 0 : FD_MGAUGE_SET( SNAPIN, STATE, (ulong)ctx->state );
94 0 : FD_MGAUGE_SET( SNAPIN, FULL_BYTES_READ, ctx->metrics.full_bytes_read );
95 0 : FD_MGAUGE_SET( SNAPIN, INCREMENTAL_BYTES_READ, ctx->metrics.incremental_bytes_read );
96 0 : FD_MGAUGE_SET( SNAPIN, ACCOUNTS_LOADED, ctx->metrics.accounts_loaded );
97 0 : FD_MGAUGE_SET( SNAPIN, ACCOUNTS_REPLACED, ctx->metrics.accounts_replaced );
98 0 : FD_MGAUGE_SET( SNAPIN, ACCOUNTS_IGNORED, ctx->metrics.accounts_ignored );
99 0 : }
100 :
101 : /* verify_slot_deltas_with_slot_history verifies the 'SlotHistory'
102 : sysvar account after loading a snapshot. The full database
103 : architecture is only instantiated after snapshot loading, so this
104 : function uses a primitive/cache-free mechanism to query the parts of
105 : the account database that are available.
106 :
107 : Returns 0 if verification passed, -1 if not. */
108 :
109 : static int
110 0 : verify_slot_deltas_with_slot_history( fd_snapin_tile_t * ctx ) {
111 : /* Do a raw read of the slot history sysvar account from the database.
112 : Requires approx 500kB stack space. */
113 :
114 0 : fd_account_meta_t meta;
115 0 : uchar data[ FD_SYSVAR_SLOT_HISTORY_BINCODE_SZ ];
116 0 : union {
117 0 : uchar buf[ FD_SYSVAR_SLOT_HISTORY_FOOTPRINT ];
118 0 : fd_slot_history_global_t o;
119 0 : } decoded;
120 0 : FD_STATIC_ASSERT( offsetof( __typeof__(decoded), buf)==offsetof( __typeof__(decoded), o ), memory_layout );
121 0 : fd_snapin_read_account( ctx, &fd_sysvar_slot_history_id, &meta, data, sizeof(data) );
122 :
123 0 : if( FD_UNLIKELY( !meta.lamports || !meta.dlen ) ) {
124 0 : FD_LOG_WARNING(( "SlotHistory sysvar account missing or empty" ));
125 0 : return -1;
126 0 : }
127 0 : if( FD_UNLIKELY( meta.dlen > FD_SYSVAR_SLOT_HISTORY_BINCODE_SZ ) ) {
128 0 : FD_LOG_WARNING(( "SlotHistory sysvar account data too large: %u bytes", meta.dlen ));
129 0 : return -1;
130 0 : }
131 0 : if( FD_UNLIKELY( !fd_memeq( meta.owner, fd_sysvar_owner_id.uc, sizeof(fd_pubkey_t) ) ) ) {
132 0 : FD_BASE58_ENCODE_32_BYTES( meta.owner, owner_b58 );
133 0 : FD_LOG_WARNING(( "SlotHistory sysvar owner is invalid: %s != sysvar_owner_id", owner_b58 ));
134 0 : return -1;
135 0 : }
136 :
137 0 : if( FD_UNLIKELY(
138 0 : !fd_bincode_decode_static_global(
139 0 : slot_history,
140 0 : &decoded.o,
141 0 : data,
142 0 : meta.dlen )
143 0 : ) ) {
144 0 : FD_LOG_WARNING(( "SlotHistory sysvar account data is corrupt" ));
145 0 : return -1;
146 0 : }
147 :
148 : /* Sanity checks for slot history:
149 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L586 */
150 :
151 0 : ulong newest_slot = fd_sysvar_slot_history_newest( &decoded.o );
152 0 : if( FD_UNLIKELY( newest_slot!=ctx->bank_slot ) ) {
153 : /* VerifySlotHistoryError::InvalidNewestSlot
154 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L621 */
155 0 : FD_LOG_WARNING(( "SlotHistory sysvar has an invalid newest slot: %lu != bank slot: %lu", newest_slot, ctx->bank_slot ));
156 0 : return -1;
157 0 : }
158 :
159 0 : ulong slot_history_len = fd_sysvar_slot_history_len( &decoded.o );
160 0 : if( FD_UNLIKELY( slot_history_len!=FD_SLOT_HISTORY_MAX_ENTRIES ) ) {
161 : /* VerifySlotHistoryError::InvalidNumEntries
162 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L625 */
163 0 : FD_LOG_WARNING(( "SLotHistory sysvar has invalid number of entries: %lu != expected: %lu", slot_history_len, FD_SLOT_HISTORY_MAX_ENTRIES ));
164 0 : return -1;
165 0 : }
166 :
167 : /* All slots in the txncache should be present in the slot history */
168 0 : for( ulong i=0UL; i<ctx->txncache_entries_len; i++ ) {
169 0 : fd_sstxncache_entry_t const * entry = &ctx->txncache_entries[i];
170 0 : if( FD_UNLIKELY( fd_sysvar_slot_history_find_slot( &decoded.o, entry->slot )!=FD_SLOT_HISTORY_SLOT_FOUND ) ) {
171 : /* VerifySlotDeltasError::SlotNotFoundInHistory
172 : https://github.com/anza-xyz/agave/blob/v3.1.8/snapshots/src/error.rs#L144
173 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L593 */
174 0 : FD_LOG_WARNING(( "slot %lu missing from SlotHistory sysvar account", entry->slot ));
175 0 : return -1;
176 0 : }
177 0 : }
178 :
179 : /* The most recent slots (up to the number of slots in the txncache)
180 : in the SlotHistory should be present in the txncache. */
181 0 : fd_slot_delta_slot_set_t slot_set = fd_slot_delta_parser_slot_set( ctx->slot_delta_parser );
182 0 : for( ulong i=newest_slot; i>newest_slot-slot_set.ele_cnt; i-- ) {
183 0 : if( FD_LIKELY( fd_sysvar_slot_history_find_slot( &decoded.o, i )==FD_SLOT_HISTORY_SLOT_FOUND ) ) {
184 0 : if( FD_UNLIKELY( slot_set_ele_query( slot_set.map, &i, NULL, slot_set.pool )==NULL ) ) {
185 : /* VerifySlotDeltasError::SlotNotFoundInDeltas
186 : https://github.com/anza-xyz/agave/blob/v3.1.8/snapshots/src/error.rs#L147
187 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L609 */
188 0 : FD_LOG_WARNING(( "slot %lu missing from slot deltas but present in SlotHistory", i ));
189 0 : return -1;
190 0 : }
191 0 : }
192 0 : }
193 0 : return 0;
194 0 : }
195 :
196 : /* verification of epoch stakes from manifest
197 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L632 */
198 : static int
199 0 : verify_epoch_stakes( fd_snapshot_manifest_t const * manifest ) {
200 :
201 0 : fd_epoch_schedule_t epoch_schedule = (fd_epoch_schedule_t){
202 0 : .slots_per_epoch = manifest->epoch_schedule_params.slots_per_epoch,
203 0 : .leader_schedule_slot_offset = manifest->epoch_schedule_params.leader_schedule_slot_offset,
204 0 : .warmup = manifest->epoch_schedule_params.warmup,
205 0 : .first_normal_epoch = manifest->epoch_schedule_params.first_normal_epoch,
206 0 : .first_normal_slot = manifest->epoch_schedule_params.first_normal_slot,
207 0 : };
208 :
209 0 : ulong min_required_epoch = fd_slot_to_epoch( &epoch_schedule, manifest->slot, NULL );
210 0 : ulong max_required_epoch = fd_slot_to_leader_schedule_epoch( &epoch_schedule, manifest->slot );
211 :
212 : /* ensure all required epochs are present in epoch stakes */
213 0 : for( ulong i=min_required_epoch; i<=max_required_epoch; i++ ) {
214 0 : int found = 0;
215 0 : for( ulong j=0UL; j<FD_SNAPSHOT_MANIFEST_EPOCH_STAKES_LEN; j++ ) {
216 0 : if( manifest->epoch_stakes[j].epoch==i ) {
217 0 : found = 1;
218 0 : break;
219 0 : }
220 0 : }
221 :
222 0 : if( FD_UNLIKELY( !found ) ) {
223 : /* VerifyEpochStakesError::StakesNotFound
224 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L667 */
225 0 : FD_LOG_WARNING(( "stakes not found for epoch %lu in manifest", i ));
226 0 : return -1;
227 0 : }
228 0 : }
229 :
230 0 : return 0;
231 0 : }
232 :
233 : static int
234 : verify_slot_deltas_with_bank_slot( fd_snapin_tile_t * ctx,
235 0 : ulong bank_slot ) {
236 0 : for( ulong i=0UL; i<ctx->txncache_entries_len; i++ ) {
237 0 : fd_sstxncache_entry_t const * entry = &ctx->txncache_entries[i];
238 : /* VerifySlotDeltasError::SlotGreaterThanMaxRoot
239 : https://github.com/anza-xyz/agave/blob/v3.1.8/snapshots/src/error.rs#L138
240 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L550 */
241 0 : if( FD_UNLIKELY( entry->slot>bank_slot ) ) {
242 0 : FD_LOG_WARNING(( "entry slot %lu is greater than bank slot %lu", entry->slot, bank_slot ));
243 0 : return -1;
244 0 : }
245 0 : }
246 0 : return 0;
247 0 : }
248 :
249 : static int
250 : verify_bank_hash( fd_snapin_tile_t const * ctx,
251 0 : fd_snapshot_manifest_t const * manifest ) {
252 0 : if( FD_UNLIKELY( manifest->blockhashes_len==0UL ) ) {
253 0 : FD_LOG_WARNING(( "%s manifest for epoch %lu and slot %lu has no blockhashes",
254 0 : ctx->full?"full":"incr", ctx->epoch, manifest->slot ));
255 0 : return -1;
256 0 : }
257 :
258 0 : if( FD_UNLIKELY( !manifest->has_accounts_lthash ) ) {
259 0 : FD_LOG_WARNING(( "%s manifest for epoch %lu and slot %lu is missing accounts lthash",
260 0 : ctx->full?"full":"incr", ctx->epoch, manifest->slot ));
261 0 : return -1;
262 0 : }
263 :
264 : /* find the last blockhash */
265 0 : ulong max_hash_idx = 0UL;
266 0 : ulong last_bh_idx = 0UL;
267 0 : for( ulong i=0UL; i<manifest->blockhashes_len; i++ ) {
268 0 : if( FD_LIKELY( manifest->blockhashes[ i ].hash_index > max_hash_idx ) ) {
269 0 : max_hash_idx = manifest->blockhashes[ i ].hash_index;
270 0 : last_bh_idx = i;
271 0 : }
272 0 : }
273 :
274 : /* fd_lthash_value_t is aligned to 64B but the accounts_lthash in the
275 : manifest may not be because its simply a uchar array. Copy is
276 : needed to avoid undefined behavior. */
277 0 : fd_lthash_value_t accounts_lthash[ 1UL ];
278 0 : fd_memcpy( accounts_lthash, manifest->accounts_lthash, sizeof(fd_lthash_value_t) );
279 :
280 0 : fd_hash_t const * parent_bank_hash = (fd_hash_t const *)fd_type_pun_const( manifest->parent_bank_hash );
281 0 : fd_hash_t const * last_blockhash = (fd_hash_t const *)fd_type_pun_const( manifest->blockhashes[ last_bh_idx ].hash );
282 0 : fd_hash_t computed_bank_hash[ 1UL ];
283 0 : fd_hashes_hash_bank( accounts_lthash, parent_bank_hash, last_blockhash, manifest->signature_count, computed_bank_hash );
284 0 : fd_hashes_apply_hard_forks(
285 0 : computed_bank_hash,
286 0 : manifest->slot,
287 0 : manifest->parent_slot,
288 0 : manifest->hard_forks,
289 0 : manifest->hard_forks_cnts,
290 0 : manifest->hard_forks_len );
291 :
292 0 : if( FD_UNLIKELY( memcmp( computed_bank_hash, manifest->bank_hash, FD_HASH_FOOTPRINT ) ) ) {
293 0 : FD_BASE58_ENCODE_32_BYTES( computed_bank_hash->hash, computed_bank_hash_enc );
294 0 : FD_BASE58_ENCODE_32_BYTES( manifest->bank_hash, manifest_bank_hash_enc );
295 0 : FD_LOG_WARNING(( "%s manifest for epoch %lu and slot %lu bank hash verification failed: computed %s does not match manifest %s",
296 0 : ctx->full?"full":"incr", ctx->epoch, manifest->slot,
297 0 : computed_bank_hash_enc, manifest_bank_hash_enc ));
298 0 : return -1;
299 0 : }
300 :
301 0 : return 0;
302 0 : }
303 :
304 : static void
305 : transition_malformed( fd_snapin_tile_t * ctx,
306 0 : fd_stem_context_t * stem ) {
307 0 : if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_ERROR ) ) return;
308 0 : ctx->state = FD_SNAPSHOT_STATE_ERROR;
309 0 : fd_stem_publish( stem, ctx->out_ct_idx, FD_SNAPSHOT_MSG_CTRL_ERROR, 0UL, 0UL, 0UL, 0UL, 0UL );
310 0 : }
311 :
312 : static int
313 : populate_txncache( fd_snapin_tile_t * ctx,
314 : fd_snapshot_manifest_blockhash_t const blockhashes[ static 301UL ],
315 0 : ulong blockhashes_len ) {
316 : /* Our txncache internally contains the fork structure for the chain,
317 : which we need to recreate here. Because snapshots are only served
318 : for rooted slots, there is actually no forking, and the bank forks
319 : are just a single bank, the root, like
320 :
321 : _root
322 :
323 : But the txncache also must contain the 150 more recent banks prior
324 : to the root (151 rooted banks total), looking like,
325 :
326 :
327 : _root_150 -> _root_149 -> ... -> _root_2 -> _root_1 -> _root
328 :
329 : Our txncache is "slot agnostic" meaning there is no concept of a
330 : slot number in it. It just has a fork tree structure. So long as
331 : the fork tree is isomorphic to the actual bank forks, and each bank
332 : has the correct blockhash, it works.
333 :
334 : So the challenge is simply to create this chain of 151 forks in the
335 : txncache, with correct blockhashes, and then insert all the
336 : transactions into it.
337 :
338 : Constructing the chain of blockhashes is easy. It is just the
339 : BLOCKHASH_QUEUE array in the manifest. This array is unfortuantely
340 : not sorted and appears in random order, but it has a hash_index
341 : field which is a gapless index, starting at some arbitrary offset,
342 : so we can back out the 151 blockhashes we need from this, by first
343 : finding the max hash_index as _max and then collecting hash entries
344 : via,
345 :
346 : _root_150 -> _root_149 -> ... -> _root_2 -> _root_1 -> _root
347 : _max-150 -> _max-149 -> ... -> _max-2 -> _max-1 -> _max
348 :
349 : Now the remaining problem is inserting transactions into this
350 : chain. Remember each transaction needs to be inserted with:
351 :
352 : (a) The fork ID (position of the bank in the chain) it was executed in.
353 : (b) The blockhash of the bank it referenced.
354 :
355 : (b) is trivial to retrieve, as it's in the actual slot_deltas entry
356 : in the manifest served by Agave. But (a) is mildly annoying. Agave
357 : serves slot_deltas based on slot, so we need an additional mapping
358 : from slot to position in our banks chain. It turns out we have to
359 : go to yet another structure in the manifest to retrieve this, the
360 : ancestors array. This is just an array of slot values, so we need
361 : to sort it, and line it up against our banks chain like so,
362 :
363 : _root_150 -> _root_149 -> ... -> _root_2 -> _root_1 -> _root
364 : _max-150 -> _max-149 -> ... -> _max-2 -> _max-1 -> _max
365 : _slots_150 -> _slots_149 -> ... -> _slots_2 -> _slots_1 -> _slots
366 :
367 : From there we are done.
368 :
369 : Well almost ... if you were paying attention you might have noticed
370 : this is a lot of work and we are lazy. Why don't we just ignore the
371 : slot mapping and assume everything executed at the root slot
372 : exactly? The only invariant we should maintain from a memory
373 : perspective is that at most, across all active banks,
374 : FD_MAX_TXN_PER_SLOT transactions are stored per slot, but we
375 : have preserved that. It is not true "per slot" technically, but
376 : it's true across all slots, and the memory is aggregated. It will
377 : also always be true, even as slots are garbage collected, because
378 : entries are collected by referece blockhash, not executed slot.
379 :
380 : ... actually we can't do this. There's more broken things here.
381 : The Agave status decided to only store 20 bytes for 32 byte
382 : transaction hashes to save on memory. That's OK, but they didn't
383 : just take the first 20 bytes. They instead, for each blockhash,
384 : take a random offset between 0 and 12, and store bytes
385 : [ offset, offset+20 ) of the transaction hash. We need to know this
386 : offset to be able to query the txncache later, so we need to
387 : retrieve it from the slot_deltas entry in the manifest, and key it
388 : into our txncache. Unfortunately this offset is stored per slot in
389 : the slot_deltas entry. So we need to first go and retrieve the
390 : ancestors array, sort it, and line it up against our banks chain as
391 : described above, and then go through slot deltas, to retrieve the
392 : offset for each slot, and stick it into the appropriate bank in
393 : our chain. */
394 :
395 0 : FD_TEST( blockhashes_len<=301UL );
396 0 : FD_TEST( blockhashes_len>0UL );
397 :
398 0 : ulong seq_min = ULONG_MAX;
399 0 : for( ulong i=0UL; i<blockhashes_len; i++ ) seq_min = fd_ulong_min( seq_min, blockhashes[ i ].hash_index );
400 :
401 0 : ulong seq_max;
402 0 : if( FD_UNLIKELY( __builtin_uaddl_overflow( seq_min, blockhashes_len, &seq_max ) ) ) {
403 0 : FD_LOG_WARNING(( "corrupt snapshot: blockhash queue sequence number wraparound (seq_min=%lu age_cnt=%lu)", seq_min, blockhashes_len ));
404 0 : transition_malformed( ctx, ctx->stem );
405 0 : return 1;
406 0 : }
407 :
408 : /* First let's construct the chain array as described above. But
409 : index 0 will be the root, index 1 the root's parent, etc. */
410 :
411 0 : struct {
412 0 : int exists;
413 0 : uchar blockhash[ 32UL ];
414 0 : fd_txncache_fork_id_t fork_id;
415 0 : ulong txnhash_offset;
416 0 : } banks[ 301UL ] = {0};
417 :
418 0 : for( ulong i=0UL; i<blockhashes_len; i++ ) {
419 0 : fd_snapshot_manifest_blockhash_t const * elem = &blockhashes[ i ];
420 0 : ulong idx;
421 0 : if( FD_UNLIKELY( __builtin_usubl_overflow( elem->hash_index, seq_min, &idx ) ) ) {
422 0 : FD_LOG_WARNING(( "corrupt snapshot: gap in blockhash queue (seq=[%lu,%lu) idx=%lu)", seq_min, seq_max, blockhashes[ i ].hash_index ));
423 0 : transition_malformed( ctx, ctx->stem );
424 0 : return 1;
425 0 : }
426 :
427 0 : if( FD_UNLIKELY( idx>=blockhashes_len ) ) {
428 0 : FD_LOG_WARNING(( "corrupt snapshot: blockhash queue index out of range (seq_min=%lu age_cnt=%lu idx=%lu)", seq_min, blockhashes_len, idx ));
429 0 : transition_malformed( ctx, ctx->stem );
430 0 : return 1;
431 0 : }
432 :
433 0 : if( FD_UNLIKELY( banks[ blockhashes_len-1UL-idx ].exists ) ) {
434 0 : FD_LOG_WARNING(( "corrupt snapshot: duplicate blockhash hash_index %lu", elem->hash_index ));
435 0 : transition_malformed( ctx, ctx->stem );
436 0 : return 1;
437 0 : }
438 :
439 0 : banks[ blockhashes_len-1UL-idx ].fork_id.val = USHORT_MAX;
440 0 : banks[ blockhashes_len-1UL-idx ].txnhash_offset = ULONG_MAX;
441 0 : memcpy( banks[ blockhashes_len-1UL-idx ].blockhash, elem->hash, 32UL );
442 0 : banks[ blockhashes_len-1UL-idx ].exists = 1;
443 0 : }
444 :
445 0 : ulong chain_len = fd_ulong_min( blockhashes_len, 151UL );
446 :
447 : /* Now we need a hashset of just the 151 most recent blockhashes,
448 : anything else is a nonce transaction which we do not insert, or an
449 : already expired transaction which can also be discarded. */
450 :
451 0 : uchar * _map = fd_alloca_check( alignof(blockhash_map_t), blockhash_map_footprint( 1024UL ) );
452 0 : blockhash_map_t * blockhash_map = blockhash_map_join( blockhash_map_new( _map, 1024UL, ctx->seed ) );
453 0 : FD_TEST( blockhash_map );
454 :
455 0 : fd_blockhash_entry_t blockhash_pool[ 151UL ];
456 0 : for( ulong i=0UL; i<chain_len; i++ ) {
457 0 : fd_memcpy( blockhash_pool[ i ].blockhash.uc, banks[ i ].blockhash, 32UL );
458 :
459 0 : if( FD_UNLIKELY( blockhash_map_ele_query_const( blockhash_map, &blockhash_pool[ i ].blockhash, NULL, blockhash_pool ) ) ) {
460 0 : FD_BASE58_ENCODE_32_BYTES( banks[ i ].blockhash, blockhash_b58 );
461 0 : FD_LOG_WARNING(( "corrupt snapshot: duplicate blockhash %s in 151 most recent blockhashes", blockhash_b58 ));
462 0 : transition_malformed( ctx, ctx->stem );
463 0 : return 1;
464 0 : }
465 :
466 0 : blockhash_map_ele_insert( blockhash_map, &blockhash_pool[ i ], blockhash_pool );
467 0 : }
468 :
469 : /* Now load the blockhash offsets for these blockhashes ... */
470 0 : FD_TEST( ctx->blockhash_offsets_len ); /* Must be at least one else nothing would be rooted */
471 0 : for( ulong i=0UL; i<ctx->blockhash_offsets_len; i++ ) {
472 0 : fd_hash_t key;
473 0 : fd_memcpy( key.uc, ctx->blockhash_offsets[ i ].blockhash, 32UL );
474 0 : fd_blockhash_entry_t * entry = blockhash_map_ele_query( blockhash_map, &key, NULL, blockhash_pool );
475 0 : if( FD_UNLIKELY( !entry ) ) continue; /* Not in the most recent 151 blockhashes */
476 :
477 0 : ulong chain_idx = (ulong)(entry - blockhash_pool);
478 :
479 0 : if( FD_UNLIKELY( banks[ chain_idx ].txnhash_offset!=ULONG_MAX && banks[ chain_idx ].txnhash_offset!=ctx->blockhash_offsets[ i ].txnhash_offset ) ) {
480 0 : FD_BASE58_ENCODE_32_BYTES( entry->blockhash.uc, blockhash_b58 );
481 0 : FD_LOG_WARNING(( "corrupt snapshot: conflicting txnhash offsets for blockhash %s", blockhash_b58 ));
482 0 : transition_malformed( ctx, ctx->stem );
483 0 : return 1;
484 0 : }
485 :
486 0 : banks[ chain_idx ].txnhash_offset = ctx->blockhash_offsets[ i ].txnhash_offset;
487 0 : }
488 :
489 : /* Construct the linear fork chain in the txncache. */
490 :
491 0 : fd_txncache_fork_id_t parent = { .val = USHORT_MAX };
492 0 : for( ulong i=0UL; i<chain_len; i++ ) banks[ chain_len-1UL-i ].fork_id = parent = fd_txncache_attach_child( ctx->txncache, parent );
493 0 : for( ulong i=0UL; i<chain_len; i++ ) fd_txncache_attach_blockhash( ctx->txncache, banks[ i ].fork_id, banks[ i ].blockhash );
494 :
495 : /* Now insert all transactions as if they executed at the current
496 : root, per above. */
497 :
498 0 : ulong insert_cnt = 0UL;
499 0 : for( ulong i=0UL; i<ctx->txncache_entries_len; i++ ) {
500 0 : fd_sstxncache_entry_t const * entry = &ctx->txncache_entries[ i ];
501 0 : fd_hash_t key;
502 0 : fd_memcpy( key.uc, entry->blockhash, 32UL );
503 0 : if( FD_UNLIKELY( !blockhash_map_ele_query_const( blockhash_map, &key, NULL, blockhash_pool ) ) ) continue;
504 :
505 0 : insert_cnt++;
506 0 : fd_txncache_insert( ctx->txncache, banks[ 0UL ].fork_id, entry->blockhash, entry->txnhash );
507 0 : }
508 :
509 0 : if( !!ctx->use_vinyl && !!ctx->txncache_entries_len_vinyl_ptr ) {
510 0 : *ctx->txncache_entries_len_vinyl_ptr = ctx->txncache_entries_len;
511 0 : }
512 :
513 0 : FD_LOG_INFO(( "inserted %lu/%lu transactions into the txncache", insert_cnt, ctx->txncache_entries_len ));
514 :
515 : /* Then finalize all the banks (freezing them) and setting the txnhash
516 : offset so future queries use the correct offset. If the offset is
517 : ULONG_MAX this is valid, it means the blockhash had no transactions
518 : in it, so there's nothing in the status cache under that blockhash.
519 :
520 : Just set the offset to 0 in this case, it doesn't matter, but
521 : should be valid between 0 and 12 inclusive. */
522 0 : for( ulong i=0UL; i<chain_len; i++ ) {
523 0 : ulong txnhash_offset = banks[ chain_len-1UL-i ].txnhash_offset==ULONG_MAX ? 0UL : banks[ chain_len-1UL-i ].txnhash_offset;
524 0 : fd_txncache_finalize_fork( ctx->txncache, banks[ chain_len-1UL-i ].fork_id, txnhash_offset, banks[ chain_len-1UL-i ].blockhash );
525 0 : }
526 :
527 0 : for( ulong i=1UL; i<chain_len; i++ ) fd_txncache_advance_root( ctx->txncache, banks[ chain_len-1UL-i ].fork_id );
528 :
529 0 : ctx->txncache_root_fork_id = parent;
530 :
531 0 : return 0;
532 0 : }
533 :
534 : static void
535 0 : process_manifest( fd_snapin_tile_t * ctx ) {
536 0 : fd_snapshot_manifest_t * manifest = fd_chunk_to_laddr( ctx->manifest_out.mem, ctx->manifest_out.chunk );
537 :
538 0 : if( FD_UNLIKELY( ctx->advertised_slot!=manifest->slot ) ) {
539 : /* SnapshotError::MismatchedSlot
540 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L472 */
541 0 : FD_LOG_WARNING(( "snapshot manifest bank slot %lu does not match advertised slot %lu from snapshot peer",
542 0 : manifest->slot, ctx->advertised_slot ));
543 0 : transition_malformed( ctx, ctx->stem );
544 0 : return;
545 0 : }
546 :
547 0 : if( FD_UNLIKELY( !manifest->has_accounts_lthash ) ) {
548 : /* The manifest must contain accounts lthash, irrespective of
549 : whether lthash verification is disabled or not.
550 : https://github.com/anza-xyz/agave/blob/v3.1.9/runtime/src/serde_snapshot.rs#L482 */
551 0 : FD_LOG_WARNING(( "snapshot manifest missing accounts lthash" ));
552 0 : transition_malformed( ctx, ctx->stem );
553 0 : return;
554 0 : }
555 :
556 0 : uchar const * sum = manifest->accounts_lthash;
557 0 : uchar hash32[32]; fd_blake3_hash( sum, FD_LTHASH_LEN_BYTES, hash32 );
558 0 : FD_BASE58_ENCODE_32_BYTES( sum, sum_enc );
559 0 : FD_BASE58_ENCODE_32_BYTES( hash32, hash32_enc );
560 0 : FD_LOG_INFO(( "snapshot manifest slot=%lu indicates lthash[..32]=%s blake3(lthash)=%s",
561 0 : manifest->slot, sum_enc, hash32_enc ));
562 :
563 0 : if( FD_UNLIKELY( memcmp( ctx->advertised_hash, hash32, FD_HASH_FOOTPRINT ) ) ) {
564 : /* SnapshotError::MismatchedHash
565 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L479 */
566 0 : FD_BASE58_ENCODE_32_BYTES( ctx->advertised_hash, advertised_hash_enc );
567 0 : FD_LOG_WARNING(( "snapshot manifest accounts lthash %s does not match advertised hash from snapshot peer %s",
568 0 : hash32_enc, advertised_hash_enc ));
569 0 : transition_malformed( ctx, ctx->stem );
570 0 : return;
571 0 : }
572 :
573 0 : ctx->bank_slot = manifest->slot;
574 0 : ctx->manifest_capitalization = manifest->capitalization;
575 0 : fd_epoch_schedule_t epoch_schedule = (fd_epoch_schedule_t){
576 0 : .slots_per_epoch = manifest->epoch_schedule_params.slots_per_epoch,
577 0 : .leader_schedule_slot_offset = manifest->epoch_schedule_params.leader_schedule_slot_offset,
578 0 : .warmup = manifest->epoch_schedule_params.warmup,
579 0 : .first_normal_epoch = manifest->epoch_schedule_params.first_normal_epoch,
580 0 : .first_normal_slot = manifest->epoch_schedule_params.first_normal_slot,
581 0 : };
582 0 : ctx->epoch = fd_slot_to_epoch( &epoch_schedule, manifest->slot, NULL );
583 :
584 0 : if( FD_UNLIKELY( verify_bank_hash( ctx, manifest ) ) ) {
585 : /* https://github.com/anza-xyz/agave/blob/v3.1.9/runtime/src/bank.rs#L4682 */
586 0 : transition_malformed( ctx, ctx->stem );
587 0 : return;
588 0 : }
589 :
590 0 : if( FD_UNLIKELY( verify_slot_deltas_with_bank_slot( ctx, manifest->slot ) ) ) {
591 0 : FD_LOG_WARNING(( "slot deltas verification failed" ));
592 0 : transition_malformed( ctx, ctx->stem );
593 0 : return;
594 0 : }
595 :
596 0 : if( FD_UNLIKELY( verify_epoch_stakes( manifest ) ) ) {
597 0 : FD_LOG_WARNING(( "epoch stakes verification failed" ));
598 0 : transition_malformed( ctx, ctx->stem );
599 0 : return;
600 0 : }
601 :
602 0 : if( FD_UNLIKELY( populate_txncache( ctx, manifest->blockhashes, manifest->blockhashes_len ) ) ) {
603 0 : FD_LOG_WARNING(( "populating txncache failed" ));
604 0 : transition_malformed( ctx, ctx->stem );
605 0 : return;
606 0 : }
607 :
608 0 : if( ctx->full ) {
609 0 : ctx->full_genesis_creation_time_seconds = manifest->creation_time_seconds;
610 0 : } else {
611 0 : if( FD_UNLIKELY( manifest->creation_time_seconds!=ctx->full_genesis_creation_time_seconds ) ) {
612 0 : FD_LOG_WARNING(( "snapshot manifest genesis creation time seconds %lu does not match full snapshot genesis creation time seconds %lu",
613 0 : manifest->creation_time_seconds, ctx->full_genesis_creation_time_seconds ));
614 0 : transition_malformed( ctx, ctx->stem );
615 0 : return;
616 0 : }
617 0 : }
618 :
619 0 : manifest->txncache_fork_id = ctx->txncache_root_fork_id.val;
620 :
621 0 : if( FD_LIKELY( !ctx->lthash_disabled ) ) {
622 0 : fd_lthash_value_t * expected_lthash = fd_chunk_to_laddr( ctx->hash_out.mem, ctx->hash_out.chunk );
623 0 : fd_memcpy( expected_lthash, manifest->accounts_lthash, sizeof(fd_lthash_value_t) );
624 0 : fd_stem_publish( ctx->stem, ctx->out_ct_idx, FD_SNAPSHOT_HASH_MSG_EXPECTED, ctx->hash_out.chunk, sizeof(fd_lthash_value_t), 0UL, 0UL, 0UL );
625 0 : ctx->hash_out.chunk = fd_dcache_compact_next( ctx->hash_out.chunk, sizeof(fd_lthash_value_t), ctx->hash_out.chunk0, ctx->hash_out.wmark );
626 :
627 0 : if( FD_LIKELY( ctx->use_vinyl ) ) {
628 0 : fd_ssctrl_capitalization_t * cap = fd_chunk_to_laddr( ctx->hash_out.mem, ctx->hash_out.chunk );
629 0 : cap->capitalization = manifest->capitalization;
630 0 : fd_stem_publish( ctx->stem, ctx->out_ct_idx, FD_SNAPSHOT_MSG_EXP_CAPITALIZATION, ctx->hash_out.chunk, sizeof(fd_ssctrl_capitalization_t), 0UL, 0UL, 0UL );
631 0 : ctx->hash_out.chunk = fd_dcache_compact_next( ctx->hash_out.chunk, sizeof(fd_ssctrl_capitalization_t), ctx->hash_out.chunk0, ctx->hash_out.wmark );
632 0 : }
633 0 : }
634 :
635 0 : ulong sig = ctx->full ? fd_ssmsg_sig( FD_SSMSG_MANIFEST_FULL ) :
636 0 : fd_ssmsg_sig( FD_SSMSG_MANIFEST_INCREMENTAL );
637 0 : fd_stem_publish( ctx->stem, ctx->manifest_out.idx, sig, ctx->manifest_out.chunk, sizeof(fd_snapshot_manifest_t), 0UL, 0UL, 0UL );
638 0 : ctx->manifest_out.chunk = fd_dcache_compact_next( ctx->manifest_out.chunk, sizeof(fd_snapshot_manifest_t), ctx->manifest_out.chunk0, ctx->manifest_out.wmark );
639 0 : }
640 :
641 :
642 : static int
643 : handle_data_frag( fd_snapin_tile_t * ctx,
644 : ulong chunk,
645 : ulong sz,
646 0 : fd_stem_context_t * stem ) {
647 0 : if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_FINISHING ) ) {
648 0 : FD_LOG_WARNING(( "received unexpected data frag while in state %s (%lu)",
649 0 : fd_ssctrl_state_str( (ulong)ctx->state ), (ulong)ctx->state ));
650 0 : transition_malformed( ctx, stem );
651 0 : return 0;
652 0 : }
653 0 : if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_ERROR ) ) {
654 : /* Ignore all data frags after observing an error in the stream until
655 : we receive fail & init control messages to restart processing. */
656 0 : return 0;
657 0 : }
658 0 : if( FD_UNLIKELY( ctx->state!=FD_SNAPSHOT_STATE_PROCESSING ) ) {
659 0 : FD_LOG_ERR(( "received data frag during invalid state %s (%lu)",
660 0 : fd_ssctrl_state_str( (ulong)ctx->state ), (ulong)ctx->state ));
661 0 : }
662 :
663 0 : FD_TEST( chunk>=ctx->in.chunk0 && chunk<=ctx->in.wmark && sz<=ctx->in.mtu );
664 :
665 0 : if( FD_UNLIKELY( !ctx->lthash_disabled && ctx->buffered_batch.batch_cnt>0UL ) ) {
666 0 : fd_snapin_process_account_batch( ctx, NULL, &ctx->buffered_batch );
667 0 : return 1;
668 0 : }
669 :
670 0 : for(;;) {
671 0 : if( FD_UNLIKELY( sz-ctx->in.pos==0UL ) ) break;
672 :
673 0 : uchar const * data = (uchar const *)fd_chunk_to_laddr_const( ctx->in.wksp, chunk ) + ctx->in.pos;
674 :
675 0 : int early_exit = 0;
676 0 : fd_ssparse_advance_result_t result[1];
677 0 : int res = fd_ssparse_advance( ctx->ssparse, data, sz-ctx->in.pos, result );
678 0 : switch( res ) {
679 0 : case FD_SSPARSE_ADVANCE_ERROR:
680 0 : FD_LOG_WARNING(( "error while parsing snapshot stream" ));
681 0 : transition_malformed( ctx, stem );
682 0 : return 0;
683 0 : case FD_SSPARSE_ADVANCE_AGAIN:
684 0 : break;
685 0 : case FD_SSPARSE_ADVANCE_MANIFEST: {
686 0 : int res = fd_ssmanifest_parser_consume( ctx->manifest_parser,
687 0 : result->manifest.data,
688 0 : result->manifest.data_sz,
689 0 : result->manifest.acc_vec_map,
690 0 : result->manifest.acc_vec_pool );
691 0 : if( FD_UNLIKELY( res==FD_SSMANIFEST_PARSER_ADVANCE_ERROR ) ) {
692 0 : FD_LOG_WARNING(( "error while parsing snapshot manifest" ));
693 0 : transition_malformed( ctx, stem );
694 0 : return 0;
695 0 : } else if( FD_LIKELY( res==FD_SSMANIFEST_PARSER_ADVANCE_DONE ) ) {
696 0 : ctx->flags.manifest_done = 1;
697 0 : }
698 0 : break;
699 0 : }
700 0 : case FD_SSPARSE_ADVANCE_STATUS_CACHE: {
701 0 : fd_slot_delta_parser_advance_result_t sd_result[1];
702 0 : ulong bytes_remaining = result->status_cache.data_sz;
703 :
704 0 : while( bytes_remaining ) {
705 0 : int res = fd_slot_delta_parser_consume( ctx->slot_delta_parser,
706 0 : result->status_cache.data,
707 0 : bytes_remaining,
708 0 : sd_result );
709 0 : if( FD_UNLIKELY( res<0 ) ) {
710 0 : FD_LOG_WARNING(( "error while parsing slot deltas in status cache" ));
711 0 : transition_malformed( ctx, stem );
712 0 : return 0;
713 0 : } else if( FD_LIKELY( res==FD_SLOT_DELTA_PARSER_ADVANCE_GROUP ) ) {
714 0 : if( FD_UNLIKELY( ctx->blockhash_offsets_len>=FD_SNAPIN_MAX_SLOT_DELTA_GROUPS ) ) FD_LOG_ERR(( "blockhash offsets overflow, max is %lu", FD_SNAPIN_MAX_SLOT_DELTA_GROUPS ));
715 :
716 0 : memcpy( ctx->blockhash_offsets[ ctx->blockhash_offsets_len ].blockhash, sd_result->group.blockhash, 32UL );
717 0 : ctx->blockhash_offsets[ ctx->blockhash_offsets_len ].txnhash_offset = sd_result->group.txnhash_offset;
718 0 : ctx->blockhash_offsets_len++;
719 0 : } else if( FD_LIKELY( res==FD_SLOT_DELTA_PARSER_ADVANCE_ENTRY ) ) {
720 0 : if( FD_UNLIKELY( ctx->txncache_entries_len>=FD_SNAPIN_TXNCACHE_MAX_ENTRIES ) ) FD_LOG_ERR(( "txncache entries overflow, max is %lu", FD_SNAPIN_TXNCACHE_MAX_ENTRIES ));
721 0 : ctx->txncache_entries[ ctx->txncache_entries_len++ ] = *sd_result->entry;
722 0 : }
723 :
724 0 : bytes_remaining -= sd_result->bytes_consumed;
725 0 : result->status_cache.data += sd_result->bytes_consumed;
726 0 : }
727 :
728 0 : ctx->flags.status_cache_done = fd_slot_delta_parser_consume( ctx->slot_delta_parser, result->status_cache.data, 0UL, sd_result )==FD_SLOT_DELTA_PARSER_ADVANCE_DONE;
729 0 : break;
730 0 : }
731 0 : case FD_SSPARSE_ADVANCE_ACCOUNT_HEADER:
732 0 : early_exit = fd_snapin_process_account_header( ctx, result );
733 0 : break;
734 0 : case FD_SSPARSE_ADVANCE_ACCOUNT_DATA:
735 0 : early_exit = fd_snapin_process_account_data( ctx, result );
736 :
737 : /* We exepect ConfigKeys Vec to be length 2. We expect the size
738 : of ConfigProgram-owned accounts to be
739 : FD_GUI_CONFIG_PARSE_MAX_VALID_ACCT_SZ, since this the size
740 : that the solana CLI allocates for them. Although the Config
741 : program itself does not enforce this limit, the vast majority
742 : of accounts (with a tiny number of excpetions on devnet) are
743 : maintained with the solana cli. */
744 0 : if( FD_UNLIKELY( ctx->gui_out.idx!=ULONG_MAX && !memcmp( result->account_data.owner, fd_solana_config_program_id.key, sizeof(fd_hash_t) ) && result->account_data.data_sz && *(uchar *)result->account_data.data==2UL && result->account_data.data_sz<=FD_GUI_CONFIG_PARSE_MAX_VALID_ACCT_SZ ) ) {
745 0 : uchar * acct = fd_chunk_to_laddr( ctx->gui_out.mem, ctx->gui_out.chunk );
746 0 : fd_memcpy( acct, result->account_data.data, result->account_data.data_sz );
747 :
748 0 : fd_stem_publish( stem, ctx->gui_out.idx, 0UL, ctx->gui_out.chunk, result->account_data.data_sz, 0UL, 0UL, 0UL );
749 0 : ctx->gui_out.chunk = fd_dcache_compact_next( ctx->gui_out.chunk, result->account_data.data_sz, ctx->gui_out.chunk0, ctx->gui_out.wmark );
750 0 : early_exit = 1;
751 0 : }
752 0 : break;
753 0 : case FD_SSPARSE_ADVANCE_ACCOUNT_BATCH:
754 0 : early_exit = fd_snapin_process_account_batch( ctx, result, NULL );
755 0 : break;
756 0 : case FD_SSPARSE_ADVANCE_DONE:
757 0 : ctx->state = FD_SNAPSHOT_STATE_FINISHING;
758 0 : break;
759 0 : default:
760 0 : FD_LOG_ERR(( "unexpected fd_ssparse_advance result %d", res ));
761 0 : break;
762 0 : }
763 :
764 0 : if( FD_UNLIKELY( !ctx->flags.manifest_processed && ctx->flags.manifest_done && ctx->flags.status_cache_done ) ) {
765 0 : process_manifest( ctx );
766 0 : if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_ERROR ) ) break;
767 0 : ctx->flags.manifest_processed = 1;
768 0 : }
769 :
770 0 : ctx->in.pos += result->bytes_consumed;
771 0 : if( FD_LIKELY( ctx->full ) ) ctx->metrics.full_bytes_read += result->bytes_consumed;
772 0 : else ctx->metrics.incremental_bytes_read += result->bytes_consumed;
773 :
774 0 : if( FD_UNLIKELY( early_exit ) ) break;
775 0 : }
776 :
777 0 : int reprocess_frag = ctx->in.pos<sz;
778 0 : if( FD_LIKELY( !reprocess_frag ) ) ctx->in.pos = 0UL;
779 0 : return reprocess_frag;
780 0 : }
781 :
782 : static int
783 0 : validate_capitalization( fd_snapin_tile_t * ctx ) {
784 : /* Capitalization is checked only when lthash verification is
785 : enabled, since a snapshot that fails lthash would most probably
786 : fail capitalization as well. This also matches the snapshot
787 : load pipeline check under vinyl. */
788 0 : if( FD_UNLIKELY( ctx->lthash_disabled ) ) return 0;
789 0 : if( FD_UNLIKELY( ctx->capitalization!=ctx->manifest_capitalization ) ) {
790 : /* SnapshotError::MismatchedCapitalization
791 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.2/runtime/src/snapshot_bank_utils.rs#L217 */
792 0 : FD_LOG_WARNING(( "%s snapshot manifest capitalization %lu does not match computed capitalization %lu",
793 0 : ctx->full?"full":"incr", ctx->manifest_capitalization, ctx->capitalization ));
794 0 : return -1;
795 0 : }
796 0 : return 0;
797 0 : }
798 :
799 : static void
800 : handle_control_frag( fd_snapin_tile_t * ctx,
801 : fd_stem_context_t * stem,
802 : ulong sig,
803 0 : ulong chunk ) {
804 0 : if( ctx->state==FD_SNAPSHOT_STATE_ERROR && sig!=FD_SNAPSHOT_MSG_CTRL_FAIL ) {
805 : /* Control messages move along the snapshot load pipeline. Since
806 : error conditions can be triggered by any tile in the pipeline,
807 : it is possible to be in error state and still receive otherwise
808 : valid messages. Only a fail message can revert this. */
809 0 : return;
810 0 : };
811 :
812 0 : int forward_msg = 1;
813 :
814 0 : switch( sig ) {
815 0 : case FD_SNAPSHOT_MSG_CTRL_INIT_FULL:
816 0 : case FD_SNAPSHOT_MSG_CTRL_INIT_INCR: {
817 0 : FD_TEST( ctx->state==FD_SNAPSHOT_STATE_IDLE );
818 0 : ctx->state = FD_SNAPSHOT_STATE_PROCESSING;
819 0 : fd_ssparse_batch_enable( ctx->ssparse, ctx->use_vinyl || sig==FD_SNAPSHOT_MSG_CTRL_INIT_FULL );
820 0 : ctx->full = sig==FD_SNAPSHOT_MSG_CTRL_INIT_FULL;
821 0 : ctx->txncache_entries_len = 0UL;
822 0 : ctx->blockhash_offsets_len = 0UL;
823 0 : ctx->manifest_capitalization = 0UL;
824 0 : fd_txncache_reset( ctx->txncache );
825 0 : fd_ssparse_reset( ctx->ssparse );
826 0 : fd_ssmanifest_parser_init( ctx->manifest_parser, fd_chunk_to_laddr( ctx->manifest_out.mem, ctx->manifest_out.chunk ) );
827 0 : fd_slot_delta_parser_init( ctx->slot_delta_parser );
828 0 : fd_memset( &ctx->flags, 0, sizeof(ctx->flags) );
829 0 : fd_memset( &ctx->vinyl_op, 0, sizeof(ctx->vinyl_op) );
830 :
831 : /* Rewind metric counters (no-op unless recovering from a fail) */
832 0 : if( sig==FD_SNAPSHOT_MSG_CTRL_INIT_FULL ) {
833 0 : ctx->metrics.accounts_loaded = ctx->metrics.full_accounts_loaded = 0;
834 0 : ctx->metrics.accounts_replaced = ctx->metrics.full_accounts_replaced = 0;
835 0 : ctx->metrics.accounts_ignored = ctx->metrics.full_accounts_ignored = 0;
836 0 : ctx->metrics.full_bytes_read = 0UL;
837 0 : ctx->metrics.incremental_bytes_read = 0UL;
838 0 : ctx->full_genesis_creation_time_seconds = 0UL;
839 0 : ctx->capitalization = 0UL;
840 0 : ctx->dup_capitalization = 0UL;
841 0 : ctx->recovery.capitalization = 0UL;
842 0 : } else {
843 0 : ctx->metrics.accounts_loaded = ctx->metrics.full_accounts_loaded;
844 0 : ctx->metrics.accounts_replaced = ctx->metrics.full_accounts_replaced;
845 0 : ctx->metrics.accounts_ignored = ctx->metrics.full_accounts_ignored;
846 0 : ctx->metrics.incremental_bytes_read = 0UL;
847 :
848 0 : fd_funk_txn_xid_t incremental_xid = { .ul={ LONG_MAX, LONG_MAX } };
849 0 : fd_accdb_attach_child( ctx->accdb_admin, ctx->xid, &incremental_xid );
850 0 : fd_funk_txn_xid_copy( ctx->xid, &incremental_xid );
851 :
852 0 : ctx->capitalization = ctx->recovery.capitalization;
853 0 : ctx->dup_capitalization = 0UL;
854 0 : }
855 :
856 : /* Save the slot advertised by the snapshot peer and verify it
857 : against the slot in the snapshot manifest. For downloaded
858 : snapshots, this is simply a best estimate. The actual
859 : advertised slot for downloaded snapshots is received in a
860 : separate fd_ssctrl_meta_t message below. */
861 0 : fd_ssctrl_init_t const * msg = fd_chunk_to_laddr_const( ctx->in.wksp, chunk );
862 0 : ctx->advertised_slot = msg->slot;
863 0 : fd_memcpy( ctx->advertised_hash, msg->snapshot_hash, FD_HASH_FOOTPRINT );
864 0 : break;
865 0 : }
866 :
867 0 : case FD_SNAPSHOT_MSG_CTRL_FINI: {
868 : /* This is a special case: handle_data_frag must have already
869 : processed FD_SSPARSE_ADVANCE_DONE and moved the state into
870 : FD_SNAPSHOT_STATE_FINISHING. */
871 0 : FD_TEST( ctx->state==FD_SNAPSHOT_STATE_FINISHING );
872 0 : ctx->state = FD_SNAPSHOT_STATE_FINISHING;
873 0 : break;
874 0 : }
875 :
876 0 : case FD_SNAPSHOT_MSG_CTRL_NEXT: {
877 0 : FD_TEST( ctx->state==FD_SNAPSHOT_STATE_FINISHING );
878 0 : ctx->state = FD_SNAPSHOT_STATE_IDLE;
879 :
880 0 : if( !ctx->use_vinyl ) {
881 0 : if( FD_UNLIKELY( verify_slot_deltas_with_slot_history( ctx ) ) ) {
882 0 : FD_LOG_WARNING(( "slot deltas verification failed for full snapshot" ));
883 0 : transition_malformed( ctx, stem );
884 0 : forward_msg = 0;
885 0 : break;
886 0 : }
887 :
888 0 : ctx->capitalization = fd_ulong_sat_sub( ctx->capitalization, ctx->dup_capitalization );
889 0 : if( FD_UNLIKELY( validate_capitalization( ctx )!=0 ) ) {
890 0 : transition_malformed( ctx, stem );
891 0 : forward_msg = 0;
892 0 : break;
893 0 : }
894 :
895 0 : ctx->recovery.capitalization = ctx->capitalization;
896 0 : }
897 :
898 : /* Backup metric counters */
899 0 : ctx->metrics.full_accounts_loaded = ctx->metrics.accounts_loaded;
900 0 : ctx->metrics.full_accounts_replaced = ctx->metrics.accounts_replaced;
901 0 : ctx->metrics.full_accounts_ignored = ctx->metrics.accounts_ignored;
902 0 : break;
903 0 : }
904 :
905 0 : case FD_SNAPSHOT_MSG_CTRL_DONE: {
906 0 : FD_TEST( ctx->state==FD_SNAPSHOT_STATE_FINISHING );
907 0 : ctx->state = FD_SNAPSHOT_STATE_IDLE;
908 :
909 0 : if( !ctx->use_vinyl ) {
910 0 : if( FD_UNLIKELY( verify_slot_deltas_with_slot_history( ctx ) ) ) {
911 0 : if( ctx->full ) FD_LOG_WARNING(( "slot deltas verification failed for full snapshot" ));
912 0 : else FD_LOG_WARNING(( "slot deltas verification failed for incremental snapshot" ));
913 0 : transition_malformed( ctx, stem );
914 0 : forward_msg = 0;
915 0 : break;
916 0 : }
917 :
918 0 : ctx->capitalization = fd_ulong_sat_sub( ctx->capitalization, ctx->dup_capitalization );
919 0 : if( FD_UNLIKELY( validate_capitalization( ctx )!=0 ) ) {
920 0 : transition_malformed( ctx, stem );
921 0 : forward_msg = 0;
922 0 : break;
923 0 : }
924 0 : }
925 :
926 : /* Publish any remaining funk txn */
927 0 : if( FD_LIKELY( fd_funk_last_publish_is_frozen( ctx->funk ) ) ) {
928 0 : ctx->accdb_admin->base.gc_root_cnt = 0UL;
929 0 : ctx->accdb_admin->base.reclaim_cnt = 0UL;
930 0 : fd_accdb_advance_root( ctx->accdb_admin, ctx->xid );
931 0 : ctx->metrics.accounts_replaced += ctx->accdb_admin->base.gc_root_cnt;
932 : /* If an incremental snapshot 'reclaims' (deletes) an account,
933 : this removes both the full snapshot's original account, and
934 : the incremental snapshot's tombstone record. Thus account
935 : for twice in metrics. */
936 0 : ctx->metrics.accounts_loaded -= ctx->accdb_admin->base.reclaim_cnt;
937 0 : ctx->metrics.accounts_replaced += ctx->accdb_admin->base.reclaim_cnt;
938 0 : }
939 0 : FD_TEST( !fd_funk_last_publish_is_frozen( ctx->funk ) );
940 :
941 : /* Make 'Last published' XID equal the restored slot number */
942 0 : fd_funk_txn_xid_t target_xid = { .ul = { ctx->bank_slot, 0UL } };
943 0 : fd_accdb_attach_child( ctx->accdb_admin, ctx->xid, &target_xid );
944 0 : fd_accdb_advance_root( ctx->accdb_admin, &target_xid );
945 0 : fd_funk_txn_xid_copy( ctx->xid, &target_xid );
946 :
947 : /* Notify replay when snapshot is fully loaded and verified. */
948 0 : fd_stem_publish( stem, ctx->manifest_out.idx, fd_ssmsg_sig( FD_SSMSG_DONE ), 0UL, 0UL, 0UL, 0UL, 0UL );
949 0 : break;
950 0 : }
951 :
952 0 : case FD_SNAPSHOT_MSG_CTRL_ERROR: {
953 0 : FD_TEST( ctx->state!=FD_SNAPSHOT_STATE_SHUTDOWN );
954 0 : ctx->state = FD_SNAPSHOT_STATE_ERROR;
955 0 : break;
956 0 : }
957 :
958 0 : case FD_SNAPSHOT_MSG_CTRL_FAIL: {
959 0 : FD_TEST( ctx->state!=FD_SNAPSHOT_STATE_SHUTDOWN );
960 0 : ctx->state = FD_SNAPSHOT_STATE_IDLE;
961 0 : if( ctx->full ) {
962 0 : fd_accdb_v1_clear( ctx->accdb_admin );
963 0 : }
964 :
965 0 : if( !ctx->full ) {
966 0 : fd_accdb_cancel( ctx->accdb_admin, ctx->xid );
967 0 : fd_funk_txn_xid_copy( ctx->xid, fd_funk_last_publish( ctx->funk ) );
968 0 : }
969 0 : break;
970 0 : }
971 :
972 0 : case FD_SNAPSHOT_MSG_CTRL_SHUTDOWN: {
973 0 : FD_TEST( ctx->state==FD_SNAPSHOT_STATE_IDLE );
974 0 : ctx->state = FD_SNAPSHOT_STATE_SHUTDOWN;
975 0 : break;
976 0 : }
977 :
978 0 : default: {
979 0 : FD_LOG_ERR(( "unexpected control frag %s (%lu) in state %s (%lu)",
980 0 : fd_ssctrl_msg_ctrl_str( sig ), sig,
981 0 : fd_ssctrl_state_str( (ulong)ctx->state ), (ulong)ctx->state ));
982 0 : break;
983 0 : }
984 0 : }
985 :
986 : /* Forward the control message down the pipeline */
987 0 : if( FD_LIKELY( forward_msg ) ) {
988 0 : fd_stem_publish( stem, ctx->out_ct_idx, sig, 0UL, 0UL, 0UL, 0UL, 0UL );
989 0 : }
990 0 : }
991 :
992 : static inline int
993 : returnable_frag( fd_snapin_tile_t * ctx,
994 : ulong in_idx FD_PARAM_UNUSED,
995 : ulong seq FD_PARAM_UNUSED,
996 : ulong sig,
997 : ulong chunk,
998 : ulong sz,
999 : ulong ctl FD_PARAM_UNUSED,
1000 : ulong tsorig FD_PARAM_UNUSED,
1001 : ulong tspub FD_PARAM_UNUSED,
1002 0 : fd_stem_context_t * stem ) {
1003 0 : FD_TEST( ctx->state!=FD_SNAPSHOT_STATE_SHUTDOWN );
1004 :
1005 0 : ctx->stem = stem;
1006 0 : if( FD_UNLIKELY( sig==FD_SNAPSHOT_MSG_DATA ) ) return handle_data_frag( ctx, chunk, sz, stem );
1007 0 : else handle_control_frag( ctx, stem, sig, chunk );
1008 0 : ctx->stem = NULL;
1009 :
1010 0 : return 0;
1011 0 : }
1012 :
1013 : static ulong
1014 : populate_allowed_fds( fd_topo_t const * topo FD_PARAM_UNUSED,
1015 : fd_topo_tile_t const * tile FD_PARAM_UNUSED,
1016 : ulong out_fds_cnt,
1017 0 : int * out_fds ) {
1018 0 : if( FD_UNLIKELY( out_fds_cnt<2UL ) ) FD_LOG_ERR(( "invalid out_fds_cnt %lu", out_fds_cnt ));
1019 :
1020 0 : ulong out_cnt = 0;
1021 0 : out_fds[ out_cnt++ ] = 2UL; /* stderr */
1022 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) ) {
1023 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
1024 0 : }
1025 :
1026 0 : return out_cnt;
1027 0 : }
1028 :
1029 : static ulong
1030 : populate_allowed_seccomp( fd_topo_t const * topo,
1031 : fd_topo_tile_t const * tile,
1032 : ulong out_cnt,
1033 0 : struct sock_filter * out ) {
1034 0 : (void)topo; (void)tile;
1035 0 : populate_sock_filter_policy_fd_snapin_tile( out_cnt, out, (uint)fd_log_private_logfile_fd() );
1036 0 : return sock_filter_policy_fd_snapin_tile_instr_cnt;
1037 0 : }
1038 :
1039 : static void
1040 : privileged_init( fd_topo_t * topo,
1041 0 : fd_topo_tile_t * tile ) {
1042 0 : fd_snapin_tile_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1043 0 : memset( ctx, 0, sizeof(fd_snapin_tile_t) );
1044 0 : FD_TEST( fd_rng_secure( &ctx->seed, 8UL ) );
1045 :
1046 0 : if( tile->snapin.use_vinyl ) {
1047 0 : ctx->use_vinyl = 1;
1048 0 : }
1049 0 : }
1050 :
1051 : static inline fd_snapin_out_link_t
1052 : out1( fd_topo_t const * topo,
1053 : fd_topo_tile_t const * tile,
1054 0 : char const * name ) {
1055 0 : ulong idx = fd_topo_find_tile_out_link( topo, tile, name, 0UL );
1056 :
1057 0 : if( FD_UNLIKELY( idx==ULONG_MAX ) ) return (fd_snapin_out_link_t){ .idx = ULONG_MAX, .mem = NULL, .chunk0 = 0, .wmark = 0, .chunk = 0, .mtu = 0 };
1058 :
1059 0 : ulong mtu = topo->links[ tile->out_link_id[ idx ] ].mtu;
1060 0 : if( FD_UNLIKELY( mtu==0UL ) ) return (fd_snapin_out_link_t){ .idx = idx, .mem = NULL, .chunk0 = ULONG_MAX, .wmark = ULONG_MAX, .chunk = ULONG_MAX, .mtu = mtu };
1061 :
1062 0 : void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp;
1063 0 : ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache );
1064 0 : ulong wmark = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, mtu );
1065 0 : return (fd_snapin_out_link_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0, .mtu = mtu };
1066 0 : }
1067 :
1068 : FD_FN_UNUSED static void
1069 : unprivileged_init( fd_topo_t * topo,
1070 0 : fd_topo_tile_t * tile ) {
1071 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1072 :
1073 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1074 0 : fd_snapin_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_snapin_tile_t), sizeof(fd_snapin_tile_t) );
1075 0 : void * _ssparse = FD_SCRATCH_ALLOC_APPEND( l, fd_ssparse_align(), fd_ssparse_footprint( 1UL<<24UL ) );
1076 0 : void * _txncache = FD_SCRATCH_ALLOC_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->snapin.max_live_slots ) );
1077 0 : void * _manifest_parser = FD_SCRATCH_ALLOC_APPEND( l, fd_ssmanifest_parser_align(), fd_ssmanifest_parser_footprint() );
1078 0 : void * _sd_parser = FD_SCRATCH_ALLOC_APPEND( l, fd_slot_delta_parser_align(), fd_slot_delta_parser_footprint() );
1079 0 : ctx->blockhash_offsets = FD_SCRATCH_ALLOC_APPEND( l, alignof(blockhash_group_t), sizeof(blockhash_group_t)*FD_SNAPIN_MAX_SLOT_DELTA_GROUPS );
1080 :
1081 0 : if( tile->snapin.use_vinyl ) {
1082 : /* snapwm needs all txn_cache data in order to verify the slot
1083 : deltas with the slot history. To make this possible, snapin
1084 : uses the dcache of the snapin_txn link as the scratch memory.
1085 : The app field of the dcache is used to communicate the
1086 : txncache_entries_len value. */
1087 0 : fd_snapin_out_link_t snapin_txn = out1( topo, tile, "snapin_txn" );
1088 0 : FD_TEST( !!snapin_txn.mem );
1089 0 : fd_topo_link_t const * out_link_txn = &topo->links[ tile->out_link_id[ snapin_txn.idx ] ];
1090 0 : ulong depth = out_link_txn->depth;
1091 0 : FD_TEST( ( depth*snapin_txn.mtu )==( sizeof(fd_sstxncache_entry_t)*FD_SNAPIN_TXNCACHE_MAX_ENTRIES ) );
1092 0 : ctx->txncache_entries = fd_chunk_to_laddr( snapin_txn.mem, snapin_txn.chunk0 );
1093 0 : ctx->txncache_entries_len_vinyl_ptr = (ulong*)fd_dcache_app_laddr( out_link_txn->dcache );
1094 0 : memset( ctx->txncache_entries_len_vinyl_ptr, 0, sizeof(ulong) );
1095 0 : } else {
1096 0 : ctx->txncache_entries = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sstxncache_entry_t), sizeof(fd_sstxncache_entry_t)*FD_SNAPIN_TXNCACHE_MAX_ENTRIES );
1097 0 : ctx->txncache_entries_len_vinyl_ptr = NULL;
1098 0 : }
1099 :
1100 0 : ctx->full = 1;
1101 0 : ctx->state = FD_SNAPSHOT_STATE_IDLE;
1102 0 : ctx->lthash_disabled = tile->snapin.lthash_disabled;
1103 :
1104 0 : ctx->boot_timestamp = fd_log_wallclock();
1105 :
1106 0 : ulong funk_obj_id; FD_TEST( (funk_obj_id = fd_pod_query_ulong( topo->props, "funk", ULONG_MAX ) )!=ULONG_MAX );
1107 0 : ulong funk_locks_obj_id; FD_TEST( (funk_locks_obj_id = fd_pod_query_ulong( topo->props, "funk_locks", ULONG_MAX ) )!=ULONG_MAX );
1108 0 : void * shfunk = fd_topo_obj_laddr( topo, funk_obj_id );
1109 0 : void * shfunk_locks = fd_topo_obj_laddr( topo, funk_locks_obj_id );
1110 0 : FD_TEST( fd_accdb_admin_v1_init( ctx->accdb_admin, shfunk, shfunk_locks ) );
1111 0 : FD_TEST( fd_accdb_user_v1_init ( ctx->accdb, shfunk, shfunk_locks, tile->snapin.accdb_max_depth ) );
1112 0 : ctx->funk = fd_accdb_user_v1_funk( ctx->accdb );
1113 0 : fd_funk_txn_xid_copy( ctx->xid, fd_funk_root( ctx->funk ) );
1114 :
1115 0 : void * _txncache_shmem = fd_topo_obj_laddr( topo, tile->snapin.txncache_obj_id );
1116 0 : fd_txncache_shmem_t * txncache_shmem = fd_txncache_shmem_join( _txncache_shmem );
1117 0 : FD_TEST( txncache_shmem );
1118 0 : ctx->txncache = fd_txncache_join( fd_txncache_new( _txncache, txncache_shmem ) );
1119 0 : FD_TEST( ctx->txncache );
1120 :
1121 0 : ctx->txncache_entries_len = 0UL;
1122 0 : ctx->blockhash_offsets_len = 0UL;
1123 :
1124 0 : ctx->ssparse = fd_ssparse_new( _ssparse, 1UL<<24UL, ctx->seed );
1125 0 : FD_TEST( ctx->ssparse );
1126 :
1127 0 : ctx->manifest_parser = fd_ssmanifest_parser_join( fd_ssmanifest_parser_new( _manifest_parser ) );
1128 0 : FD_TEST( ctx->manifest_parser );
1129 :
1130 0 : ctx->slot_delta_parser = fd_slot_delta_parser_join( fd_slot_delta_parser_new( _sd_parser ) );
1131 0 : FD_TEST( ctx->slot_delta_parser );
1132 :
1133 0 : fd_memset( &ctx->metrics, 0, sizeof(ctx->metrics) );
1134 :
1135 0 : if( FD_UNLIKELY( tile->kind_id ) ) FD_LOG_ERR(( "There can only be one `" NAME "` tile" ));
1136 0 : if( FD_UNLIKELY( tile->in_cnt!=1UL ) ) FD_LOG_ERR(( "tile `" NAME "` has %lu ins, expected 1", tile->in_cnt ));
1137 :
1138 0 : ctx->manifest_out = out1( topo, tile, "snapin_manif" );
1139 0 : ctx->gui_out = out1( topo, tile, "snapin_gui" );
1140 0 : ulong out_link_ct_idx = fd_topo_find_tile_out_link( topo, tile, "snapin_ct", 0UL );
1141 0 : if( out_link_ct_idx==ULONG_MAX ) out_link_ct_idx = fd_topo_find_tile_out_link( topo, tile, "snapin_ls", 0UL );
1142 0 : if( out_link_ct_idx==ULONG_MAX ) out_link_ct_idx = fd_topo_find_tile_out_link( topo, tile, "snapin_wm", 0UL );
1143 0 : if( FD_UNLIKELY( out_link_ct_idx==ULONG_MAX ) ) FD_LOG_ERR(( "tile `" NAME "` missing required out link `snapin_ct` or `snapin_ls` or `snapin_wm`" ));
1144 0 : fd_topo_link_t * snapin_out_link = &topo->links[ tile->out_link_id[ out_link_ct_idx ] ];
1145 0 : ctx->out_ct_idx = out_link_ct_idx;
1146 :
1147 0 : if( FD_UNLIKELY( ctx->out_ct_idx==ULONG_MAX ) ) FD_LOG_ERR(( "tile `" NAME "` missing required out link `snapin_ct` or `snapin_ls` or `snapin_wm`" ));
1148 0 : if( FD_UNLIKELY( ctx->manifest_out.idx==ULONG_MAX ) ) FD_LOG_ERR(( "tile `" NAME "` missing required out link `snapin_manif`" ));
1149 :
1150 0 : if( ( 0==strcmp( snapin_out_link->name, "snapin_ls" ) ) ||
1151 0 : ( 0==strcmp( snapin_out_link->name, "snapin_wm" ) ) ) {
1152 0 : ctx->hash_out = out1( topo, tile, snapin_out_link->name );
1153 0 : }
1154 :
1155 0 : fd_ssparse_reset( ctx->ssparse );
1156 0 : fd_ssmanifest_parser_init( ctx->manifest_parser, fd_chunk_to_laddr( ctx->manifest_out.mem, ctx->manifest_out.chunk ) );
1157 0 : fd_slot_delta_parser_init( ctx->slot_delta_parser );
1158 :
1159 0 : fd_topo_link_t const * in_link = &topo->links[ tile->in_link_id[ 0UL ] ];
1160 0 : FD_TEST( 0==strcmp( in_link->name, "snapdc_in" ) );
1161 0 : fd_topo_wksp_t const * in_wksp = &topo->workspaces[ topo->objs[ in_link->dcache_obj_id ].wksp_id ];
1162 0 : ctx->in.wksp = in_wksp->wksp;
1163 0 : ctx->in.chunk0 = fd_dcache_compact_chunk0( ctx->in.wksp, in_link->dcache );
1164 0 : ctx->in.wmark = fd_dcache_compact_wmark( ctx->in.wksp, in_link->dcache, in_link->mtu );
1165 0 : ctx->in.mtu = in_link->mtu;
1166 0 : ctx->in.pos = 0UL;
1167 :
1168 0 : ctx->buffered_batch.batch_cnt = 0UL;
1169 0 : ctx->buffered_batch.remaining_idx = 0UL;
1170 :
1171 0 : ctx->advertised_slot = 0UL;
1172 0 : ctx->bank_slot = 0UL;
1173 0 : ctx->epoch = 0UL;
1174 :
1175 0 : ctx->full_genesis_creation_time_seconds = 0UL;
1176 0 : ctx->manifest_capitalization = 0UL;
1177 0 : ctx->capitalization = 0UL;
1178 0 : ctx->dup_capitalization = 0UL;
1179 0 : ctx->recovery.capitalization = 0UL;
1180 :
1181 0 : fd_memset( &ctx->flags, 0, sizeof(ctx->flags) );
1182 :
1183 0 : if( tile->snapin.use_vinyl ) {
1184 0 : ctx->use_vinyl = 1;
1185 0 : }
1186 0 : }
1187 :
1188 : /* Control fragments can result in one extra publish to forward the
1189 : message down the pipeline, in addition to the result / malformed
1190 : message. Can send one duplicate account message as well. */
1191 0 : #define STEM_BURST 3UL
1192 :
1193 0 : #define STEM_LAZY 1000L
1194 :
1195 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_snapin_tile_t
1196 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_snapin_tile_t)
1197 :
1198 : #define STEM_CALLBACK_SHOULD_SHUTDOWN should_shutdown
1199 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
1200 0 : #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
1201 :
1202 : #include "../../disco/stem/fd_stem.c"
1203 :
1204 : fd_topo_run_tile_t fd_tile_snapin = {
1205 : .name = NAME,
1206 : .populate_allowed_fds = populate_allowed_fds,
1207 : .populate_allowed_seccomp = populate_allowed_seccomp,
1208 : .scratch_align = scratch_align,
1209 : .scratch_footprint = scratch_footprint,
1210 : .privileged_init = privileged_init,
1211 : .unprivileged_init = unprivileged_init,
1212 : .run = stem_run,
1213 : };
1214 :
1215 : #undef NAME
|