LCOV - code coverage report
Current view: top level - discof/restore - fd_snapin_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 759 0.0 %
Date: 2026-03-19 18:19:27 Functions: 0 20 0.0 %

          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

Generated by: LCOV version 1.14