LCOV - code coverage report
Current view: top level - discof/restore - fd_snapin_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 118 970 12.2 %
Date: 2026-08-14 04:54:57 Functions: 4 52 7.7 %

          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

Generated by: LCOV version 1.14