Line data Source code
1 : #include "utils/fd_ssctrl.h"
2 : #include "utils/fd_ssload.h"
3 : #include "utils/fd_ssmsg.h"
4 : #include "utils/fd_ssparse.h"
5 : #include "utils/fd_ssmanifest_parser.h"
6 : #include "utils/fd_slot_delta_parser.h"
7 :
8 : #include "../../disco/topo/fd_topo.h"
9 : #include "../../disco/metrics/fd_metrics.h"
10 : #include "../../disco/gui/fd_gui_config_parse.h"
11 : #include "../../flamenco/runtime/fd_txncache.h"
12 : #include "../../flamenco/runtime/fd_system_ids.h"
13 : #include "../../flamenco/runtime/fd_hashes.h"
14 : #include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h"
15 : #include "../../flamenco/runtime/sysvar/fd_sysvar_slot_history.h"
16 :
17 : #include "../../flamenco/runtime/fd_txncache.h"
18 : #include "../../flamenco/runtime/fd_bank.h"
19 : #include "../../flamenco/features/fd_feature_snoop.h"
20 : #include "../../flamenco/stakes/fd_stake_types.h"
21 : #include "../../disco/stem/fd_stem.h"
22 : #include "../../flamenco/accdb/fd_accdb.h"
23 : #include "../../disco/events/generated/fd_event_gen.h"
24 :
25 : #include "generated/fd_snapin_tile_seccomp.h"
26 :
27 : #define NAME "snapin"
28 :
29 : /* The snapin tile is a state machine that parses and loads a full
30 : and optionally an incremental snapshot. It is currently responsible
31 : for loading accounts into an in-memory database, though this may
32 : change. */
33 :
34 : /* 300 root slots in the slot deltas array, and each one references all
35 : 151 prior blockhashes that it's able to. */
36 : #define FD_SNAPIN_MAX_SLOT_DELTA_GROUPS (300UL*151UL)
37 :
38 : struct fd_blockhash_entry {
39 : fd_hash_t blockhash;
40 :
41 : struct {
42 : ulong prev;
43 : ulong next;
44 : } map;
45 : };
46 :
47 : typedef struct fd_blockhash_entry fd_blockhash_entry_t;
48 :
49 : #define MAP_NAME blockhash_map
50 0 : #define MAP_KEY blockhash
51 : #define MAP_KEY_T fd_hash_t
52 : #define MAP_ELE_T fd_blockhash_entry_t
53 0 : #define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1), sizeof(fd_hash_t)))
54 0 : #define MAP_KEY_HASH(key,seed) (fd_hash((seed),(key),sizeof(fd_hash_t)))
55 0 : #define MAP_PREV map.prev
56 0 : #define MAP_NEXT map.next
57 : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
58 : #include "../../util/tmpl/fd_map_chain.c"
59 :
60 : /* The most root slots Agave could possibly serve in a snapshot. The
61 : txnpage pool sizing assumes staged entries never exceed this. */
62 : #define FD_SNAPIN_TXNCACHE_MAX_ENTRIES (FD_TXNCACHE_SNAPSHOT_SLOT_DELTA_MAX*FD_PACK_MAX_TXNCACHE_TXN_PER_SLOT)
63 :
64 : FD_STATIC_ASSERT( FD_SLOT_DELTA_MAX_ENTRIES==FD_TXNCACHE_SNAPSHOT_SLOT_DELTA_MAX, slot_delta_max );
65 :
66 : struct blockhash_group {
67 : uchar blockhash[ 32UL ];
68 : ulong txnhash_offset;
69 : };
70 :
71 : typedef struct blockhash_group blockhash_group_t;
72 :
73 : struct fd_snapin_out_link {
74 : ulong idx;
75 : fd_wksp_t * mem;
76 : ulong chunk0;
77 : ulong wmark;
78 : ulong chunk;
79 : ulong mtu;
80 : };
81 : typedef struct fd_snapin_out_link fd_snapin_out_link_t;
82 :
83 : struct fd_snapin_tile {
84 : int state;
85 : uint full : 1; /* loading a full snapshot? */
86 :
87 : ulong seed;
88 : long boot_timestamp;
89 :
90 : fd_accdb_t * accdb;
91 : fd_txncache_t * txncache;
92 :
93 : fd_banks_t * banks;
94 : fd_bank_t * bank;
95 :
96 : fd_feature_snoop_t feature_snoop[1];
97 : struct {
98 : int capturing;
99 : fd_pubkey_t pubkey;
100 : ulong lamports;
101 : uchar owner[ 32UL ];
102 : ulong need;
103 : ulong write_pos;
104 : uchar buf[ sizeof(fd_feature_t) ];
105 : } feature_reasm;
106 : struct {
107 : int capturing;
108 : fd_pubkey_t pubkey;
109 : ulong lamports;
110 : ulong data_len;
111 : ulong write_pos;
112 : uchar buf[ sizeof(fd_stake_state_t) ];
113 : } stake_reasm;
114 :
115 : fd_ssparse_t ssparse[1];
116 : fd_ssmanifest_parser_t * manifest_parser;
117 : fd_slot_delta_parser_t * slot_delta_parser;
118 :
119 : struct {
120 : int manifest_done;
121 : int status_cache_done;
122 : int manifest_processed;
123 : } flags;
124 :
125 : ulong advertised_slot;
126 : ulong bank_slot;
127 : ulong epoch;
128 :
129 : fd_epoch_schedule_t epoch_schedule;
130 :
131 : ulong full_genesis_creation_time_seconds;
132 : uchar advertised_hash[ FD_HASH_FOOTPRINT ];
133 :
134 : ulong capitalization; /* tracks capitalization of all loaded accounts in the current snapshot */
135 : ulong dup_capitalization; /* tracks capitalization of duplicate accounts encountered during incremental snapshot loading */
136 : ulong manifest_capitalization; /* capitalization according to the current snapshot manifest */
137 :
138 : struct {
139 : ulong capitalization;
140 : fd_accdb_snapshot_recovery_t accdb_metadata;
141 : fd_feature_snoop_t feature_snoop;
142 : } recovery; /* stores state from the last full snapshot for incremental revert */
143 :
144 : ulong blockhash_offsets_len;
145 : blockhash_group_t * blockhash_offsets;
146 :
147 : ulong txncache_entries_len;
148 : fd_sstxncache_entry_t * txncache_entries;
149 :
150 : fd_accdb_fork_id_t accdb_root_fork_id;
151 : fd_accdb_fork_id_t accdb_incr_fork_id; /* child fork for incremental writes (purge on failure) */
152 : fd_txncache_fork_id_t txncache_root_fork_id;
153 :
154 : struct {
155 : ulong full_bytes_read;
156 : ulong incremental_bytes_read;
157 :
158 : /* Account counters (full + incremental) */
159 : ulong accounts_loaded;
160 : ulong accounts_replaced;
161 : ulong accounts_ignored;
162 :
163 : /* Account counters (snapshot taken for full snapshot only) */
164 : ulong full_accounts_loaded;
165 : ulong full_accounts_replaced;
166 : ulong full_accounts_ignored;
167 :
168 : /* Persistent counters */
169 : ulong total_accounts_processed;
170 : ulong total_account_batches_processed;
171 : } metrics;
172 :
173 : struct {
174 : fd_wksp_t * wksp;
175 : ulong chunk0;
176 : ulong wmark;
177 : ulong mtu;
178 : ulong pos;
179 : } in;
180 :
181 : fd_snapin_out_link_t ct_out;
182 : fd_snapin_out_link_t manifest_out;
183 : fd_snapin_out_link_t gui_out;
184 :
185 : ulong gui_config_acct_sz; /* total expected account data length (0 when not accumulating) */
186 : ulong gui_config_acct_off; /* bytes accumulated so far into the current gui_out link chunk */
187 :
188 : /* In-memory copy of the SlotHistory sysvar account, captured by
189 : snooping the account stream as the snapshot is loaded. The accdb
190 : read-back path is unsafe at the end of load because the snapwr
191 : tile may not have flushed the bytes yet; the snoop path observes
192 : the bytes directly. The captured copy is then used by
193 : verify_slot_deltas_with_slot_history.
194 :
195 : Replacement uses the same precedence as fd_accdb_snapshot_write_*:
196 : a write with slot >= captured.slot replaces the captured copy.
197 : This handles the incremental snapshot superseding the full. */
198 : struct {
199 : int captured;
200 : int capturing; /* streaming-path: currently appending data for this account */
201 : ulong slot;
202 : ulong lamports;
203 : ulong data_len;
204 : uchar owner[ 32UL ];
205 : int executable;
206 : ulong write_pos; /* bytes written into buf during the current streaming capture */
207 : uchar buf[ FD_SYSVAR_SLOT_HISTORY_BINCODE_SZ ];
208 : } slot_history;
209 : };
210 :
211 : typedef struct fd_snapin_tile fd_snapin_tile_t;
212 :
213 : static void
214 0 : format_count( char * out, ulong out_sz, ulong n ) {
215 0 : if( n>=1000000UL ) FD_TEST( fd_cstr_printf_check( out, out_sz, NULL, "%.1fM", (double)n/1e6 ) );
216 0 : else if( n>=1000UL ) FD_TEST( fd_cstr_printf_check( out, out_sz, NULL, "%.1fK", (double)n/1e3 ) );
217 0 : else FD_TEST( fd_cstr_printf_check( out, out_sz, NULL, "%lu", n ) );
218 0 : }
219 :
220 : static inline int
221 0 : should_shutdown( fd_snapin_tile_t * ctx ) {
222 0 : if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_SHUTDOWN ) ) {
223 0 : ulong accounts_dup = ctx->metrics.accounts_ignored + ctx->metrics.accounts_replaced;
224 0 : long elapsed_ns = fd_log_wallclock() - ctx->boot_timestamp;
225 0 : char loaded_buf[ 32 ];
226 0 : char dup_buf [ 32 ];
227 0 : format_count( loaded_buf, sizeof(loaded_buf), ctx->metrics.accounts_loaded );
228 0 : format_count( dup_buf, sizeof(dup_buf), accounts_dup );
229 0 : FD_LOG_NOTICE(( "loaded %s accounts %s(%s dups)%s from snapshot in %.3f seconds",
230 0 : loaded_buf, fd_log_style_dim(), dup_buf, fd_log_style_normal(), (double)elapsed_ns/1e9 ));
231 0 : }
232 0 : return ctx->state==FD_SNAPSHOT_STATE_SHUTDOWN;
233 0 : }
234 :
235 : static ulong
236 0 : scratch_align( void ) {
237 0 : return 512UL;
238 0 : }
239 :
240 : static ulong
241 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
242 0 : ulong l = FD_LAYOUT_INIT;
243 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_snapin_tile_t), sizeof(fd_snapin_tile_t) );
244 0 : l = FD_LAYOUT_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->snapin.max_live_slots ) );
245 0 : l = FD_LAYOUT_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->snapin.max_live_slots ) );
246 0 : l = FD_LAYOUT_APPEND( l, fd_ssmanifest_parser_align(), fd_ssmanifest_parser_footprint() );
247 0 : l = FD_LAYOUT_APPEND( l, fd_slot_delta_parser_align(), fd_slot_delta_parser_footprint() );
248 0 : l = FD_LAYOUT_APPEND( l, alignof(blockhash_group_t), sizeof(blockhash_group_t)*FD_SNAPIN_MAX_SLOT_DELTA_GROUPS );
249 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_sstxncache_entry_t), sizeof(fd_sstxncache_entry_t)*FD_SNAPIN_TXNCACHE_MAX_ENTRIES );
250 0 : return FD_LAYOUT_FINI( l, scratch_align() );
251 0 : }
252 :
253 : static void
254 0 : metrics_write( fd_snapin_tile_t * ctx ) {
255 0 : fd_accdb_flush_metrics( ctx->accdb );
256 :
257 0 : FD_MGAUGE_SET( SNAPIN, STATE, (ulong)ctx->state );
258 0 : FD_MGAUGE_SET( SNAPIN, FULL_BYTES_READ, ctx->metrics.full_bytes_read );
259 0 : FD_MGAUGE_SET( SNAPIN, INCREMENTAL_BYTES_READ, ctx->metrics.incremental_bytes_read );
260 0 : FD_MGAUGE_SET( SNAPIN, ACCOUNT_LOADED, ctx->metrics.accounts_loaded );
261 0 : FD_MGAUGE_SET( SNAPIN, ACCOUNT_REPLACED, ctx->metrics.accounts_replaced );
262 0 : FD_MGAUGE_SET( SNAPIN, ACCOUNT_IGNORED, ctx->metrics.accounts_ignored );
263 0 : FD_MCNT_SET ( SNAPIN, ACCOUNT_PROCESSED, ctx->metrics.total_accounts_processed );
264 0 : FD_MCNT_SET ( SNAPIN, ACCOUNT_BATCH_PROCESSED, ctx->metrics.total_account_batches_processed );
265 0 : }
266 :
267 : /* verify_slot_deltas_with_slot_history verifies the 'SlotHistory'
268 : sysvar account after loading a snapshot. Uses the in-memory copy
269 : captured by snooping the account stream (process_account_batch /
270 : process_account_header / process_account_data). We cannot read
271 : from accdb at this point because the snapwr tile's pwritev2 may
272 : not have completed yet for the SlotHistory bytes.
273 :
274 : Returns 0 if verification passed, -1 if not. */
275 :
276 : static int
277 0 : verify_slot_deltas_with_slot_history( fd_snapin_tile_t * ctx ) {
278 0 : if( FD_UNLIKELY( !ctx->slot_history.captured ) ) {
279 0 : FD_LOG_WARNING(( "SlotHistory sysvar account was not present in the snapshot stream" ));
280 0 : return -1;
281 0 : }
282 0 : if( FD_UNLIKELY( !ctx->slot_history.lamports || !ctx->slot_history.data_len ) ) {
283 0 : FD_LOG_WARNING(( "SlotHistory sysvar account missing or empty" ));
284 0 : return -1;
285 0 : }
286 0 : if( FD_UNLIKELY( !fd_memeq( ctx->slot_history.owner, fd_sysvar_owner_id.uc, sizeof(fd_pubkey_t) ) ) ) {
287 0 : FD_BASE58_ENCODE_32_BYTES( ctx->slot_history.owner, owner_b58 );
288 0 : FD_LOG_WARNING(( "SlotHistory sysvar owner is invalid: %s != sysvar_owner_id", owner_b58 ));
289 0 : return -1;
290 0 : }
291 :
292 0 : fd_slot_history_view_t view[1];
293 0 : if( FD_UNLIKELY( !fd_sysvar_slot_history_view( view, ctx->slot_history.buf, ctx->slot_history.data_len ) ) ) {
294 0 : FD_LOG_WARNING(( "SlotHistory sysvar account data is corrupt" ));
295 0 : return -1;
296 0 : }
297 :
298 : /* Sanity checks for slot history:
299 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L586 */
300 :
301 0 : ulong newest_slot = view->next_slot - 1UL;
302 0 : if( FD_UNLIKELY( newest_slot!=ctx->bank_slot ) ) {
303 : /* VerifySlotHistoryError::InvalidNewestSlot
304 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L621 */
305 0 : FD_LOG_WARNING(( "SlotHistory sysvar has an invalid newest slot: %lu != bank slot: %lu", newest_slot, ctx->bank_slot ));
306 0 : return -1;
307 0 : }
308 :
309 0 : if( FD_UNLIKELY( view->bits_len!=FD_SLOT_HISTORY_MAX_ENTRIES ) ) {
310 : /* VerifySlotHistoryError::InvalidNumEntries
311 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L625 */
312 0 : FD_LOG_WARNING(( "SlotHistory sysvar has invalid number of entries: %lu != expected: %lu", view->bits_len, FD_SLOT_HISTORY_MAX_ENTRIES ));
313 0 : return -1;
314 0 : }
315 :
316 : /* All slots in the txncache should be present in the slot history */
317 0 : for( ulong i=0UL; i<ctx->txncache_entries_len; i++ ) {
318 0 : fd_sstxncache_entry_t const * entry = &ctx->txncache_entries[i];
319 0 : if( FD_UNLIKELY( fd_sysvar_slot_history_find_slot( view, entry->slot )!=FD_SLOT_HISTORY_SLOT_FOUND ) ) {
320 : /* VerifySlotDeltasError::SlotNotFoundInHistory
321 : https://github.com/anza-xyz/agave/blob/v3.1.8/snapshots/src/error.rs#L144
322 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L593 */
323 0 : FD_LOG_WARNING(( "slot %lu missing from SlotHistory sysvar account", entry->slot ));
324 0 : return -1;
325 0 : }
326 0 : }
327 :
328 : /* The most recent slots (up to the number of slots in the txncache)
329 : in the SlotHistory should be present in the txncache. */
330 0 : fd_slot_delta_slot_set_t slot_set = fd_slot_delta_parser_slot_set( ctx->slot_delta_parser );
331 0 : if( FD_LIKELY( slot_set.ele_cnt ) ) {
332 0 : ulong oldest = newest_slot - slot_set.ele_cnt;
333 0 : for( ulong i=newest_slot; i>oldest; i-- ) {
334 0 : if( FD_LIKELY( fd_sysvar_slot_history_find_slot( view, i )==FD_SLOT_HISTORY_SLOT_FOUND ) ) {
335 0 : if( FD_UNLIKELY( slot_set_ele_query( slot_set.map, &i, NULL, slot_set.pool )==NULL ) ) {
336 : /* VerifySlotDeltasError::SlotNotFoundInDeltas
337 : https://github.com/anza-xyz/agave/blob/v3.1.8/snapshots/src/error.rs#L147
338 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L609 */
339 0 : FD_LOG_WARNING(( "slot %lu missing from slot deltas but present in SlotHistory", i ));
340 0 : return -1;
341 0 : }
342 0 : }
343 0 : }
344 0 : }
345 :
346 0 : return 0;
347 0 : }
348 :
349 : /* verification of epoch stakes from manifest
350 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L632 */
351 : static int
352 0 : verify_epoch_stakes( fd_snapshot_manifest_t const * manifest ) {
353 0 : fd_epoch_schedule_t epoch_schedule = (fd_epoch_schedule_t){
354 0 : .slots_per_epoch = manifest->epoch_schedule_params.slots_per_epoch,
355 0 : .leader_schedule_slot_offset = manifest->epoch_schedule_params.leader_schedule_slot_offset,
356 0 : .warmup = manifest->epoch_schedule_params.warmup,
357 0 : .first_normal_epoch = manifest->epoch_schedule_params.first_normal_epoch,
358 0 : .first_normal_slot = manifest->epoch_schedule_params.first_normal_slot,
359 0 : };
360 :
361 0 : ulong min_required_epoch = fd_slot_to_epoch( &epoch_schedule, manifest->slot, NULL );
362 0 : ulong max_required_epoch = fd_slot_to_leader_schedule_epoch( &epoch_schedule, manifest->slot );
363 :
364 : /* ensure all required epochs are present in epoch stakes */
365 0 : for( ulong i=min_required_epoch; i<=max_required_epoch; i++ ) {
366 0 : int found = 0;
367 0 : for( ulong j=0UL; j<FD_RUNTIME_MANIFEST_EPOCH_STAKES_LEN; j++ ) {
368 0 : if( manifest->epoch_stakes[j].epoch==i ) {
369 0 : found = 1;
370 0 : break;
371 0 : }
372 0 : }
373 :
374 0 : if( FD_UNLIKELY( !found ) ) {
375 : /* VerifyEpochStakesError::StakesNotFound
376 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L667 */
377 0 : FD_LOG_WARNING(( "stakes not found for epoch %lu in manifest", i ));
378 0 : return -1;
379 0 : }
380 0 : }
381 :
382 0 : return 0;
383 0 : }
384 :
385 : static int
386 : verify_slot_deltas_with_bank_slot( fd_snapin_tile_t * ctx,
387 0 : ulong bank_slot ) {
388 0 : for( ulong i=0UL; i<ctx->txncache_entries_len; i++ ) {
389 0 : fd_sstxncache_entry_t const * entry = &ctx->txncache_entries[i];
390 : /* VerifySlotDeltasError::SlotGreaterThanMaxRoot
391 : https://github.com/anza-xyz/agave/blob/v3.1.8/snapshots/src/error.rs#L138
392 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L550 */
393 0 : if( FD_UNLIKELY( entry->slot>bank_slot ) ) {
394 0 : FD_LOG_WARNING(( "entry slot %lu is greater than bank slot %lu", entry->slot, bank_slot ));
395 0 : return -1;
396 0 : }
397 0 : }
398 0 : return 0;
399 0 : }
400 :
401 : static int
402 : verify_bank_hash( fd_snapin_tile_t const * ctx,
403 0 : fd_snapshot_manifest_t const * manifest ) {
404 0 : if( FD_UNLIKELY( manifest->blockhashes_len==0UL ) ) {
405 0 : FD_LOG_WARNING(( "%s manifest for epoch %lu and slot %lu has no blockhashes",
406 0 : ctx->full?"full":"incr", ctx->epoch, manifest->slot ));
407 0 : return -1;
408 0 : }
409 :
410 0 : if( FD_UNLIKELY( !manifest->has_accounts_lthash ) ) {
411 0 : FD_LOG_WARNING(( "%s manifest for epoch %lu and slot %lu is missing accounts lthash",
412 0 : ctx->full?"full":"incr", ctx->epoch, manifest->slot ));
413 0 : return -1;
414 0 : }
415 :
416 : /* find the last blockhash */
417 0 : ulong max_hash_idx = 0UL;
418 0 : ulong last_bh_idx = 0UL;
419 0 : for( ulong i=0UL; i<manifest->blockhashes_len; i++ ) {
420 0 : if( FD_LIKELY( manifest->blockhashes[ i ].hash_index > max_hash_idx ) ) {
421 0 : max_hash_idx = manifest->blockhashes[ i ].hash_index;
422 0 : last_bh_idx = i;
423 0 : }
424 0 : }
425 :
426 : /* fd_lthash_value_t is aligned to 64B but the accounts_lthash in the
427 : manifest may not be because its simply a uchar array. Copy is
428 : needed to avoid undefined behavior. */
429 0 : fd_lthash_value_t accounts_lthash[ 1UL ];
430 0 : fd_memcpy( accounts_lthash, manifest->accounts_lthash, sizeof(fd_lthash_value_t) );
431 :
432 0 : fd_hash_t const * parent_bank_hash = (fd_hash_t const *)fd_type_pun_const( manifest->parent_bank_hash );
433 0 : fd_hash_t const * last_blockhash = (fd_hash_t const *)fd_type_pun_const( manifest->blockhashes[ last_bh_idx ].hash );
434 0 : fd_hash_t computed_bank_hash[ 1UL ];
435 0 : fd_hashes_hash_bank( accounts_lthash, parent_bank_hash, last_blockhash, manifest->signature_count, computed_bank_hash );
436 0 : fd_hashes_apply_hard_forks(
437 0 : computed_bank_hash,
438 0 : manifest->slot,
439 0 : manifest->parent_slot,
440 0 : manifest->hard_forks,
441 0 : manifest->hard_fork_cnt );
442 :
443 0 : if( FD_UNLIKELY( memcmp( computed_bank_hash, manifest->bank_hash, FD_HASH_FOOTPRINT ) ) ) {
444 0 : FD_BASE58_ENCODE_32_BYTES( computed_bank_hash->hash, computed_bank_hash_enc );
445 0 : FD_BASE58_ENCODE_32_BYTES( manifest->bank_hash, manifest_bank_hash_enc );
446 0 : FD_LOG_WARNING(( "%s manifest for epoch %lu and slot %lu bank hash verification failed: computed %s does not match manifest %s",
447 0 : ctx->full?"full":"incr", ctx->epoch, manifest->slot,
448 0 : computed_bank_hash_enc, manifest_bank_hash_enc ));
449 0 : return -1;
450 0 : }
451 :
452 0 : return 0;
453 0 : }
454 :
455 : static void
456 : transition_malformed( fd_snapin_tile_t * ctx,
457 0 : fd_stem_context_t * stem ) {
458 0 : if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_ERROR ) ) return;
459 0 : ctx->state = FD_SNAPSHOT_STATE_ERROR;
460 0 : fd_stem_publish( stem, ctx->ct_out.idx, FD_SNAPSHOT_MSG_CTRL_ERROR, 0UL, 0UL, 0UL, 0UL, 0UL );
461 0 : }
462 :
463 : static int
464 : populate_txncache( fd_snapin_tile_t * ctx,
465 : fd_snapshot_manifest_blockhash_t const blockhashes[ static FD_BLOCKHASHES_MAX ],
466 0 : ulong blockhashes_len ) {
467 : /* Our txncache internally contains the fork structure for the chain,
468 : which we need to recreate here. Because snapshots are only served
469 : for rooted slots, there is actually no forking, and the bank forks
470 : are just a single bank, the root, like
471 :
472 : _root
473 :
474 : But the txncache also must contain the 150 more recent banks prior
475 : to the root (151 rooted banks total), looking like,
476 :
477 :
478 : _root_150 -> _root_149 -> ... -> _root_2 -> _root_1 -> _root
479 :
480 : Our txncache is "slot agnostic" meaning there is no concept of a
481 : slot number in it. It just has a fork tree structure. So long as
482 : the fork tree is isomorphic to the actual bank forks, and each bank
483 : has the correct blockhash, it works.
484 :
485 : So the challenge is simply to create this chain of 151 forks in the
486 : txncache, with correct blockhashes, and then insert all the
487 : transactions into it.
488 :
489 : Constructing the chain of blockhashes is easy. It is just the
490 : BLOCKHASH_QUEUE array in the manifest. This array is unfortunately
491 : not sorted and appears in random order, but it has a hash_index
492 : field which is a gapless index, starting at some arbitrary offset,
493 : so we can back out the 151 blockhashes we need from this, by first
494 : finding the max hash_index as _max and then collecting hash entries
495 : via,
496 :
497 : _root_150 -> _root_149 -> ... -> _root_2 -> _root_1 -> _root
498 : _max-150 -> _max-149 -> ... -> _max-2 -> _max-1 -> _max
499 :
500 : Now the remaining problem is inserting transactions into this
501 : chain. Remember each transaction needs to be inserted with:
502 :
503 : (a) The fork ID (position of the bank in the chain) it was executed in.
504 : (b) The blockhash of the bank it referenced.
505 :
506 : (b) is trivial to retrieve, as it's in the actual slot_deltas entry
507 : in the manifest served by Agave. But (a) is mildly annoying. Agave
508 : serves slot_deltas based on slot, so we need an additional mapping
509 : from slot to position in our banks chain. It turns out we have to
510 : go to yet another structure in the manifest to retrieve this, the
511 : ancestors array. This is just an array of slot values, so we need
512 : to sort it, and line it up against our banks chain like so,
513 :
514 : _root_150 -> _root_149 -> ... -> _root_2 -> _root_1 -> _root
515 : _max-150 -> _max-149 -> ... -> _max-2 -> _max-1 -> _max
516 : _slots_150 -> _slots_149 -> ... -> _slots_2 -> _slots_1 -> _slots
517 :
518 : From there we are done.
519 :
520 : Well almost ... if you were paying attention you might have noticed
521 : this is a lot of work and we are lazy. Why don't we just ignore the
522 : slot mapping and assume everything executed at the root slot
523 : exactly? The only invariant we should maintain from a memory
524 : perspective is that at most, across all active banks,
525 : FD_MAX_TXN_PER_SLOT transactions are stored per slot, but we
526 : have preserved that. It is not true "per slot" technically, but
527 : it's true across all slots, and the memory is aggregated. It will
528 : also always be true, even as slots are garbage collected, because
529 : entries are collected by reference blockhash, not executed slot.
530 :
531 : ... actually we can't do this. There's more broken things here.
532 : The Agave status decided to only store 20 bytes for 32 byte
533 : transaction hashes to save on memory. That's OK, but they didn't
534 : just take the first 20 bytes. They instead, for each blockhash,
535 : take a random offset between 0 and 12, and store bytes
536 : [ offset, offset+20 ) of the transaction hash. We need to know this
537 : offset to be able to query the txncache later, so we need to
538 : retrieve it from the slot_deltas entry in the manifest, and key it
539 : into our txncache. Unfortunately this offset is stored per slot in
540 : the slot_deltas entry. So we need to first go and retrieve the
541 : ancestors array, sort it, and line it up against our banks chain as
542 : described above, and then go through slot deltas, to retrieve the
543 : offset for each slot, and stick it into the appropriate bank in
544 : our chain. */
545 :
546 0 : if( FD_UNLIKELY( blockhashes_len>FD_BLOCKHASHES_MAX ) ) {
547 0 : FD_LOG_WARNING(( "corrupt snapshot: blockhash queue length %lu exceeds maximum %lu", blockhashes_len, FD_BLOCKHASHES_MAX ));
548 0 : return 1;
549 0 : }
550 0 : if( FD_UNLIKELY( !blockhashes_len ) ) {
551 0 : FD_LOG_WARNING(( "corrupt snapshot: blockhash queue is empty" ));
552 0 : return 1;
553 0 : }
554 :
555 0 : ulong seq_min = ULONG_MAX;
556 0 : for( ulong i=0UL; i<blockhashes_len; i++ ) seq_min = fd_ulong_min( seq_min, blockhashes[ i ].hash_index );
557 :
558 0 : ulong seq_max;
559 0 : if( FD_UNLIKELY( __builtin_uaddl_overflow( seq_min, blockhashes_len, &seq_max ) ) ) {
560 0 : FD_LOG_WARNING(( "corrupt snapshot: blockhash queue sequence number wraparound (seq_min=%lu age_cnt=%lu)", seq_min, blockhashes_len ));
561 0 : return 1;
562 0 : }
563 :
564 : /* First let's construct the chain array as described above. But
565 : index 0 will be the root, index 1 the root's parent, etc. */
566 :
567 0 : struct {
568 0 : int exists;
569 0 : uchar blockhash[ 32UL ];
570 0 : fd_txncache_fork_id_t fork_id;
571 0 : ulong txnhash_offset;
572 0 : } banks[ FD_BLOCKHASHES_MAX ] = {0};
573 :
574 0 : for( ulong i=0UL; i<blockhashes_len; i++ ) {
575 0 : fd_snapshot_manifest_blockhash_t const * elem = &blockhashes[ i ];
576 0 : ulong idx;
577 0 : if( FD_UNLIKELY( __builtin_usubl_overflow( elem->hash_index, seq_min, &idx ) ) ) {
578 0 : FD_LOG_WARNING(( "corrupt snapshot: gap in blockhash queue (seq=[%lu,%lu) idx=%lu)", seq_min, seq_max, blockhashes[ i ].hash_index ));
579 0 : return 1;
580 0 : }
581 :
582 0 : if( FD_UNLIKELY( idx>=blockhashes_len ) ) {
583 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 ));
584 0 : return 1;
585 0 : }
586 :
587 0 : if( FD_UNLIKELY( banks[ blockhashes_len-1UL-idx ].exists ) ) {
588 0 : FD_LOG_WARNING(( "corrupt snapshot: duplicate blockhash hash_index %lu", elem->hash_index ));
589 0 : return 1;
590 0 : }
591 :
592 0 : banks[ blockhashes_len-1UL-idx ].fork_id.val = USHORT_MAX;
593 0 : banks[ blockhashes_len-1UL-idx ].txnhash_offset = ULONG_MAX;
594 0 : memcpy( banks[ blockhashes_len-1UL-idx ].blockhash, elem->hash, 32UL );
595 0 : banks[ blockhashes_len-1UL-idx ].exists = 1;
596 0 : }
597 :
598 0 : ulong chain_len = fd_ulong_min( blockhashes_len, 151UL );
599 :
600 : /* Now we need a hashset of just the 151 most recent blockhashes,
601 : anything else is a nonce transaction which we do not insert, or an
602 : already expired transaction which can also be discarded. */
603 :
604 0 : uchar __attribute__((aligned(alignof(blockhash_map_t)))) _map[ blockhash_map_footprint( 1024UL ) ];
605 0 : blockhash_map_t * blockhash_map = blockhash_map_join( blockhash_map_new( _map, 1024UL, ctx->seed ) );
606 0 : if( FD_UNLIKELY( !blockhash_map ) ) FD_LOG_ERR(( "failed to create blockhash map" ));
607 :
608 0 : fd_blockhash_entry_t blockhash_pool[ 151UL ];
609 0 : for( ulong i=0UL; i<chain_len; i++ ) {
610 0 : fd_memcpy( blockhash_pool[ i ].blockhash.uc, banks[ i ].blockhash, 32UL );
611 :
612 0 : if( FD_UNLIKELY( blockhash_map_ele_query_const( blockhash_map, &blockhash_pool[ i ].blockhash, NULL, blockhash_pool ) ) ) {
613 0 : FD_BASE58_ENCODE_32_BYTES( banks[ i ].blockhash, blockhash_b58 );
614 0 : FD_LOG_WARNING(( "corrupt snapshot: duplicate blockhash %s in 151 most recent blockhashes", blockhash_b58 ));
615 0 : return 1;
616 0 : }
617 :
618 0 : blockhash_map_ele_insert( blockhash_map, &blockhash_pool[ i ], blockhash_pool );
619 0 : }
620 :
621 : /* Now load the blockhash offsets for these blockhashes ... */
622 0 : if( FD_UNLIKELY( !ctx->blockhash_offsets_len ) ) {
623 0 : FD_LOG_WARNING(( "corrupt snapshot: no blockhash offsets found (nothing is rooted)" ));
624 0 : return 1;
625 0 : }
626 0 : for( ulong i=0UL; i<ctx->blockhash_offsets_len; i++ ) {
627 0 : fd_hash_t key;
628 0 : fd_memcpy( key.uc, ctx->blockhash_offsets[ i ].blockhash, 32UL );
629 0 : fd_blockhash_entry_t * entry = blockhash_map_ele_query( blockhash_map, &key, NULL, blockhash_pool );
630 0 : if( FD_UNLIKELY( !entry ) ) continue; /* Not in the most recent 151 blockhashes */
631 :
632 0 : ulong chain_idx = (ulong)(entry - blockhash_pool);
633 :
634 0 : if( FD_UNLIKELY( banks[ chain_idx ].txnhash_offset!=ULONG_MAX && banks[ chain_idx ].txnhash_offset!=ctx->blockhash_offsets[ i ].txnhash_offset ) ) {
635 0 : FD_BASE58_ENCODE_32_BYTES( entry->blockhash.uc, blockhash_b58 );
636 0 : FD_LOG_WARNING(( "corrupt snapshot: conflicting txnhash offsets for blockhash %s", blockhash_b58 ));
637 0 : return 1;
638 0 : }
639 :
640 0 : banks[ chain_idx ].txnhash_offset = ctx->blockhash_offsets[ i ].txnhash_offset;
641 0 : }
642 :
643 : /* Construct the linear fork chain in the txncache. */
644 :
645 0 : fd_txncache_fork_id_t parent = { .val = USHORT_MAX };
646 0 : for( ulong i=0UL; i<chain_len; i++ ) banks[ chain_len-1UL-i ].fork_id = parent = fd_txncache_attach_child( ctx->txncache, parent );
647 0 : for( ulong i=0UL; i<chain_len; i++ ) fd_txncache_attach_blockhash( ctx->txncache, banks[ i ].fork_id, banks[ i ].blockhash );
648 :
649 : /* Now insert all transactions as if they executed at the current
650 : root, per above. */
651 :
652 0 : ulong insert_cnt = 0UL;
653 0 : for( ulong i=0UL; i<ctx->txncache_entries_len; i++ ) {
654 0 : fd_sstxncache_entry_t const * entry = &ctx->txncache_entries[ i ];
655 0 : fd_hash_t key;
656 0 : fd_memcpy( key.uc, entry->blockhash, 32UL );
657 0 : if( FD_UNLIKELY( !blockhash_map_ele_query_const( blockhash_map, &key, NULL, blockhash_pool ) ) ) continue;
658 :
659 0 : insert_cnt++;
660 0 : fd_txncache_insert( ctx->txncache, banks[ 0UL ].fork_id, entry->blockhash, entry->txnhash );
661 0 : }
662 :
663 0 : FD_LOG_INFO(( "inserted %lu/%lu transactions into the txncache", insert_cnt, ctx->txncache_entries_len ));
664 :
665 : /* Then finalize all the banks (freezing them) and setting the txnhash
666 : offset so future queries use the correct offset. If the offset is
667 : ULONG_MAX this is valid, it means the blockhash had no transactions
668 : in it, so there's nothing in the status cache under that blockhash.
669 :
670 : Just set the offset to 0 in this case, it doesn't matter, but
671 : should be valid between 0 and 12 inclusive. */
672 0 : for( ulong i=0UL; i<chain_len; i++ ) {
673 0 : ulong txnhash_offset = banks[ chain_len-1UL-i ].txnhash_offset==ULONG_MAX ? 0UL : banks[ chain_len-1UL-i ].txnhash_offset;
674 0 : fd_txncache_finalize_fork( ctx->txncache, banks[ chain_len-1UL-i ].fork_id, txnhash_offset, banks[ chain_len-1UL-i ].blockhash );
675 0 : }
676 :
677 0 : for( ulong i=1UL; i<chain_len; i++ ) fd_txncache_advance_root( ctx->txncache, banks[ chain_len-1UL-i ].fork_id );
678 :
679 0 : ctx->txncache_root_fork_id = parent;
680 :
681 0 : return 0;
682 0 : }
683 :
684 : static void
685 : process_manifest( fd_snapin_tile_t * ctx,
686 0 : fd_stem_context_t * stem ) {
687 0 : fd_snapshot_manifest_t * manifest = fd_chunk_to_laddr( ctx->manifest_out.mem, ctx->manifest_out.chunk );
688 :
689 0 : if( FD_UNLIKELY( ctx->advertised_slot!=manifest->slot ) ) {
690 : /* SnapshotError::MismatchedSlot
691 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L472 */
692 0 : FD_LOG_WARNING(( "snapshot manifest bank slot %lu does not match advertised slot %lu from snapshot peer",
693 0 : manifest->slot, ctx->advertised_slot ));
694 0 : transition_malformed( ctx, stem );
695 0 : return;
696 0 : }
697 :
698 0 : if( FD_UNLIKELY( !manifest->has_accounts_lthash ) ) {
699 : /* The manifest must contain accounts lthash, irrespective of
700 : whether lthash verification is disabled or not.
701 : https://github.com/anza-xyz/agave/blob/v3.1.9/runtime/src/serde_snapshot.rs#L482 */
702 0 : FD_LOG_WARNING(( "snapshot manifest missing accounts lthash" ));
703 0 : transition_malformed( ctx, stem );
704 0 : return;
705 0 : }
706 :
707 0 : uchar const * sum = manifest->accounts_lthash;
708 0 : uchar hash32[32]; fd_blake3_hash( sum, FD_LTHASH_LEN_BYTES, hash32 );
709 0 : FD_BASE58_ENCODE_32_BYTES( sum, sum_enc );
710 0 : FD_BASE58_ENCODE_32_BYTES( hash32, hash32_enc );
711 0 : FD_LOG_INFO(( "snapshot manifest slot=%lu indicates lthash[..32]=%s blake3(lthash)=%s",
712 0 : manifest->slot, sum_enc, hash32_enc ));
713 :
714 0 : if( FD_UNLIKELY( memcmp( ctx->advertised_hash, hash32, FD_HASH_FOOTPRINT ) ) ) {
715 : /* SnapshotError::MismatchedHash
716 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L479 */
717 0 : FD_BASE58_ENCODE_32_BYTES( ctx->advertised_hash, advertised_hash_enc );
718 0 : FD_LOG_WARNING(( "snapshot manifest accounts lthash %s does not match advertised hash from snapshot peer %s",
719 0 : hash32_enc, advertised_hash_enc ));
720 0 : transition_malformed( ctx, stem );
721 0 : return;
722 0 : }
723 :
724 0 : ctx->bank_slot = manifest->slot;
725 0 : ctx->manifest_capitalization = manifest->capitalization;
726 0 : if( FD_UNLIKELY( ctx->manifest_capitalization>LONG_MAX ) ) {
727 : /* Calculations downstream require capitalization to be treated
728 : as long (to handle addition and subtraction). */
729 0 : FD_LOG_WARNING(( "snapshot manifest capitalization %lu exceeds LONG_MAX", ctx->manifest_capitalization ));
730 0 : transition_malformed( ctx, stem );
731 0 : return;
732 0 : }
733 :
734 0 : if( FD_UNLIKELY( fd_ssload_manifest_validate( manifest, FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS, FD_RUNTIME_MAX_STAKE_ACCOUNTS ) ) ) {
735 0 : FD_LOG_WARNING(( "snapshot manifest validation failed" ));
736 0 : transition_malformed( ctx, stem );
737 0 : return;
738 0 : }
739 :
740 0 : fd_epoch_schedule_t epoch_schedule = (fd_epoch_schedule_t){
741 0 : .slots_per_epoch = manifest->epoch_schedule_params.slots_per_epoch,
742 0 : .leader_schedule_slot_offset = manifest->epoch_schedule_params.leader_schedule_slot_offset,
743 0 : .warmup = manifest->epoch_schedule_params.warmup,
744 0 : .first_normal_epoch = manifest->epoch_schedule_params.first_normal_epoch,
745 0 : .first_normal_slot = manifest->epoch_schedule_params.first_normal_slot,
746 0 : };
747 0 : ctx->epoch = fd_slot_to_epoch( &epoch_schedule, manifest->slot, NULL );
748 0 : ctx->epoch_schedule = epoch_schedule;
749 :
750 0 : if( FD_UNLIKELY( verify_bank_hash( ctx, manifest ) ) ) {
751 : /* https://github.com/anza-xyz/agave/blob/v3.1.9/runtime/src/bank.rs#L4682 */
752 0 : transition_malformed( ctx, stem );
753 0 : return;
754 0 : }
755 :
756 0 : if( FD_UNLIKELY( verify_slot_deltas_with_bank_slot( ctx, manifest->slot ) ) ) {
757 0 : FD_LOG_WARNING(( "slot deltas verification failed" ));
758 0 : transition_malformed( ctx, stem );
759 0 : return;
760 0 : }
761 :
762 0 : if( FD_UNLIKELY( verify_epoch_stakes( manifest ) ) ) {
763 0 : FD_LOG_WARNING(( "epoch stakes verification failed" ));
764 0 : transition_malformed( ctx, stem );
765 0 : return;
766 0 : }
767 :
768 0 : if( FD_UNLIKELY( populate_txncache( ctx, manifest->blockhashes, manifest->blockhashes_len ) ) ) {
769 0 : FD_LOG_WARNING(( "populating txncache failed" ));
770 0 : transition_malformed( ctx, stem );
771 0 : return;
772 0 : }
773 :
774 0 : if( ctx->full ) {
775 0 : ctx->full_genesis_creation_time_seconds = manifest->creation_time_seconds;
776 0 : } else {
777 0 : if( FD_UNLIKELY( manifest->creation_time_seconds!=ctx->full_genesis_creation_time_seconds ) ) {
778 0 : FD_LOG_WARNING(( "snapshot manifest genesis creation time seconds %lu does not match full snapshot genesis creation time seconds %lu",
779 0 : manifest->creation_time_seconds, ctx->full_genesis_creation_time_seconds ));
780 0 : transition_malformed( ctx, stem );
781 0 : return;
782 0 : }
783 0 : }
784 :
785 0 : manifest->accdb_fork_id = fd_ushort_if( ctx->full, ctx->accdb_root_fork_id.val, ctx->accdb_incr_fork_id.val );
786 0 : manifest->txncache_fork_id = ctx->txncache_root_fork_id.val;
787 :
788 0 : ulong sig = ctx->full ? fd_ssmsg_sig( FD_SSMSG_MANIFEST_FULL ) :
789 0 : fd_ssmsg_sig( FD_SSMSG_MANIFEST_INCREMENTAL );
790 0 : fd_stem_publish( stem, ctx->manifest_out.idx, sig, ctx->manifest_out.chunk, sizeof(fd_snapshot_manifest_t), 0UL, 0UL, 0UL );
791 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 );
792 0 : }
793 :
794 : static void
795 : snoop_stake_delegation( fd_snapin_tile_t * ctx,
796 : fd_pubkey_t const * stake_account,
797 : ulong lamports,
798 : ulong data_len,
799 : uchar const * data,
800 6 : ulong data_sz ) {
801 6 : fd_stake_state_t const * stake_state = fd_stake_state_view( data, data_sz );
802 6 : if( FD_UNLIKELY( !stake_state || stake_state->stake_type!=FD_STAKE_STATE_STAKE ) ) return;
803 :
804 6 : fd_delegation_t const * delegation = &stake_state->stake.stake.delegation;
805 6 : if( FD_UNLIKELY( ( delegation->activation_epoch!=ULONG_MAX &&
806 6 : delegation->activation_epoch>=(ulong)USHORT_MAX ) ||
807 6 : ( delegation->deactivation_epoch!=ULONG_MAX &&
808 6 : delegation->deactivation_epoch>=(ulong)USHORT_MAX ) ) ) return;
809 :
810 6 : fd_stake_delegations_root_update(
811 6 : fd_banks_stake_delegations_root_query( ctx->banks ),
812 6 : stake_account,
813 6 : &delegation->voter_pubkey,
814 6 : delegation->stake,
815 6 : delegation->activation_epoch,
816 6 : delegation->deactivation_epoch,
817 6 : stake_state->stake.stake.credits_observed,
818 6 : lamports,
819 6 : (uint)data_len,
820 : /* fd_stake_delegations_refresh recomputes this after load. */
821 6 : FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_025 );
822 6 : }
823 :
824 : static int
825 : process_account_batch( fd_snapin_tile_t * ctx,
826 3 : fd_ssparse_advance_result_t * result ) {
827 3 : uchar const * const * entries = result->account_batch.batch;
828 3 : ulong cnt = result->account_batch.batch_cnt;
829 3 : ulong batch_slot = result->account_batch.slot;
830 :
831 3 : uchar const * pubkeys [ FD_SSPARSE_ACC_BATCH_MAX ] = {0};
832 3 : ulong slots [ FD_SSPARSE_ACC_BATCH_MAX ] = {0};
833 3 : ulong lamports [ FD_SSPARSE_ACC_BATCH_MAX ] = {0};
834 3 : ulong data_lens [ FD_SSPARSE_ACC_BATCH_MAX ] = {0};
835 3 : int executables[ FD_SSPARSE_ACC_BATCH_MAX ] = {0};
836 :
837 6 : for( ulong i=0UL; i<cnt; i++ ) {
838 3 : uchar const * e = entries[ i ];
839 3 : pubkeys[ i ] = e + 16UL;
840 3 : slots[ i ] = batch_slot;
841 3 : lamports[ i ] = fd_ulong_load_8_fast( e+48UL );
842 3 : data_lens[ i ] = fd_ulong_load_8_fast( e+8UL );
843 3 : executables[ i ] = e[ 96UL ];
844 :
845 : /* Snoop SlotHistory sysvar. Account body in the batch path is
846 : contiguous starting at e+136. */
847 3 : if( FD_UNLIKELY( !memcmp( pubkeys[ i ], fd_sysvar_slot_history_id.uc, 32UL ) ) &&
848 3 : ( !ctx->slot_history.captured || batch_slot>=ctx->slot_history.slot ) &&
849 3 : data_lens[ i ]<=FD_SYSVAR_SLOT_HISTORY_BINCODE_SZ ) {
850 0 : ctx->slot_history.slot = batch_slot;
851 0 : ctx->slot_history.lamports = lamports[ i ];
852 0 : ctx->slot_history.data_len = data_lens[ i ];
853 0 : ctx->slot_history.executable = executables[ i ];
854 0 : memcpy( ctx->slot_history.owner, e+64UL, 32UL );
855 0 : memcpy( ctx->slot_history.buf, e+136UL, data_lens[ i ] );
856 0 : ctx->slot_history.captured = 1;
857 0 : }
858 :
859 3 : fd_feature_snoop_account( ctx->feature_snoop, (fd_pubkey_t const *)pubkeys[ i ], lamports[ i ], e+64UL, e+136UL, data_lens[ i ] );
860 :
861 3 : if( FD_UNLIKELY( lamports[ i ] &&
862 3 : !memcmp( e+64UL, &fd_solana_stake_program_id, sizeof(fd_pubkey_t) ) ) ) {
863 3 : snoop_stake_delegation( ctx, (fd_pubkey_t const *)pubkeys[ i ], lamports[ i ],
864 3 : data_lens[ i ], e+136UL, data_lens[ i ] );
865 3 : }
866 3 : }
867 :
868 3 : ulong accounts_ignored, accounts_replaced, accounts_loaded, replaced_lamports, ignored_lamports;
869 3 : fd_accdb_fork_id_t fork_id = ctx->full ? (fd_accdb_fork_id_t){ .val = USHORT_MAX } : ctx->accdb_incr_fork_id;
870 3 : if( FD_UNLIKELY( 0!=fd_accdb_snapshot_write_batch( ctx->accdb, fork_id, cnt, pubkeys, slots, lamports, data_lens,
871 3 : executables, &accounts_ignored, &accounts_replaced, &accounts_loaded,
872 3 : &replaced_lamports, &ignored_lamports ) ) ) {
873 0 : return -1;
874 0 : }
875 3 : ctx->metrics.accounts_ignored += accounts_ignored;
876 3 : ctx->metrics.accounts_replaced += accounts_replaced;
877 3 : ctx->metrics.accounts_loaded += accounts_loaded;
878 3 : ctx->metrics.total_accounts_processed += cnt;
879 3 : ctx->metrics.total_account_batches_processed++;
880 : /* Sum lamports of every accepted entry into capitalization, and
881 : accumulate the lamports of overwritten entries into
882 : dup_capitalization so the final value can be reconciled with the
883 : manifest. Ignored entries (older slot than what's already in the
884 : accdb) contribute neither to the live database nor to capitalization,
885 : so subtract their lamports back out. */
886 6 : for( ulong i=0UL; i<cnt; i++ ) ctx->capitalization = fd_ulong_sat_add( ctx->capitalization, lamports[ i ] );
887 3 : ctx->capitalization = fd_ulong_sat_sub( ctx->capitalization, ignored_lamports );
888 3 : ctx->dup_capitalization = fd_ulong_sat_add( ctx->dup_capitalization, replaced_lamports );
889 :
890 3 : return 0;
891 3 : }
892 :
893 : static int
894 : process_account_header( fd_snapin_tile_t * ctx,
895 3 : fd_ssparse_advance_result_t * result ) {
896 3 : ctx->metrics.total_account_batches_processed++;
897 3 : ctx->metrics.total_accounts_processed++;
898 3 : ulong replaced_lamports = 0UL;
899 3 : fd_accdb_fork_id_t fork_id = ctx->full ? (fd_accdb_fork_id_t){ .val = USHORT_MAX } : ctx->accdb_incr_fork_id;
900 3 : int account = fd_accdb_snapshot_write_one( ctx->accdb,
901 3 : fork_id,
902 3 : result->account_header.pubkey,
903 3 : result->account_header.slot,
904 3 : result->account_header.lamports,
905 3 : result->account_header.data_len,
906 3 : result->account_header.executable,
907 3 : &replaced_lamports );
908 3 : if( FD_UNLIKELY( -1==account ) ) {
909 0 : ctx->metrics.accounts_ignored++;
910 3 : } else {
911 3 : if( FD_UNLIKELY( 2==account ) ) {
912 0 : ctx->metrics.accounts_replaced++;
913 0 : ctx->dup_capitalization = fd_ulong_sat_add( ctx->dup_capitalization, replaced_lamports );
914 3 : } else {
915 3 : ctx->metrics.accounts_loaded++;
916 3 : }
917 3 : ctx->capitalization = fd_ulong_sat_add( ctx->capitalization, result->account_header.lamports );
918 3 : }
919 :
920 : /* Snoop SlotHistory sysvar. Streaming path: arm the capture window
921 : here; process_account_data appends bytes while armed. */
922 3 : ctx->slot_history.capturing = 0;
923 3 : if( FD_UNLIKELY( !memcmp( result->account_header.pubkey, fd_sysvar_slot_history_id.uc, 32UL ) ) &&
924 3 : ( !ctx->slot_history.captured || result->account_header.slot>=ctx->slot_history.slot ) &&
925 3 : result->account_header.data_len<=FD_SYSVAR_SLOT_HISTORY_BINCODE_SZ ) {
926 0 : ctx->slot_history.slot = result->account_header.slot;
927 0 : ctx->slot_history.lamports = result->account_header.lamports;
928 0 : ctx->slot_history.data_len = result->account_header.data_len;
929 0 : ctx->slot_history.executable = result->account_header.executable;
930 0 : memcpy( ctx->slot_history.owner, result->account_header.owner, 32UL );
931 0 : ctx->slot_history.write_pos = 0UL;
932 0 : ctx->slot_history.capturing = 1;
933 0 : }
934 3 : ctx->feature_reasm.capturing = 0;
935 3 : if( FD_UNLIKELY( !memcmp( result->account_header.owner, fd_solana_feature_program_id.uc, 32UL ) &&
936 3 : result->account_header.lamports ) ) {
937 0 : memcpy( ctx->feature_reasm.pubkey.uc, result->account_header.pubkey, 32UL );
938 0 : memcpy( ctx->feature_reasm.owner, result->account_header.owner, 32UL );
939 0 : ctx->feature_reasm.lamports = result->account_header.lamports;
940 0 : ctx->feature_reasm.need = fd_ulong_min( result->account_header.data_len, sizeof(ctx->feature_reasm.buf) );
941 0 : ctx->feature_reasm.write_pos = 0UL;
942 0 : ctx->feature_reasm.capturing = 1;
943 0 : if( FD_UNLIKELY( !ctx->feature_reasm.need ) ) {
944 0 : fd_feature_snoop_account( ctx->feature_snoop, &ctx->feature_reasm.pubkey,
945 0 : ctx->feature_reasm.lamports, ctx->feature_reasm.owner,
946 0 : ctx->feature_reasm.buf, 0UL );
947 0 : ctx->feature_reasm.capturing = 0;
948 0 : }
949 0 : }
950 :
951 3 : ctx->stake_reasm.capturing = 0;
952 3 : if( FD_UNLIKELY( account!=-1 &&
953 3 : result->account_header.lamports &&
954 3 : result->account_header.data_len>=sizeof(fd_stake_state_t) &&
955 3 : !memcmp( result->account_header.owner, &fd_solana_stake_program_id, sizeof(fd_pubkey_t) ) ) ) {
956 3 : memcpy( ctx->stake_reasm.pubkey.uc, result->account_header.pubkey, sizeof(fd_pubkey_t) );
957 3 : ctx->stake_reasm.lamports = result->account_header.lamports;
958 3 : ctx->stake_reasm.data_len = result->account_header.data_len;
959 3 : ctx->stake_reasm.write_pos = 0UL;
960 3 : ctx->stake_reasm.capturing = 1;
961 3 : }
962 :
963 3 : return 0;
964 3 : }
965 :
966 : static void
967 : process_account_data( fd_snapin_tile_t * ctx,
968 6 : fd_ssparse_advance_result_t * result ) {
969 6 : if( FD_UNLIKELY( ctx->slot_history.capturing ) ) {
970 0 : ulong remaining = ctx->slot_history.data_len - ctx->slot_history.write_pos;
971 0 : ulong copy_sz = fd_ulong_min( result->account_data.data_sz, remaining );
972 0 : memcpy( ctx->slot_history.buf + ctx->slot_history.write_pos, result->account_data.data, copy_sz );
973 0 : ctx->slot_history.write_pos += copy_sz;
974 0 : if( ctx->slot_history.write_pos==ctx->slot_history.data_len ) {
975 0 : ctx->slot_history.captured = 1;
976 0 : ctx->slot_history.capturing = 0;
977 0 : }
978 0 : }
979 :
980 6 : if( FD_UNLIKELY( ctx->feature_reasm.capturing ) ) {
981 0 : ulong remaining = ctx->feature_reasm.need - ctx->feature_reasm.write_pos;
982 0 : ulong copy_sz = fd_ulong_min( result->account_data.data_sz, remaining );
983 0 : memcpy( ctx->feature_reasm.buf + ctx->feature_reasm.write_pos, result->account_data.data, copy_sz );
984 0 : ctx->feature_reasm.write_pos += copy_sz;
985 0 : if( ctx->feature_reasm.write_pos==ctx->feature_reasm.need ) {
986 0 : fd_feature_snoop_account( ctx->feature_snoop, &ctx->feature_reasm.pubkey,
987 0 : ctx->feature_reasm.lamports, ctx->feature_reasm.owner,
988 0 : ctx->feature_reasm.buf, ctx->feature_reasm.need );
989 0 : ctx->feature_reasm.capturing = 0;
990 0 : }
991 0 : }
992 :
993 6 : if( FD_UNLIKELY( ctx->stake_reasm.capturing ) ) {
994 6 : ulong remaining = sizeof(ctx->stake_reasm.buf) - ctx->stake_reasm.write_pos;
995 6 : ulong copy_sz = fd_ulong_min( result->account_data.data_sz, remaining );
996 6 : memcpy( ctx->stake_reasm.buf + ctx->stake_reasm.write_pos, result->account_data.data, copy_sz );
997 6 : ctx->stake_reasm.write_pos += copy_sz;
998 6 : if( ctx->stake_reasm.write_pos==sizeof(ctx->stake_reasm.buf) ) {
999 3 : snoop_stake_delegation( ctx, &ctx->stake_reasm.pubkey, ctx->stake_reasm.lamports,
1000 3 : ctx->stake_reasm.data_len, ctx->stake_reasm.buf,
1001 3 : sizeof(ctx->stake_reasm.buf) );
1002 3 : ctx->stake_reasm.capturing = 0;
1003 3 : }
1004 6 : }
1005 6 : }
1006 :
1007 : static int
1008 : handle_data_frag( fd_snapin_tile_t * ctx,
1009 : ulong chunk,
1010 : ulong sz,
1011 0 : fd_stem_context_t * stem ) {
1012 0 : if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_FINISHING ) ) {
1013 0 : FD_LOG_WARNING(( "received unexpected data frag while in state %s (%lu)",
1014 0 : fd_ssctrl_state_str( (ulong)ctx->state ), (ulong)ctx->state ));
1015 0 : transition_malformed( ctx, stem );
1016 0 : return 0;
1017 0 : }
1018 0 : if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_ERROR ) ) {
1019 : /* Ignore all data frags after observing an error in the stream until
1020 : we receive fail & init control messages to restart processing. */
1021 0 : return 0;
1022 0 : }
1023 0 : if( FD_UNLIKELY( ctx->state!=FD_SNAPSHOT_STATE_PROCESSING ) ) {
1024 0 : FD_LOG_ERR(( "received data frag during invalid state %s (%lu)",
1025 0 : fd_ssctrl_state_str( (ulong)ctx->state ), (ulong)ctx->state ));
1026 0 : }
1027 :
1028 0 : if( FD_UNLIKELY( chunk<ctx->in.chunk0 || chunk>ctx->in.wmark || sz>ctx->in.mtu ) ) FD_LOG_ERR(( "invalid data frag bounds (chunk=%lu chunk0=%lu wmark=%lu sz=%lu mtu=%lu)", chunk, ctx->in.chunk0, ctx->in.wmark, sz, ctx->in.mtu ));
1029 :
1030 0 : for(;;) {
1031 0 : if( FD_UNLIKELY( sz-ctx->in.pos==0UL ) ) break;
1032 :
1033 0 : uchar const * data = (uchar const *)fd_chunk_to_laddr_const( ctx->in.wksp, chunk ) + ctx->in.pos;
1034 :
1035 0 : int early_exit = 0;
1036 0 : fd_ssparse_advance_result_t result[1];
1037 0 : int res = fd_ssparse_advance( ctx->ssparse, data, sz-ctx->in.pos, result );
1038 0 : switch( res ) {
1039 0 : case FD_SSPARSE_ADVANCE_ERROR:
1040 0 : FD_LOG_WARNING(( "error while parsing snapshot stream" ));
1041 0 : transition_malformed( ctx, stem );
1042 0 : return 0;
1043 0 : case FD_SSPARSE_ADVANCE_AGAIN:
1044 0 : break;
1045 0 : case FD_SSPARSE_ADVANCE_MANIFEST:
1046 0 : case FD_SSPARSE_ADVANCE_MANIFEST_DONE: {
1047 0 : if( FD_UNLIKELY( ctx->flags.manifest_done ) ) {
1048 0 : FD_LOG_WARNING(( "excess data after manifest" ));
1049 0 : transition_malformed( ctx, stem );
1050 0 : return 0;
1051 0 : }
1052 0 : int parser_res = fd_ssmanifest_parser_consume( ctx->manifest_parser,
1053 0 : result->manifest.data,
1054 0 : result->manifest.data_sz );
1055 0 : if( FD_UNLIKELY( parser_res==FD_SSMANIFEST_PARSER_ADVANCE_ERROR ) ) {
1056 0 : FD_LOG_WARNING(( "error while parsing snapshot manifest" ));
1057 0 : transition_malformed( ctx, stem );
1058 0 : return 0;
1059 0 : }
1060 0 : if( res==FD_SSPARSE_ADVANCE_MANIFEST_DONE ) {
1061 0 : if( FD_UNLIKELY( fd_ssmanifest_parser_fini( ctx->manifest_parser )!=FD_SSMANIFEST_PARSER_ADVANCE_DONE ) ) {
1062 0 : FD_LOG_WARNING(( "manifest stream ended before parser was done" ));
1063 0 : transition_malformed( ctx, stem );
1064 0 : return 0;
1065 0 : }
1066 0 : ctx->flags.manifest_done = 1;
1067 0 : }
1068 0 : break;
1069 0 : }
1070 0 : case FD_SSPARSE_ADVANCE_STATUS_CACHE: {
1071 0 : fd_slot_delta_parser_advance_result_t sd_result[1];
1072 0 : ulong bytes_remaining = result->status_cache.data_sz;
1073 :
1074 0 : while( bytes_remaining ) {
1075 0 : int res = fd_slot_delta_parser_consume( ctx->slot_delta_parser,
1076 0 : result->status_cache.data,
1077 0 : bytes_remaining,
1078 0 : sd_result );
1079 0 : if( FD_UNLIKELY( res<0 ) ) {
1080 0 : FD_LOG_WARNING(( "error while parsing slot deltas in status cache" ));
1081 0 : transition_malformed( ctx, stem );
1082 0 : return 0;
1083 0 : } else if( FD_LIKELY( res==FD_SLOT_DELTA_PARSER_ADVANCE_GROUP ) ) {
1084 0 : if( FD_UNLIKELY( ctx->blockhash_offsets_len>=FD_SNAPIN_MAX_SLOT_DELTA_GROUPS ) ) {
1085 0 : FD_LOG_WARNING(( "blockhash offsets overflow, max is %lu", FD_SNAPIN_MAX_SLOT_DELTA_GROUPS ));
1086 0 : transition_malformed( ctx, stem );
1087 0 : return 0;
1088 0 : }
1089 :
1090 0 : memcpy( ctx->blockhash_offsets[ ctx->blockhash_offsets_len ].blockhash, sd_result->group.blockhash, 32UL );
1091 0 : ctx->blockhash_offsets[ ctx->blockhash_offsets_len ].txnhash_offset = sd_result->group.txnhash_offset;
1092 0 : ctx->blockhash_offsets_len++;
1093 0 : } else if( FD_LIKELY( res==FD_SLOT_DELTA_PARSER_ADVANCE_ENTRY ) ) {
1094 0 : if( FD_UNLIKELY( ctx->txncache_entries_len>=FD_SNAPIN_TXNCACHE_MAX_ENTRIES ) ) {
1095 0 : FD_LOG_WARNING(( "txncache entries overflow, max is %lu", FD_SNAPIN_TXNCACHE_MAX_ENTRIES ));
1096 0 : transition_malformed( ctx, stem );
1097 0 : return 0;
1098 0 : }
1099 0 : ctx->txncache_entries[ ctx->txncache_entries_len++ ] = *sd_result->entry;
1100 0 : }
1101 :
1102 0 : bytes_remaining -= sd_result->bytes_consumed;
1103 0 : result->status_cache.data += sd_result->bytes_consumed;
1104 0 : }
1105 :
1106 0 : if( FD_UNLIKELY( result->status_cache.done ) ) {
1107 0 : int fini_res = fd_slot_delta_parser_consume( ctx->slot_delta_parser, result->status_cache.data, 0UL, sd_result );
1108 0 : if( FD_UNLIKELY( fini_res<0 ) ) {
1109 0 : FD_LOG_WARNING(( "error while finalizing slot deltas in status cache" ));
1110 0 : transition_malformed( ctx, stem );
1111 0 : return 0;
1112 0 : }
1113 0 : ctx->flags.status_cache_done = fini_res==FD_SLOT_DELTA_PARSER_ADVANCE_DONE;
1114 0 : }
1115 0 : break;
1116 0 : }
1117 0 : case FD_SSPARSE_ADVANCE_ACCOUNT_HEADER:
1118 0 : early_exit = process_account_header( ctx, result );
1119 0 : if( FD_UNLIKELY( early_exit<0 ) ) {
1120 0 : transition_malformed( ctx, stem );
1121 0 : return 0;
1122 0 : }
1123 :
1124 0 : if( FD_UNLIKELY( ctx->gui_out.idx!=ULONG_MAX
1125 0 : && !memcmp( result->account_header.owner, fd_solana_config_program_id.key, sizeof(fd_hash_t) )
1126 0 : && result->account_header.data_len
1127 0 : && result->account_header.data_len<=FD_GUI_CONFIG_PARSE_MAX_VALID_ACCT_SZ ) ) {
1128 0 : ctx->gui_config_acct_sz = result->account_header.data_len;
1129 0 : ctx->gui_config_acct_off = 0UL;
1130 0 : } else {
1131 0 : ctx->gui_config_acct_sz = 0UL;
1132 0 : }
1133 0 : break;
1134 0 : case FD_SSPARSE_ADVANCE_ACCOUNT_DATA:
1135 0 : process_account_data( ctx, result );
1136 :
1137 : /* Account data may span multiple input chunks (when an account
1138 : straddles a decompressed chunk boundary), so we copy each
1139 : piece into the gui_out dcache and only publish once the full
1140 : account has been received.
1141 :
1142 : We expect ConfigKeys Vec to be length 2 (checked via the
1143 : first byte of the accumulated data). We expect the size of
1144 : ConfigProgram-owned accounts to be at most
1145 : FD_GUI_CONFIG_PARSE_MAX_VALID_ACCT_SZ, since this is the
1146 : size that the Solana CLI allocates for them. Although the
1147 : ConfigProgram itself does not enforce these invariants, the
1148 : vast majority of accounts (with a tiny number of exceptions
1149 : on devnet) are maintained with the Solana CLI. */
1150 0 : if( FD_UNLIKELY( ctx->gui_config_acct_sz ) ) {
1151 0 : uchar * acct = fd_chunk_to_laddr( ctx->gui_out.mem, ctx->gui_out.chunk );
1152 0 : fd_memcpy( acct + ctx->gui_config_acct_off, result->account_data.data, result->account_data.data_sz );
1153 0 : ctx->gui_config_acct_off += result->account_data.data_sz;
1154 :
1155 0 : if( FD_LIKELY( ctx->gui_config_acct_off>=ctx->gui_config_acct_sz ) ) {
1156 0 : ctx->gui_config_acct_sz = 0UL;
1157 0 : if( FD_LIKELY( acct[ 0 ]==2UL ) ) {
1158 0 : fd_stem_publish( stem, ctx->gui_out.idx, 0UL, ctx->gui_out.chunk, ctx->gui_config_acct_off, 0UL, 0UL, 0UL );
1159 0 : ctx->gui_out.chunk = fd_dcache_compact_next( ctx->gui_out.chunk, ctx->gui_config_acct_off, ctx->gui_out.chunk0, ctx->gui_out.wmark );
1160 0 : early_exit = 1;
1161 0 : }
1162 0 : }
1163 0 : }
1164 0 : break;
1165 0 : case FD_SSPARSE_ADVANCE_ACCOUNT_BATCH:
1166 0 : early_exit = process_account_batch( ctx, result );
1167 0 : if( FD_UNLIKELY( early_exit<0 ) ) {
1168 0 : transition_malformed( ctx, stem );
1169 0 : return 0;
1170 0 : }
1171 0 : break;
1172 0 : case FD_SSPARSE_ADVANCE_DONE:
1173 0 : ctx->state = FD_SNAPSHOT_STATE_FINISHING;
1174 0 : break;
1175 0 : default:
1176 0 : FD_LOG_ERR(( "unexpected fd_ssparse_advance result %d", res ));
1177 0 : break;
1178 0 : }
1179 :
1180 0 : if( FD_UNLIKELY( !ctx->flags.manifest_processed && ctx->flags.manifest_done && ctx->flags.status_cache_done ) ) {
1181 0 : process_manifest( ctx, stem );
1182 0 : if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_ERROR ) ) break;
1183 0 : ctx->flags.manifest_processed = 1;
1184 0 : }
1185 :
1186 0 : ctx->in.pos += result->bytes_consumed;
1187 0 : if( FD_LIKELY( ctx->full ) ) ctx->metrics.full_bytes_read += result->bytes_consumed;
1188 0 : else ctx->metrics.incremental_bytes_read += result->bytes_consumed;
1189 :
1190 0 : if( FD_UNLIKELY( early_exit ) ) break;
1191 0 : }
1192 :
1193 0 : int reprocess_frag = ctx->in.pos<sz;
1194 0 : if( FD_LIKELY( !reprocess_frag ) ) ctx->in.pos = 0UL;
1195 0 : return reprocess_frag;
1196 0 : }
1197 :
1198 : static int
1199 0 : validate_capitalization( fd_snapin_tile_t * ctx ) {
1200 0 : if( FD_UNLIKELY( ctx->capitalization!=ctx->manifest_capitalization ) ) {
1201 : /* SnapshotError::MismatchedCapitalization
1202 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.2/runtime/src/snapshot_bank_utils.rs#L217 */
1203 0 : FD_LOG_WARNING(( "%s snapshot manifest capitalization %lu does not match computed capitalization %lu",
1204 0 : ctx->full?"full":"incr", ctx->manifest_capitalization, ctx->capitalization ));
1205 0 : return -1;
1206 0 : }
1207 0 : return 0;
1208 0 : }
1209 :
1210 : static void
1211 : handle_control_frag( fd_snapin_tile_t * ctx,
1212 : fd_stem_context_t * stem,
1213 : ulong sig,
1214 : ulong chunk,
1215 0 : ulong sz ) {
1216 0 : if( ctx->state==FD_SNAPSHOT_STATE_ERROR && sig!=FD_SNAPSHOT_MSG_CTRL_FAIL ) {
1217 : /* Control messages move along the snapshot load pipeline. Since
1218 : error conditions can be triggered by any tile in the pipeline,
1219 : it is possible to be in error state and still receive otherwise
1220 : valid messages. Only a fail message can revert this. */
1221 0 : return;
1222 0 : };
1223 :
1224 0 : int forward_msg = 1;
1225 :
1226 0 : switch( sig ) {
1227 0 : case FD_SNAPSHOT_MSG_CTRL_INIT_FULL:
1228 0 : case FD_SNAPSHOT_MSG_CTRL_INIT_INCR: {
1229 0 : FD_TEST( ctx->state==FD_SNAPSHOT_STATE_IDLE );
1230 0 : ctx->state = FD_SNAPSHOT_STATE_PROCESSING;
1231 0 : ctx->full = sig==FD_SNAPSHOT_MSG_CTRL_INIT_FULL;
1232 0 : ctx->in.pos = 0UL;
1233 0 : ctx->txncache_entries_len = 0UL;
1234 0 : ctx->blockhash_offsets_len = 0UL;
1235 0 : ctx->manifest_capitalization = 0UL;
1236 0 : fd_txncache_reset( ctx->txncache );
1237 0 : fd_ssparse_init( ctx->ssparse );
1238 0 : fd_ssparse_batch_enable( ctx->ssparse, 1 );
1239 0 : fd_ssmanifest_parser_init( ctx->manifest_parser, fd_chunk_to_laddr( ctx->manifest_out.mem, ctx->manifest_out.chunk ) );
1240 0 : fd_slot_delta_parser_init( ctx->slot_delta_parser );
1241 0 : fd_memset( &ctx->flags, 0, sizeof(ctx->flags) );
1242 :
1243 : /* Rewind metric counters (no-op unless recovering from a fail) */
1244 0 : if( sig==FD_SNAPSHOT_MSG_CTRL_INIT_FULL ) {
1245 0 : ctx->metrics.accounts_loaded = ctx->metrics.full_accounts_loaded = 0;
1246 0 : ctx->metrics.accounts_replaced = ctx->metrics.full_accounts_replaced = 0;
1247 0 : ctx->metrics.accounts_ignored = ctx->metrics.full_accounts_ignored = 0;
1248 0 : ctx->metrics.full_bytes_read = 0UL;
1249 0 : ctx->metrics.incremental_bytes_read = 0UL;
1250 0 : ctx->full_genesis_creation_time_seconds = 0UL;
1251 0 : ctx->capitalization = 0UL;
1252 0 : ctx->dup_capitalization = 0UL;
1253 0 : ctx->recovery.capitalization = 0UL;
1254 :
1255 0 : fd_stake_delegations_reset( fd_banks_stake_delegations_root_query( ctx->banks ) );
1256 0 : fd_accdb_reset( ctx->accdb );
1257 0 : fd_accdb_fork_id_t null_fork_id = (fd_accdb_fork_id_t){ .val = USHORT_MAX };
1258 0 : ctx->accdb_root_fork_id = fd_accdb_attach_child( ctx->accdb, null_fork_id );
1259 :
1260 0 : fd_accdb_snapshot_load_begin( ctx->accdb );
1261 :
1262 0 : ctx->slot_history.captured = 0;
1263 0 : ctx->slot_history.capturing = 0;
1264 :
1265 0 : fd_memset( ctx->feature_snoop, 0, sizeof(ctx->feature_snoop) );
1266 0 : ctx->feature_reasm.capturing = 0;
1267 0 : ctx->stake_reasm.capturing = 0;
1268 0 : } else {
1269 0 : ctx->metrics.accounts_loaded = ctx->metrics.full_accounts_loaded;
1270 0 : ctx->metrics.accounts_replaced = ctx->metrics.full_accounts_replaced;
1271 0 : ctx->metrics.accounts_ignored = ctx->metrics.full_accounts_ignored;
1272 0 : ctx->metrics.incremental_bytes_read = 0UL;
1273 :
1274 0 : ctx->capitalization = ctx->recovery.capitalization;
1275 0 : ctx->dup_capitalization = 0UL;
1276 :
1277 : /* Discard stale capture so the retry's sysvar is snooped fresh */
1278 0 : ctx->slot_history.captured = 0;
1279 0 : ctx->slot_history.capturing = 0;
1280 0 : ctx->feature_reasm.capturing = 0;
1281 0 : ctx->stake_reasm.capturing = 0;
1282 :
1283 : /* Create a child fork for incremental writes. On failure,
1284 : fd_accdb_purge(child) reverts just the incremental changes.
1285 : On success, fd_accdb_advance_root(child) promotes them. */
1286 0 : ctx->accdb_incr_fork_id = fd_accdb_attach_child( ctx->accdb, ctx->accdb_root_fork_id );
1287 0 : }
1288 :
1289 : /* Save the slot advertised by the snapshot peer and verify it
1290 : against the slot in the snapshot manifest. For redirect-based
1291 : HTTP downloads, these are initial estimates from gossip and
1292 : will be updated by the META message below once the redirect
1293 : resolves to a concrete snapshot filename. */
1294 0 : fd_ssctrl_init_t const * msg = fd_chunk_to_laddr_const( ctx->in.wksp, chunk );
1295 0 : ctx->advertised_slot = msg->slot;
1296 0 : fd_memcpy( ctx->advertised_hash, msg->snapshot_hash, FD_HASH_FOOTPRINT );
1297 0 : break;
1298 0 : }
1299 :
1300 0 : case FD_SNAPSHOT_MSG_META: {
1301 : /* For redirect-based HTTP downloads, the META message carries
1302 : the resolved slot and hash from the actual snapshot filename
1303 : the server redirected to. Update the advertised values so
1304 : that process_manifest can verify the manifest against them. */
1305 0 : FD_TEST( sz==sizeof(fd_ssctrl_meta_t) );
1306 0 : fd_ssctrl_meta_t const * meta = fd_chunk_to_laddr_const( ctx->in.wksp, chunk );
1307 0 : if( meta->resolved_slot!=ULONG_MAX ) {
1308 0 : ctx->advertised_slot = meta->resolved_slot;
1309 0 : fd_memcpy( ctx->advertised_hash, meta->resolved_hash, FD_HASH_FOOTPRINT );
1310 0 : }
1311 0 : forward_msg = 0; /* snapct already receives META directly from snapld */
1312 0 : break;
1313 0 : }
1314 :
1315 0 : case FD_SNAPSHOT_MSG_CTRL_FINI: {
1316 : /* This is a special case: handle_data_frag must have already
1317 : processed FD_SSPARSE_ADVANCE_DONE and moved the state into
1318 : FD_SNAPSHOT_STATE_FINISHING. Otherwise, treat this as a
1319 : malformed snapshot so that the pipeline can retry. */
1320 0 : if( FD_UNLIKELY( ctx->state!=FD_SNAPSHOT_STATE_FINISHING ) ) {
1321 0 : FD_LOG_WARNING(( "received FINI while in state %s (%lu), expected FINISHING (possibly truncated tar stream)",
1322 0 : fd_ssctrl_state_str( (ulong)ctx->state ), (ulong)ctx->state ));
1323 0 : transition_malformed( ctx, stem );
1324 0 : forward_msg = 0;
1325 0 : break;
1326 0 : }
1327 0 : break;
1328 0 : }
1329 :
1330 0 : case FD_SNAPSHOT_MSG_CTRL_NEXT: {
1331 0 : FD_TEST( ctx->state==FD_SNAPSHOT_STATE_FINISHING );
1332 0 : ctx->state = FD_SNAPSHOT_STATE_IDLE;
1333 :
1334 0 : if( FD_UNLIKELY( verify_slot_deltas_with_slot_history( ctx ) ) ) {
1335 0 : FD_LOG_WARNING(( "slot deltas verification failed for full snapshot" ));
1336 0 : transition_malformed( ctx, stem );
1337 0 : forward_msg = 0;
1338 0 : break;
1339 0 : }
1340 :
1341 0 : ctx->capitalization = fd_ulong_sat_sub( ctx->capitalization, ctx->dup_capitalization );
1342 0 : if( FD_UNLIKELY( validate_capitalization( ctx )!=0 ) ) {
1343 0 : transition_malformed( ctx, stem );
1344 0 : forward_msg = 0;
1345 0 : break;
1346 0 : }
1347 :
1348 0 : ctx->recovery.capitalization = ctx->capitalization;
1349 0 : fd_accdb_snapshot_save_whead( ctx->accdb, &ctx->recovery.accdb_metadata );
1350 0 : ctx->recovery.feature_snoop = *ctx->feature_snoop;
1351 :
1352 : /* Backup metric counters */
1353 0 : ctx->metrics.full_accounts_loaded = ctx->metrics.accounts_loaded;
1354 0 : ctx->metrics.full_accounts_replaced = ctx->metrics.accounts_replaced;
1355 0 : ctx->metrics.full_accounts_ignored = ctx->metrics.accounts_ignored;
1356 0 : break;
1357 0 : }
1358 :
1359 0 : case FD_SNAPSHOT_MSG_CTRL_DONE: {
1360 0 : FD_TEST( ctx->state==FD_SNAPSHOT_STATE_FINISHING );
1361 0 : ctx->state = FD_SNAPSHOT_STATE_IDLE;
1362 :
1363 0 : if( FD_UNLIKELY( verify_slot_deltas_with_slot_history( ctx ) ) ) {
1364 0 : if( ctx->full ) FD_LOG_WARNING(( "slot deltas verification failed for full snapshot" ));
1365 0 : else FD_LOG_WARNING(( "slot deltas verification failed for incremental snapshot" ));
1366 0 : transition_malformed( ctx, stem );
1367 0 : forward_msg = 0;
1368 0 : break;
1369 0 : }
1370 :
1371 0 : ctx->capitalization = fd_ulong_sat_sub( ctx->capitalization, ctx->dup_capitalization );
1372 0 : if( FD_UNLIKELY( validate_capitalization( ctx )!=0 ) ) {
1373 0 : transition_malformed( ctx, stem );
1374 0 : forward_msg = 0;
1375 0 : break;
1376 0 : }
1377 :
1378 0 : if( !ctx->full ) {
1379 0 : fd_accdb_snapshot_recover_delta( ctx->accdb, ctx->accdb_incr_fork_id );
1380 : /* ensure that snapin tile sees all delta changes before rooting */
1381 0 : __atomic_thread_fence( __ATOMIC_SEQ_CST );
1382 0 : fd_accdb_advance_root( ctx->accdb, ctx->accdb_incr_fork_id );
1383 0 : ctx->accdb_root_fork_id = ctx->accdb_incr_fork_id;
1384 0 : ctx->accdb_incr_fork_id = (fd_accdb_fork_id_t){ .val = USHORT_MAX };
1385 0 : }
1386 :
1387 0 : fd_accdb_snapshot_load_end( ctx->accdb );
1388 :
1389 0 : fd_feature_snoop_finalize( &ctx->bank->f.features, ctx->bank_slot, &ctx->epoch_schedule, ctx->feature_snoop );
1390 :
1391 : /* Notify replay when snapshot is fully loaded and verified. */
1392 0 : fd_stem_publish( stem, ctx->manifest_out.idx, fd_ssmsg_sig( FD_SSMSG_DONE ), 0UL, 0UL, 0UL, 0UL, 0UL );
1393 0 : break;
1394 0 : }
1395 :
1396 0 : case FD_SNAPSHOT_MSG_CTRL_ERROR: {
1397 0 : FD_TEST( ctx->state!=FD_SNAPSHOT_STATE_SHUTDOWN );
1398 0 : ctx->state = FD_SNAPSHOT_STATE_ERROR;
1399 0 : break;
1400 0 : }
1401 :
1402 0 : case FD_SNAPSHOT_MSG_CTRL_FAIL: {
1403 0 : FD_TEST( ctx->state!=FD_SNAPSHOT_STATE_SHUTDOWN );
1404 0 : if( ctx->full ) {
1405 0 : fd_accdb_reset( ctx->accdb );
1406 0 : ctx->accdb_root_fork_id = (fd_accdb_fork_id_t){ .val = USHORT_MAX };
1407 0 : ctx->accdb_incr_fork_id = (fd_accdb_fork_id_t){ .val = USHORT_MAX };
1408 0 : } else {
1409 0 : fd_accdb_purge( ctx->accdb, ctx->accdb_incr_fork_id ); /* this fork and subsequent children */
1410 0 : fd_accdb_snapshot_revert_whead( ctx->accdb, &ctx->recovery.accdb_metadata );
1411 0 : ctx->accdb_incr_fork_id = (fd_accdb_fork_id_t){ .val = USHORT_MAX };
1412 0 : *ctx->feature_snoop = ctx->recovery.feature_snoop;
1413 0 : }
1414 0 : ctx->state = FD_SNAPSHOT_STATE_IDLE;
1415 0 : break;
1416 0 : }
1417 :
1418 0 : case FD_SNAPSHOT_MSG_CTRL_SHUTDOWN: {
1419 0 : FD_TEST( ctx->state==FD_SNAPSHOT_STATE_IDLE );
1420 0 : ctx->state = FD_SNAPSHOT_STATE_SHUTDOWN;
1421 0 : break;
1422 0 : }
1423 :
1424 0 : default: {
1425 0 : FD_LOG_ERR(( "unexpected control frag %s (%lu) in state %s (%lu)",
1426 0 : fd_ssctrl_msg_ctrl_str( sig ), sig,
1427 0 : fd_ssctrl_state_str( (ulong)ctx->state ), (ulong)ctx->state ));
1428 0 : break;
1429 0 : }
1430 0 : }
1431 :
1432 : /* Forward the control message down the pipeline */
1433 0 : if( FD_LIKELY( forward_msg ) ) {
1434 0 : fd_stem_publish( stem, ctx->ct_out.idx, sig, 0UL, 0UL, 0UL, 0UL, 0UL );
1435 0 : }
1436 0 : }
1437 :
1438 : static inline int
1439 : returnable_frag( fd_snapin_tile_t * ctx,
1440 : ulong in_idx FD_PARAM_UNUSED,
1441 : ulong seq FD_PARAM_UNUSED,
1442 : ulong sig,
1443 : ulong chunk,
1444 : ulong sz,
1445 : ulong ctl FD_PARAM_UNUSED,
1446 : ulong tsorig FD_PARAM_UNUSED,
1447 : ulong tspub FD_PARAM_UNUSED,
1448 0 : fd_stem_context_t * stem ) {
1449 0 : FD_TEST( ctx->state!=FD_SNAPSHOT_STATE_SHUTDOWN );
1450 :
1451 0 : if( FD_UNLIKELY( sig==FD_SNAPSHOT_MSG_DATA ) ) return handle_data_frag( ctx, chunk, sz, stem );
1452 0 : else handle_control_frag( ctx, stem, sig, chunk, sz );
1453 :
1454 0 : return 0;
1455 0 : }
1456 :
1457 : static ulong
1458 : populate_allowed_fds( fd_topo_t const * topo FD_PARAM_UNUSED,
1459 : fd_topo_tile_t const * tile FD_PARAM_UNUSED,
1460 : ulong out_fds_cnt,
1461 0 : int * out_fds ) {
1462 0 : if( FD_UNLIKELY( out_fds_cnt<3UL ) ) FD_LOG_ERR(( "invalid out_fds_cnt %lu", out_fds_cnt ));
1463 :
1464 0 : ulong out_cnt = 0;
1465 0 : out_fds[ out_cnt++ ] = 2UL; /* stderr */
1466 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) ) {
1467 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
1468 0 : }
1469 0 : out_fds[ out_cnt++ ] = FD_ACCDB_FD_RW; /* accounts db */
1470 :
1471 0 : return out_cnt;
1472 0 : }
1473 :
1474 : static ulong
1475 : populate_allowed_seccomp( fd_topo_t const * topo,
1476 : fd_topo_tile_t const * tile,
1477 : ulong out_cnt,
1478 0 : struct sock_filter * out ) {
1479 0 : (void)topo; (void)tile;
1480 0 : populate_sock_filter_policy_fd_snapin_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), FD_ACCDB_FD_RW );
1481 0 : return sock_filter_policy_fd_snapin_tile_instr_cnt;
1482 0 : }
1483 :
1484 : static void
1485 : privileged_init( fd_topo_t const * topo,
1486 0 : fd_topo_tile_t const * tile ) {
1487 0 : fd_snapin_tile_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1488 0 : memset( ctx, 0, sizeof(fd_snapin_tile_t) );
1489 0 : FD_TEST( fd_rng_secure( &ctx->seed, 8UL ) );
1490 0 : }
1491 :
1492 : static inline fd_snapin_out_link_t
1493 : out1( fd_topo_t const * topo,
1494 : fd_topo_tile_t const * tile,
1495 0 : char const * name ) {
1496 0 : ulong idx = fd_topo_find_tile_out_link( topo, tile, name, 0UL );
1497 :
1498 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 };
1499 :
1500 0 : ulong mtu = topo->links[ tile->out_link_id[ idx ] ].mtu;
1501 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 };
1502 :
1503 0 : void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp;
1504 0 : ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache );
1505 0 : ulong wmark = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, mtu );
1506 0 : return (fd_snapin_out_link_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0, .mtu = mtu };
1507 0 : }
1508 :
1509 : static void
1510 : unprivileged_init( fd_topo_t const * topo,
1511 0 : fd_topo_tile_t const * tile ) {
1512 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1513 :
1514 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1515 0 : fd_snapin_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_snapin_tile_t), sizeof(fd_snapin_tile_t) );
1516 0 : void * _txncache = FD_SCRATCH_ALLOC_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->snapin.max_live_slots ) );
1517 0 : void * _accdb = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->snapin.max_live_slots ) );
1518 0 : void * _manifest_parser = FD_SCRATCH_ALLOC_APPEND( l, fd_ssmanifest_parser_align(), fd_ssmanifest_parser_footprint() );
1519 0 : void * _sd_parser = FD_SCRATCH_ALLOC_APPEND( l, fd_slot_delta_parser_align(), fd_slot_delta_parser_footprint() );
1520 0 : ctx->blockhash_offsets = FD_SCRATCH_ALLOC_APPEND( l, alignof(blockhash_group_t), sizeof(blockhash_group_t)*FD_SNAPIN_MAX_SLOT_DELTA_GROUPS );
1521 0 : ctx->txncache_entries = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sstxncache_entry_t), sizeof(fd_sstxncache_entry_t)*FD_SNAPIN_TXNCACHE_MAX_ENTRIES );
1522 :
1523 0 : ctx->full = 1;
1524 0 : ctx->state = FD_SNAPSHOT_STATE_IDLE;
1525 :
1526 0 : void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->snapin.accdb_obj_id );
1527 0 : fd_accdb_shmem_t * accdb_shmem = fd_accdb_shmem_join( _accdb_shmem );
1528 0 : FD_TEST( accdb_shmem );
1529 0 : ctx->accdb = fd_accdb_join( fd_accdb_new( _accdb, accdb_shmem, FD_ACCDB_FD_RW, 0UL, NULL ) );
1530 0 : FD_TEST( ctx->accdb );
1531 :
1532 0 : void * _txncache_shmem = fd_topo_obj_laddr( topo, tile->snapin.txncache_obj_id );
1533 0 : fd_txncache_shmem_t * txncache_shmem = fd_txncache_shmem_join( _txncache_shmem );
1534 0 : FD_TEST( txncache_shmem );
1535 0 : ctx->txncache = fd_txncache_join( fd_txncache_new( _txncache, txncache_shmem ) );
1536 0 : FD_TEST( ctx->txncache );
1537 :
1538 0 : ctx->banks = fd_banks_join( fd_topo_obj_laddr( topo, tile->snapin.banks_obj_id ) );
1539 0 : FD_TEST( ctx->banks );
1540 0 : ctx->bank = fd_banks_init_bank( ctx->banks );
1541 0 : FD_TEST( ctx->bank );
1542 0 : FD_TEST( ctx->bank->idx==0UL );
1543 :
1544 0 : ctx->txncache_entries_len = 0UL;
1545 0 : ctx->blockhash_offsets_len = 0UL;
1546 :
1547 0 : ctx->manifest_parser = fd_ssmanifest_parser_join( fd_ssmanifest_parser_new( _manifest_parser ) );
1548 0 : FD_TEST( ctx->manifest_parser );
1549 :
1550 0 : ctx->slot_delta_parser = fd_slot_delta_parser_join( fd_slot_delta_parser_new( _sd_parser ) );
1551 0 : FD_TEST( ctx->slot_delta_parser );
1552 :
1553 0 : fd_memset( &ctx->metrics, 0, sizeof(ctx->metrics) );
1554 :
1555 0 : if( FD_UNLIKELY( tile->kind_id ) ) FD_LOG_ERR(( "There can only be one `" NAME "` tile" ));
1556 0 : if( FD_UNLIKELY( tile->in_cnt!=1UL ) ) FD_LOG_ERR(( "tile `" NAME "` has %lu ins, expected 1", tile->in_cnt ));
1557 :
1558 0 : ctx->ct_out = out1( topo, tile, "snapin_ct" );
1559 0 : ctx->manifest_out = out1( topo, tile, "snapin_manif" );
1560 0 : ctx->gui_out = out1( topo, tile, "snapin_gui" );
1561 :
1562 0 : if( FD_UNLIKELY( ctx->ct_out.idx==ULONG_MAX ) ) FD_LOG_ERR(( "tile `" NAME "` missing required out link `snapin_ct`" ));
1563 0 : if( FD_UNLIKELY( ctx->manifest_out.idx==ULONG_MAX ) ) FD_LOG_ERR(( "tile `" NAME "` missing required out link `snapin_manif`" ));
1564 :
1565 0 : fd_ssparse_init( ctx->ssparse );
1566 0 : fd_ssmanifest_parser_init( ctx->manifest_parser, fd_chunk_to_laddr( ctx->manifest_out.mem, ctx->manifest_out.chunk ) );
1567 0 : fd_slot_delta_parser_init( ctx->slot_delta_parser );
1568 :
1569 0 : fd_topo_link_t const * in_link = &topo->links[ tile->in_link_id[ 0UL ] ];
1570 0 : FD_TEST( 0==strcmp( in_link->name, "snapdc_in" ) );
1571 0 : fd_topo_wksp_t const * in_wksp = &topo->workspaces[ topo->objs[ in_link->dcache_obj_id ].wksp_id ];
1572 0 : ctx->in.wksp = in_wksp->wksp;
1573 0 : ctx->in.chunk0 = fd_dcache_compact_chunk0( ctx->in.wksp, in_link->dcache );
1574 0 : ctx->in.wmark = fd_dcache_compact_wmark( ctx->in.wksp, in_link->dcache, in_link->mtu );
1575 0 : ctx->in.mtu = in_link->mtu;
1576 0 : ctx->in.pos = 0UL;
1577 :
1578 0 : ctx->gui_config_acct_sz = 0UL;
1579 0 : ctx->gui_config_acct_off = 0UL;
1580 :
1581 0 : ctx->advertised_slot = 0UL;
1582 0 : ctx->bank_slot = 0UL;
1583 0 : ctx->epoch = 0UL;
1584 :
1585 0 : ctx->full_genesis_creation_time_seconds = 0UL;
1586 0 : ctx->manifest_capitalization = 0UL;
1587 0 : ctx->capitalization = 0UL;
1588 0 : ctx->dup_capitalization = 0UL;
1589 0 : ctx->recovery.capitalization = 0UL;
1590 0 : memset( &ctx->recovery.accdb_metadata, 0, sizeof(ctx->recovery.accdb_metadata) );
1591 :
1592 0 : ctx->accdb_root_fork_id = (fd_accdb_fork_id_t){ .val = USHORT_MAX };
1593 0 : ctx->accdb_incr_fork_id = (fd_accdb_fork_id_t){ .val = USHORT_MAX };
1594 :
1595 0 : fd_memset( &ctx->flags, 0, sizeof(ctx->flags) );
1596 0 : ctx->boot_timestamp = fd_log_wallclock();
1597 0 : }
1598 :
1599 : /* There are 3 output links that affect the calculation of STEM_BURST:
1600 : 1. snapin_ct
1601 : 2. snapin_manif - worst case: 1 message
1602 : 3. snapin_gui - worst case: 1 message (config program account)
1603 : The STEM_BURST is the max value across these 3 links (not the sum).
1604 : Note that snapin_txn is excluded from this calculation, since it is
1605 : an unreliable link, working as a dcache place holder. */
1606 0 : #define STEM_BURST 1UL
1607 :
1608 0 : #define STEM_LAZY (128L*3000L)
1609 :
1610 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_snapin_tile_t
1611 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_snapin_tile_t)
1612 :
1613 : #define STEM_CALLBACK_SHOULD_SHUTDOWN should_shutdown
1614 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
1615 0 : #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
1616 :
1617 : #include "../../disco/stem/fd_stem.c"
1618 :
1619 : static ulong
1620 0 : max_event_sz( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
1621 0 : return sizeof(fd_event_accdb_partition_added_t);
1622 0 : }
1623 :
1624 : fd_topo_run_tile_t fd_tile_snapin = {
1625 : .name = NAME,
1626 : .populate_allowed_fds = populate_allowed_fds,
1627 : .populate_allowed_seccomp = populate_allowed_seccomp,
1628 : .scratch_align = scratch_align,
1629 : .scratch_footprint = scratch_footprint,
1630 : .privileged_init = privileged_init,
1631 : .unprivileged_init = unprivileged_init,
1632 : .max_event_sz = max_event_sz,
1633 : .run = stem_run,
1634 : };
1635 :
1636 : #undef NAME
|