LCOV - code coverage report
Current view: top level - discof/restore - fd_snapin_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 1419 0.0 %
Date: 2026-11-11 12:40:43 Functions: 0 165 0.0 %

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : #include "utils/fd_ssctrl.h"
       3             : #include "utils/fd_ssload.h"
       4             : #include "utils/fd_ssmsg.h"
       5             : #include "utils/fd_ssparse.h"
       6             : #include "utils/fd_ssmanifest_parser.h"
       7             : #include "utils/fd_wfs.h"
       8             : #include "utils/fd_slot_delta_parser.h"
       9             : #include "../../util/fd_hash32.h"
      10             : #include "../../util/bits/fd_float.h"
      11             : #include "../../util/bits/fd_uwide.h"
      12             : 
      13             : #include "../../disco/topo/fd_topo.h"
      14             : #include "../../disco/metrics/fd_metrics.h"
      15             : #include "../../disco/gui/fd_gui_config_parse.h"
      16             : #include "../../flamenco/runtime/fd_txncache.h"
      17             : #include "../../flamenco/runtime/fd_system_ids.h"
      18             : #include "../../flamenco/runtime/fd_hashes.h"
      19             : #include "../../flamenco/runtime/sysvar/fd_sysvar_cache.h"
      20             : #include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h"
      21             : #include "../../flamenco/runtime/sysvar/fd_sysvar_slot_history.h"
      22             : #include "../../flamenco/rewards/fd_rewards_base.h"
      23             : 
      24             : #include "../../flamenco/runtime/fd_bank.h"
      25             : #include "../../flamenco/features/fd_features.h"
      26             : #include "../../flamenco/stakes/fd_stake_types.h"
      27             : #include "../../disco/stem/fd_stem.h"
      28             : #include "../../flamenco/accdb/fd_accdb.h"
      29             : #include "../../disco/events/generated/fd_event_gen.h"
      30             : 
      31             : #include <linux/futex.h>
      32             : #include "generated/fd_snapin_tile_seccomp.h"
      33             : 
      34             : #include <errno.h>
      35             : #include <fcntl.h>
      36             : #include <unistd.h>
      37             : 
      38             : #define NAME "snapin"
      39             : 
      40           0 : #define FD_SNAPIN_WRITE_BUF_SZ      (16UL<<20)
      41             : #define FD_SNAPIN_WRITE_ACCOUNT_MAX (FD_SNAPIN_WRITE_BUF_SZ/sizeof(fd_accdb_disk_meta_t))
      42             : /* Accounts are written with O_DIRECT so that snapin tiles do not
      43             :    serialize on the inode lock taken by buffered writes.  Each flush is
      44             :    padded to FD_SNAPIN_DIRECT_ALIGN with a dead record header.  The
      45             :    buffer fills only to FD_SNAPIN_WRITE_BUF_MAX so the header always
      46             :    fits. */
      47           0 : #define FD_SNAPIN_DIRECT_ALIGN      (4096UL)
      48           0 : #define FD_SNAPIN_WRITE_BUF_MAX     (FD_SNAPIN_WRITE_BUF_SZ-sizeof(fd_accdb_disk_meta_t))
      49             : 
      50             : FD_STATIC_ASSERT( FD_SNAPIN_WRITE_BUF_SZ%FD_SNAPIN_DIRECT_ALIGN==0UL, write_buf_align );
      51             : 
      52             : /* Raw state values exported by the metrics (states= enums) */
      53             : FD_STATIC_ASSERT( FD_SNAPSHOT_STATE_IDLE==FD_METRICS_ENUM_SNAPSHOT_STREAM_STATE_V_IDLE_IDX, metrics_state );
      54             : FD_STATIC_ASSERT( FD_SNAPSHOT_STATE_PROCESSING==FD_METRICS_ENUM_SNAPSHOT_STREAM_STATE_V_PROCESSING_IDX, metrics_state );
      55             : FD_STATIC_ASSERT( FD_SNAPSHOT_STATE_FINISHING==FD_METRICS_ENUM_SNAPSHOT_STREAM_STATE_V_FINISHING_IDX, metrics_state );
      56             : FD_STATIC_ASSERT( FD_SNAPSHOT_STATE_ERROR==FD_METRICS_ENUM_SNAPSHOT_STREAM_STATE_V_ERROR_IDX, metrics_state );
      57             : FD_STATIC_ASSERT( FD_SNAPSHOT_STATE_SHUTDOWN==FD_METRICS_ENUM_SNAPSHOT_STREAM_STATE_V_SHUTDOWN_IDX, metrics_state );
      58             : FD_STATIC_ASSERT( FD_SNAPSHOT_DATA_MTU<FD_SNAPIN_WRITE_BUF_MAX, write_buf );
      59             : FD_STATIC_ASSERT( sizeof(fd_accdb_disk_meta_t)+FD_RUNTIME_ACC_SZ_MAX<=FD_SNAPIN_WRITE_BUF_MAX, max_account );
      60             : 
      61             : /* The snapin tiles are state machines that parse and load a full and
      62             :    optionally an incremental snapshot.  They are responsible for loading
      63             :    accounts into the accounts database and writing their records to
      64             :    disk. */
      65             : 
      66             : struct fd_blockhash_entry {
      67             :   fd_hash_t blockhash;
      68             : 
      69             :   struct {
      70             :     ulong prev;
      71             :     ulong next;
      72             :   } map;
      73             : };
      74             : 
      75             : typedef struct fd_blockhash_entry fd_blockhash_entry_t;
      76             : 
      77             : #define MAP_NAME                           blockhash_map
      78           0 : #define MAP_KEY                            blockhash
      79             : #define MAP_KEY_T                          fd_hash_t
      80             : #define MAP_ELE_T                          fd_blockhash_entry_t
      81           0 : #define MAP_KEY_EQ(k0,k1)                  (!memcmp((k0),(k1), sizeof(fd_hash_t)))
      82           0 : #define MAP_KEY_HASH(key,seed)             (fd_hash32( (key)->uc, (seed) ))
      83           0 : #define MAP_PREV                           map.prev
      84           0 : #define MAP_NEXT                           map.next
      85             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
      86             : #include "../../util/tmpl/fd_map_chain.c"
      87             : 
      88             : /* One link of the txncache chain rebuilt by populate_txncache, from one
      89             :    entry of the snapshot's blockhash queue. */
      90             : 
      91             : struct fd_snapin_bank {
      92             :   ulong                 hash_index;
      93             :   uchar                 blockhash[ 32UL ];
      94             :   fd_txncache_fork_id_t fork_id;
      95             :   ulong                 txnhash_offset;
      96             : };
      97             : 
      98             : typedef struct fd_snapin_bank fd_snapin_bank_t;
      99             : 
     100             : #define SORT_NAME        fd_snapin_bank_sort_newest_first
     101           0 : #define SORT_KEY_T       fd_snapin_bank_t
     102           0 : #define SORT_BEFORE(a,b) ((a).hash_index>(b).hash_index)
     103             : #include "../../util/tmpl/fd_sort.c"
     104             : 
     105             : /* For a transaction to be valid to be inserted into the txncache, it
     106             :    must reference a blockhash that is in the set of recent blockhashes.
     107             :    This means that only transactions executed in the latest 151 rooted
     108             :    blocks can be in the txncache.  The remaining entries can be ignored.
     109             : 
     110             :    A slot delta holds the transactions of one block, each twice ,once as
     111             :    signature and once as message hash.  When slot deltas are ingested,
     112             :    their entries are staged in a grid of FD_TXNCACHE_MAX_SLOT_DELTAS
     113             :    rows of txncache_max_entries_per_slot entries per row, one row per
     114             :    retained slot delta. */
     115             : 
     116             : FD_STATIC_ASSERT( FD_TXNCACHE_MAX_SLOT_DELTAS<=FD_SLOT_DELTA_MAX_ENTRIES, txncache_staging_slot_cnt );
     117             : 
     118             : /* A blockhash group is one (slot, blockhash) pair in the status cache,
     119             :    holding the txnhash offset for that blockhash and the entries of the
     120             :    transactions executed in that slot which referenced the blockhash.
     121             :    Agave inserts every processed transaction into the status cache twice
     122             :    (once with message hash and once with signature), both times under
     123             :    the same blockhash and slot.  Groups can reference any blockhash,
     124             :    including durable nonces that are not in the recent blockhash queue.
     125             :    So an honest slot has at most txncache_max_groups_per_slot groups
     126             :    (config->limits.max_txn_per_slot).  Snapshots exceeding this are
     127             :    rejected as malformed. */
     128             : 
     129             : struct blockhash_group {
     130             :   uchar blockhash[ 32UL ];
     131             :   uint  txnhash_offset;
     132             :   uint  txncache_entry_cnt;
     133             : };
     134             : 
     135             : typedef struct blockhash_group blockhash_group_t;
     136             : 
     137             : FD_STATIC_ASSERT( sizeof(blockhash_group_t)==40UL, blockhash_group );
     138             : 
     139             : /* Each slot delta's groups are staged in the txncache at a fixed
     140             :    stride of txncache_staging_row_sz bytes. */
     141             : 
     142             : FD_FN_CONST static inline ulong
     143           0 : txncache_staging_row_sz( ulong max_groups_per_slot ) {
     144           0 :   return fd_ulong_align_up( max_groups_per_slot*sizeof(blockhash_group_t), FD_TXNCACHE_SNAPIN_STAGE_ALIGN );
     145           0 : }
     146             : 
     147             : /* After filtering with the recent blockhash queue, there is at most one
     148             :    group per retained (slot, recent blockhash) pair.  Filtering can only
     149             :    happen after we receive the manifest, hence we still need
     150             :    FD_TXNCACHE_MAX_SLOT_DELTAS*txncache_max_groups_per_slot worst case
     151             :    slot deltas buffered. */
     152           0 : #define FD_SNAPIN_MAX_RECENT_GROUPS (FD_TXNCACHE_MAX_SLOT_DELTAS*FD_TXNCACHE_MAX_SLOT_DELTAS)
     153             : 
     154             : struct recent_blockhash_group {
     155             :   ulong blockhash_bank_i;
     156             :   ulong execution_slot;
     157             :   ulong execution_bank_i;
     158             :   ulong txnhash_offset;
     159             :   ulong txncache_entry_idx;
     160             :   ulong txncache_entry_cnt;
     161             : };
     162             : 
     163             : typedef struct recent_blockhash_group recent_blockhash_group_t;
     164             : 
     165             : struct txncache_staging_slot {
     166             :   ulong slot;
     167             :   ulong entry_cnt;
     168             :   ulong group_cnt;
     169             : };
     170             : 
     171             : typedef struct txncache_staging_slot txncache_staging_slot_t;
     172             : 
     173             : struct fd_snapin_out_link {
     174             :   ulong       idx;
     175             :   fd_wksp_t * mem;
     176             :   ulong       chunk0;
     177             :   ulong       wmark;
     178             :   ulong       chunk;
     179             :   ulong       mtu;
     180             : };
     181             : typedef struct fd_snapin_out_link fd_snapin_out_link_t;
     182             : 
     183             : struct fd_snapin_account_batch {
     184             :   ulong cnt;
     185             :   ulong lamports    [ FD_SNAPIN_WRITE_ACCOUNT_MAX ];
     186             :   uint  slots       [ FD_SNAPIN_WRITE_ACCOUNT_MAX ];
     187             :   uint  data_lens   [ FD_SNAPIN_WRITE_ACCOUNT_MAX ];
     188             :   int   executables [ FD_SNAPIN_WRITE_ACCOUNT_MAX ];
     189             : };
     190             : 
     191             : typedef struct fd_snapin_account_batch fd_snapin_account_batch_t;
     192             : 
     193             : /* Only tile 0 uses this state. */
     194             : struct fd_snapin_lead {
     195             :   uint init_completed : 1;  /* did INIT complete for this attempt? */
     196             : 
     197             :   ulong seed;
     198             :   long boot_timestamp;
     199             : 
     200             :   fd_txncache_t * txncache;
     201             :   fd_bank_t *  bank;
     202             : 
     203             :   fd_ssmanifest_parser_t * manifest_parser;
     204             :   fd_slot_delta_parser_t * slot_delta_parser;
     205             : 
     206             :   struct {
     207             :     int manifest_done;
     208             :     int status_cache_done;
     209             :     int manifest_processed;
     210             :   } flags;
     211             : 
     212             :   ulong advertised_slot;
     213             :   ulong bank_slot;
     214             :   ulong epoch;
     215             : 
     216             :   fd_epoch_schedule_t epoch_schedule;
     217             : 
     218             :   ulong full_genesis_creation_time_seconds;
     219             :   uchar advertised_hash[ FD_HASH_FOOTPRINT ];
     220             : 
     221             :   ulong manifest_capitalization; /* capitalization according to the current snapshot manifest */
     222             : 
     223             :   struct {
     224             :     ulong loaded;
     225             :     ulong duplicates;
     226             :   } account_counts;
     227             : 
     228             :   struct {
     229             :     ulong                        capitalization;
     230             :     fd_accdb_snapshot_recovery_t accdb_metadata;
     231             :   } recovery; /* stores state from the last full snapshot for incremental revert */
     232             : 
     233             :   fd_sysvar_cache_t sysvar_cache[1]; /* verify_sysvars scratch */
     234             : 
     235             :   blockhash_group_t *        blockhash_groups;     /* groups of one staged slot delta */
     236             :   ulong                      blockhash_groups_cnt; /* every group parsed, including those from dropped slots */
     237             :   recent_blockhash_group_t * recent_groups;
     238             :   ulong                      recent_groups_len;
     239             : 
     240             :   fd_sstxncache_hash_t *  txncache_entries;
     241             :   txncache_staging_slot_t txncache_slots[ FD_TXNCACHE_MAX_SLOT_DELTAS ];
     242             :   ulong                   txncache_slots_len;
     243             :   ulong                   txncache_current_slot_idx;
     244             :   ulong                   txncache_current_slot_group_cnt;
     245             :   ulong                   txncache_current_slot_entry_cnt; /* entries of the slot delta being parsed, retained or not */
     246             :   ulong                   txncache_max_groups_per_slot;  /* config->limits.max_txn_per_slot */
     247             :   ulong                   txncache_max_entries_per_slot; /* 2x, signature and message hash entries */
     248             : 
     249             :   fd_accdb_fork_id_t accdb_root_fork_id;
     250             :   fd_accdb_fork_id_t accdb_incr_fork_id; /* child fork for incremental writes (purge on failure) */
     251             :   fd_txncache_fork_id_t txncache_root_fork_id;
     252             : 
     253             :   fd_snapin_out_link_t manifest_out;
     254             : 
     255             :   /* Roll back a failure at the next INIT. */
     256             :   struct {
     257             :     int                pending;
     258             :     int                full;  /* failed attempt type */
     259             :     fd_accdb_fork_id_t fork;  /* failed incremental fork */
     260             :   } rollback;
     261             : };
     262             : 
     263             : typedef struct fd_snapin_lead fd_snapin_lead_t;
     264             : 
     265             : /* Shared state for parallel snapin tiles. */
     266             : struct fd_snapin_shmem {
     267             :   /* Tile 0 publishes the accdb fork before acknowledging INIT. */
     268             :   ulong fork_id;
     269             : 
     270             :   /* Stake delegations fork for incremental writes, USHORT_MAX for full. */
     271             :   ushort stake_fork;
     272             : 
     273             :   /* Per-tile attempt values.  Lamport totals are 128-bit <*_hi,*>:
     274             :      they count every account version, which can pass 2^64. */
     275             :   struct __attribute__((aligned(128))) {
     276             :     ulong loaded;
     277             :     ulong duplicates;
     278             :     ulong input_lamports;
     279             :     ulong duplicate_lamports;
     280             :     ulong input_lamports_hi;
     281             :     ulong duplicate_lamports_hi;
     282             :   } values[ FD_TOPO_MAX_TILE_IN_LINKS ];
     283             : 
     284             :   /* Atomic index of the next unclaimed appendvec. */
     285             :   ulong next_appendvec_ticket __attribute__((aligned(128)));
     286             : };
     287             : 
     288             : typedef struct fd_snapin_shmem fd_snapin_shmem_t;
     289             : 
     290             : struct fd_snapin_tile {
     291             :   int  state;
     292             :   uint full : 1;  /* loading a full snapshot? */
     293             : 
     294             :   fd_snapin_lead_t lead;
     295             : 
     296             :   ulong tile_idx;           /* tile kind ID */
     297             :   ulong lane_cnt;
     298             :   ulong expected_frame;
     299             :   ulong pending_control;    /* control message expected from snapdc tiles */
     300             :   uchar control_seen[ FD_TOPO_MAX_TILE_IN_LINKS ];
     301             : 
     302             :   fd_accdb_t * accdb;
     303             : 
     304             :   /* Shared stake data updated by every tile. */
     305             :   fd_stake_delegations_t * stake_delegations;
     306             : 
     307             :   fd_ssparse_t ssparse[1];
     308             : 
     309             :   struct {
     310             :     ulong full_bytes_read;
     311             :     ulong incremental_bytes_read;
     312             : 
     313             :     /* Account counters (full + incremental) */
     314             :     ulong accounts_loaded;
     315             :     ulong accounts_replaced;
     316             :     ulong accounts_ignored;
     317             : 
     318             :     /* Account counters (snapshot taken for full snapshot only) */
     319             :     ulong full_accounts_loaded;
     320             :     ulong full_accounts_replaced;
     321             :     ulong full_accounts_ignored;
     322             : 
     323             :     /* Persistent counters */
     324             :     ulong total_accounts_processed;
     325             :     ulong total_account_batches_processed;
     326             :     ulong disk_bytes_written;
     327             :   } metrics;
     328             : 
     329             :   struct {
     330             :     fd_wksp_t * wksp;
     331             :     ulong       chunk0;
     332             :     ulong       wmark;
     333             :     ulong       mtu;
     334             :     ulong       pos;
     335             :   } in[ FD_TOPO_MAX_TILE_IN_LINKS ];
     336             : 
     337             :   fd_snapin_out_link_t ct_out;
     338             :   fd_snapin_out_link_t gui_out;
     339             : 
     340             :   /* Shared snapshot state. */
     341             :   fd_snapin_shmem_t * shmem;
     342             : 
     343             :   /* Parse state for one attempt. */
     344             :   ulong appendvec_seq;      /* next appendvec number */
     345             :   ulong claimed_appendvec;  /* current claim */
     346             :   ulong incr_fork;          /* insert fork; USHORT_MAX for full */
     347             : 
     348             :   struct {
     349             :     uchar                     buf[ FD_SNAPIN_WRITE_BUF_SZ ] __attribute__((aligned(FD_SNAPIN_DIRECT_ALIGN)));
     350             :     ulong                     buf_used;
     351             :     int                       accdb_direct_fd;  /* O_DIRECT fd to accounts.db */
     352             :     fd_snapin_account_batch_t batch;
     353             :   } writer;
     354             : 
     355             :   /* Buffer streamed account data before inserting. */
     356             :   struct {
     357             :     int   executable;
     358             :     ulong slot;
     359             :     ulong lamports;
     360             :     ulong data_len;
     361             :     ulong bytes_received;
     362             :     uchar pubkey[ 32UL ];
     363             :     uchar owner [ 32UL ];
     364             :     uchar data[ FD_RUNTIME_ACC_SZ_MAX ] __attribute__((aligned(64)));
     365             :   } staged;
     366             : 
     367             :   ulong     wfs_slot;
     368             :   fd_hash_t wfs_bank_hash;
     369             :   ushort    wfs_shred_version;
     370             :   int       wfs_hash_is_zero;
     371             : };
     372             : 
     373             : typedef struct fd_snapin_tile fd_snapin_tile_t;
     374             : 
     375             : static inline int
     376           0 : is_lead( fd_snapin_tile_t const * ctx ) {
     377           0 :   return ctx->tile_idx==0UL;
     378           0 : }
     379             : 
     380             : static void
     381           0 : format_count( char * out, ulong out_sz, ulong n ) {
     382           0 :   if(      n>=1000000UL ) FD_TEST( fd_cstr_printf_check( out, out_sz, NULL, "%.1fM", (double)n/1e6 ) );
     383           0 :   else if( n>=1000UL    ) FD_TEST( fd_cstr_printf_check( out, out_sz, NULL, "%.1fK", (double)n/1e3 ) );
     384           0 :   else                    FD_TEST( fd_cstr_printf_check( out, out_sz, NULL, "%lu",   n             ) );
     385           0 : }
     386             : 
     387             : static inline int
     388           0 : should_shutdown( fd_snapin_tile_t * ctx ) {
     389           0 :   if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_SHUTDOWN && is_lead( ctx ) ) ) {
     390           0 :     long  elapsed_ns   = fd_log_wallclock() - ctx->lead.boot_timestamp;
     391           0 :     char  loaded_buf[ 32 ];
     392           0 :     char  dup_buf   [ 32 ];
     393           0 :     format_count( loaded_buf, sizeof(loaded_buf), ctx->lead.account_counts.loaded );
     394           0 :     format_count( dup_buf,    sizeof(dup_buf),    ctx->lead.account_counts.duplicates );
     395           0 :     FD_LOG_NOTICE(( "loaded %s accounts %s(%s dups)%s from snapshot in %.3f seconds",
     396           0 :                     loaded_buf, fd_log_style_dim(), dup_buf, fd_log_style_normal(), (double)elapsed_ns/1e9 ));
     397           0 :   }
     398           0 :   return ctx->state==FD_SNAPSHOT_STATE_SHUTDOWN;
     399           0 : }
     400             : 
     401             : static ulong
     402           0 : scratch_align( void ) {
     403           0 :   return FD_SNAPIN_DIRECT_ALIGN;
     404           0 : }
     405             : 
     406             : static ulong
     407           0 : scratch_footprint( fd_topo_tile_t const * tile ) {
     408           0 :   ulong l = FD_LAYOUT_INIT;
     409           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_snapin_tile_t),         sizeof(fd_snapin_tile_t)                                          );
     410           0 :   l = FD_LAYOUT_APPEND( l, fd_accdb_align(),                  fd_accdb_footprint( tile->snapin.max_live_slots, 0 ) );
     411             : 
     412             :   /* Only tile 0 publishes the manifest */
     413           0 :   if( FD_LIKELY( !tile->kind_id ) ) {
     414           0 :     l = FD_LAYOUT_APPEND( l, fd_txncache_align(),               fd_txncache_footprint( tile->snapin.max_live_slots )         );
     415           0 :     l = FD_LAYOUT_APPEND( l, fd_ssmanifest_parser_align(),      fd_ssmanifest_parser_footprint()                             );
     416           0 :     l = FD_LAYOUT_APPEND( l, fd_slot_delta_parser_align(),      fd_slot_delta_parser_footprint()                             );
     417           0 :     l = FD_LAYOUT_APPEND( l, alignof(recent_blockhash_group_t), sizeof(recent_blockhash_group_t)*FD_SNAPIN_MAX_RECENT_GROUPS );
     418           0 :     l = FD_LAYOUT_APPEND( l, alignof(fd_sstxncache_hash_t),     sizeof(fd_sstxncache_hash_t)*FD_TXNCACHE_MAX_SLOT_DELTAS*2UL*tile->snapin.max_txn_per_slot );
     419           0 :     l = FD_LAYOUT_APPEND( l, FD_TXNCACHE_SNAPIN_STAGE_ALIGN,    txncache_staging_row_sz( tile->snapin.max_txn_per_slot )     );
     420           0 :   }
     421           0 :   return FD_LAYOUT_FINI( l, scratch_align() );
     422           0 : }
     423             : 
     424             : static void
     425           0 : metrics_write( fd_snapin_tile_t * ctx ) {
     426           0 :   fd_accdb_flush_metrics( ctx->accdb );
     427             : 
     428           0 :   FD_MGAUGE_SET( SNAPIN, STREAM_STATE,                     (ulong)ctx->state                    );
     429           0 :   FD_MGAUGE_SET( SNAPIN, PARSE_PROGRESS_BYTES_FULL,        ctx->metrics.full_bytes_read         );
     430           0 :   FD_MGAUGE_SET( SNAPIN, PARSE_PROGRESS_BYTES_INCREMENTAL, ctx->metrics.incremental_bytes_read  );
     431           0 :   FD_MCNT_SET  ( SNAPIN, DISK_WRITTEN_BYTES,               ctx->metrics.disk_bytes_written      );
     432           0 :   FD_MGAUGE_SET( SNAPIN, ACCOUNT_PROGRESS_NEW,             ctx->metrics.accounts_loaded         );
     433           0 :   FD_MGAUGE_SET( SNAPIN, ACCOUNT_PROGRESS_REPLACED,        ctx->metrics.accounts_replaced       );
     434           0 :   FD_MGAUGE_SET( SNAPIN, ACCOUNT_PROGRESS_IGNORED,         ctx->metrics.accounts_ignored        );
     435           0 :   FD_MCNT_SET  ( SNAPIN, ACCOUNT_PROCESSED,       ctx->metrics.total_accounts_processed        );
     436           0 :   FD_MCNT_SET  ( SNAPIN, ACCOUNT_BATCH_PROCESSED, ctx->metrics.total_account_batches_processed );
     437           0 : }
     438             : 
     439             : /* verify_slot_deltas_with_slot_history verifies the 'SlotHistory'
     440             :    sysvar account (data, data_len) after loading a snapshot.  Returns 0
     441             :    if verification passed, -1 if not. */
     442             : 
     443             : static int
     444             : verify_slot_deltas_with_slot_history( fd_snapin_tile_t * ctx,
     445             :                                       uchar const *      data,
     446           0 :                                       ulong              data_len ) {
     447           0 :   if( FD_UNLIKELY( data_len!=FD_SYSVAR_SLOT_HISTORY_BINCODE_SZ ) ) {
     448           0 :     FD_LOG_WARNING(( "SlotHistory sysvar account data size is %lu, expected %lu", data_len, FD_SYSVAR_SLOT_HISTORY_BINCODE_SZ ));
     449           0 :     return -1;
     450           0 :   }
     451             : 
     452           0 :   fd_slot_history_view_t view[1];
     453           0 :   if( FD_UNLIKELY( !fd_sysvar_slot_history_view( view, data, data_len ) ) ) {
     454           0 :     FD_LOG_WARNING(( "SlotHistory sysvar account data is corrupt" ));
     455           0 :     return -1;
     456           0 :   }
     457             : 
     458             :   /* Sanity checks for slot history:
     459             :      https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L586 */
     460             : 
     461           0 :   ulong newest_slot = view->next_slot - 1UL;
     462           0 :   if( FD_UNLIKELY( newest_slot!=ctx->lead.bank_slot ) ) {
     463             :     /* VerifySlotHistoryError::InvalidNewestSlot
     464             :        https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L621 */
     465           0 :     FD_LOG_WARNING(( "SlotHistory sysvar has an invalid newest slot: %lu != bank slot: %lu", newest_slot, ctx->lead.bank_slot ));
     466           0 :     return -1;
     467           0 :   }
     468             : 
     469           0 :   if( FD_UNLIKELY( view->bits_len!=FD_SLOT_HISTORY_MAX_ENTRIES ) ) {
     470             :     /* VerifySlotHistoryError::InvalidNumEntries
     471             :        https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L625 */
     472           0 :     FD_LOG_WARNING(( "SlotHistory sysvar has invalid number of entries: %lu != expected: %lu", view->bits_len, FD_SLOT_HISTORY_MAX_ENTRIES ));
     473           0 :     return -1;
     474           0 :   }
     475             : 
     476             :   /* Stricter than Agave, which only checks bits_len (the bv crate
     477             :      merely requires bits_len<=blocks_len*64).  An exact block count is
     478             :      what makes fd_sysvar_slot_history_find_slot's (slot/64)%blocks_len
     479             :      indexing agree with Agave's bits.get(slot%MAX_ENTRIES); the runtime
     480             :      always writes exactly this many blocks. */
     481           0 :   if( FD_UNLIKELY( view->blocks_len!=FD_SLOT_HISTORY_MAX_ENTRIES/64UL ) ) {
     482           0 :     FD_LOG_WARNING(( "SlotHistory sysvar has invalid bitvec block count: %lu != expected: %lu", view->blocks_len, FD_SLOT_HISTORY_MAX_ENTRIES/64UL ));
     483           0 :     return -1;
     484           0 :   }
     485             : 
     486             :   /* All slots in slot deltas should be present in the slot history */
     487           0 :   fd_slot_delta_slot_set_t slot_set = fd_slot_delta_parser_slot_set( ctx->lead.slot_delta_parser );
     488           0 :   for( ulong i=0UL; i<slot_set.ele_cnt; i++ ) {
     489           0 :     ulong slot = slot_set.pool[ i ].slot;
     490           0 :     if( FD_UNLIKELY( fd_sysvar_slot_history_find_slot( view, slot )!=FD_SLOT_HISTORY_SLOT_FOUND ) ) {
     491             :       /* VerifySlotDeltasError::SlotNotFoundInHistory
     492             :          https://github.com/anza-xyz/agave/blob/v3.1.8/snapshots/src/error.rs#L144
     493             :          https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L593 */
     494           0 :       FD_LOG_WARNING(( "slot %lu missing from SlotHistory sysvar account", slot ));
     495           0 :       return -1;
     496           0 :     }
     497           0 :   }
     498             : 
     499           0 :   ulong scan_cnt  = fd_ulong_min( view->next_slot, FD_SLOT_HISTORY_MAX_ENTRIES );
     500           0 :   ulong found_cnt = 0UL;
     501           0 :   for( ulong i=0UL; i<scan_cnt && found_cnt<FD_SLOT_DELTA_MAX_ENTRIES; i++ ) {
     502           0 :     ulong slot = newest_slot - i;
     503           0 :     if( FD_UNLIKELY( fd_sysvar_slot_history_find_slot( view, slot )!=FD_SLOT_HISTORY_SLOT_FOUND ) ) continue;
     504           0 :     found_cnt++;
     505           0 :     if( FD_UNLIKELY( slot_set_ele_query( slot_set.map, &slot, NULL, slot_set.pool )==NULL ) ) {
     506             :       /* VerifySlotDeltasError::SlotNotFoundInDeltas
     507             :          https://github.com/anza-xyz/agave/blob/v3.1.8/snapshots/src/error.rs#L147
     508             :          https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L609 */
     509           0 :       FD_LOG_WARNING(( "slot %lu missing from slot deltas but present in SlotHistory", slot ));
     510           0 :       return -1;
     511           0 :     }
     512           0 :   }
     513             : 
     514           0 :   return 0;
     515           0 : }
     516             : 
     517             : /* Sysvar accounts checked by verify_sysvars.  Agave requires Rent at
     518             :    restore; Firedancer also needs Clock and SlotHistory. */
     519             : 
     520             : struct snapin_sysvar {
     521             :   fd_pubkey_t const * id;
     522             :   char const *        name;
     523             :   int                 required;
     524             : };
     525             : typedef struct snapin_sysvar snapin_sysvar_t;
     526             : 
     527             : static snapin_sysvar_t const snapin_sysvar_tbl[ FD_SYSVAR_CACHE_ENTRY_CNT ] = {
     528             :   [ FD_SYSVAR_clock_IDX             ] = { &fd_sysvar_clock_id,               "Clock",             1 },
     529             :   [ FD_SYSVAR_epoch_rewards_IDX     ] = { &fd_sysvar_epoch_rewards_id,       "EpochRewards",      0 },
     530             :   [ FD_SYSVAR_epoch_schedule_IDX    ] = { &fd_sysvar_epoch_schedule_id,      "EpochSchedule",     0 },
     531             :   [ FD_SYSVAR_last_restart_slot_IDX ] = { &fd_sysvar_last_restart_slot_id,   "LastRestartSlot",   0 },
     532             :   [ FD_SYSVAR_recent_hashes_IDX     ] = { &fd_sysvar_recent_block_hashes_id, "RecentBlockhashes", 0 },
     533             :   [ FD_SYSVAR_rent_IDX              ] = { &fd_sysvar_rent_id,                "Rent",              1 },
     534             :   [ FD_SYSVAR_slot_hashes_IDX       ] = { &fd_sysvar_slot_hashes_id,         "SlotHashes",        0 },
     535             :   [ FD_SYSVAR_stake_history_IDX     ] = { &fd_sysvar_stake_history_id,       "StakeHistory",      0 },
     536             : };
     537             : 
     538             : /* Rent::try_minimum_balance bounds
     539             :    https://github.com/anza-xyz/solana-sdk/blob/rent%40v4.4.0/rent/src/lib.rs#L187-L204 */
     540             : 
     541             : #define SNAPIN_RENT_MAX_LAMPORTS_PER_BYTE_THRESHOLD_1 (1759197129867UL)
     542             : #define SNAPIN_RENT_MAX_LAMPORTS_PER_BYTE_THRESHOLD_2 ( 879598564933UL)
     543             : 
     544             : static int
     545           0 : verify_rent( fd_sysvar_cache_t const * cache ) {
     546           0 :   fd_rent_t rent[1];
     547           0 :   fd_sysvar_cache_rent_read( cache, rent ); /* required */
     548           0 :   ulong threshold_bits = fd_dblbits( rent->exemption_threshold );
     549           0 :   if( FD_UNLIKELY( ( threshold_bits==fd_dblbits( 1.0 ) && rent->lamports_per_uint8_year>SNAPIN_RENT_MAX_LAMPORTS_PER_BYTE_THRESHOLD_1 ) ||
     550           0 :                    ( threshold_bits==fd_dblbits( 2.0 ) && rent->lamports_per_uint8_year>SNAPIN_RENT_MAX_LAMPORTS_PER_BYTE_THRESHOLD_2 ) ) ) {
     551           0 :     FD_LOG_WARNING(( "Rent sysvar lamports_per_byte %lu overflows minimum_balance for exemption_threshold %g",
     552           0 :                      rent->lamports_per_uint8_year, rent->exemption_threshold ));
     553           0 :     return -1;
     554           0 :   }
     555           0 :   return 0;
     556           0 : }
     557             : 
     558             : /* fd_sysvar_slot_hashes_update needs the full-size account */
     559             : 
     560             : static int
     561             : verify_slot_hashes( fd_sysvar_cache_t const * cache,
     562           0 :                     ulong                     data_len ) {
     563           0 :   if( FD_UNLIKELY( fd_sysvar_cache_slot_hashes_is_valid( cache ) && data_len<FD_SYSVAR_SLOT_HASHES_BINCODE_SZ ) ) {
     564           0 :     FD_LOG_WARNING(( "SlotHashes sysvar account data size is %lu, expected at least %lu",
     565           0 :                      data_len, FD_SYSVAR_SLOT_HASHES_BINCODE_SZ ));
     566           0 :     return -1;
     567           0 :   }
     568           0 :   return 0;
     569           0 : }
     570             : 
     571             : /* An active EpochRewards sysvar drives fd_rewards_recalculate_partitioned_rewards
     572             :    at boot; check what that path asserts. */
     573             : 
     574             : static int
     575             : verify_epoch_rewards( fd_snapin_tile_t const *  ctx,
     576           0 :                       fd_sysvar_cache_t const * cache ) {
     577           0 :   fd_sysvar_epoch_rewards_t rewards[1];
     578           0 :   if( !fd_sysvar_cache_epoch_rewards_read( cache, rewards ) || !rewards->active ) return 0;
     579             : 
     580             :   /* https://github.com/anza-xyz/agave/blob/v4.4.0-alpha.4/runtime/src/bank/partitioned_epoch_rewards/sysvar.rs#L117 */
     581           0 :   if( FD_UNLIKELY( rewards->distributed_rewards>rewards->total_rewards ) ) {
     582           0 :     FD_LOG_WARNING(( "EpochRewards sysvar distributed rewards %lu exceed total rewards %lu",
     583           0 :                      rewards->distributed_rewards, rewards->total_rewards ));
     584           0 :     return -1;
     585           0 :   }
     586             :   /* Bounds asserted by fd_stake_rewards_init and
     587             :      fd_distribute_partitioned_epoch_rewards */
     588           0 :   ulong epoch_slot_cnt = fd_epoch_slot_cnt( &ctx->lead.epoch_schedule, ctx->lead.epoch );
     589           0 :   if( FD_UNLIKELY( !rewards->num_partitions ||
     590           0 :                    rewards->num_partitions>MAX_PARTITIONS_PER_EPOCH ||
     591           0 :                    rewards->num_partitions>=epoch_slot_cnt ) ) {
     592           0 :     FD_LOG_WARNING(( "EpochRewards sysvar has invalid partition count %lu (epoch has %lu slots)",
     593           0 :                      rewards->num_partitions, epoch_slot_cnt ));
     594           0 :     return -1;
     595           0 :   }
     596           0 :   if( FD_UNLIKELY( rewards->distribution_starting_block_height>ULONG_MAX-rewards->num_partitions ) ) {
     597           0 :     FD_LOG_WARNING(( "EpochRewards sysvar distribution starting block height %lu overflows with %lu partitions",
     598           0 :                      rewards->distribution_starting_block_height, rewards->num_partitions ));
     599           0 :     return -1;
     600           0 :   }
     601             :   /* read_stake_history in fd_rewards.c aborts without it */
     602           0 :   if( FD_UNLIKELY( !fd_sysvar_cache_stake_history_is_valid( cache ) ) ) {
     603           0 :     FD_LOG_WARNING(( "EpochRewards sysvar is active but the StakeHistory sysvar account is not present" ));
     604           0 :     return -1;
     605           0 :   }
     606           0 :   return 0;
     607           0 : }
     608             : 
     609             : /* read_sysvar reads a sysvar account into ctx->staged.data.  Returns 1
     610             :    if present, 0 if absent and optional, -1 if absent and required or
     611             :    not owned by the sysvar program. */
     612             : 
     613             : static int
     614             : read_sysvar( fd_snapin_tile_t *      ctx,
     615             :              fd_accdb_fork_id_t      fork_id,
     616             :              snapin_sysvar_t const * sysvar,
     617             :              ulong *                 lamports,
     618           0 :              ulong *                 data_len ) {
     619           0 :   int   executable;
     620           0 :   uchar owner[ 32UL ];
     621           0 :   int   source = fd_accdb_read_one_nocache( ctx->accdb, fork_id, sysvar->id->uc,
     622           0 :                                             lamports, &executable, owner,
     623           0 :                                             ctx->staged.data, data_len );
     624           0 :   if( source==FD_ACCDB_READ_ONE_NOCACHE_MISS ) {
     625           0 :     if( FD_UNLIKELY( sysvar->required ) ) {
     626           0 :       FD_LOG_WARNING(( "%s sysvar account was not present in the accounts database", sysvar->name ));
     627           0 :       return -1;
     628           0 :     }
     629           0 :     return 0;
     630           0 :   }
     631             : 
     632           0 :   if( FD_UNLIKELY( !fd_memeq( owner, fd_sysvar_owner_id.uc, sizeof(fd_pubkey_t) ) ) ) {
     633           0 :     FD_BASE58_ENCODE_32_BYTES( owner, owner_b58 );
     634           0 :     FD_LOG_WARNING(( "%s sysvar owner is invalid: %s != sysvar_owner_id", sysvar->name, owner_b58 ));
     635           0 :     return -1;
     636           0 :   }
     637           0 :   return 1;
     638           0 : }
     639             : 
     640             : /* verify_sysvars reads the sysvar accounts back from the accounts
     641             :    database and checks they decode and satisfy the invariants replay
     642             :    asserts at boot.  Call after all FINI acks.  Returns 0 on success,
     643             :    -1 on failure. */
     644             : 
     645             : static int
     646           0 : verify_sysvars( fd_snapin_tile_t * ctx ) {
     647           0 :   fd_accdb_fork_id_t fork_id = ctx->full ? ctx->lead.accdb_root_fork_id
     648           0 :                                          : ctx->lead.accdb_incr_fork_id;
     649           0 :   fd_sysvar_cache_t * cache = fd_sysvar_cache_join( fd_sysvar_cache_new( ctx->lead.sysvar_cache ) );
     650             : 
     651           0 :   ulong data_lens[ FD_SYSVAR_CACHE_ENTRY_CNT ] = {0}; /* untruncated */
     652             : 
     653           0 :   for( ulong i=0UL; i<FD_SYSVAR_CACHE_ENTRY_CNT; i++ ) {
     654           0 :     snapin_sysvar_t const * sysvar = &snapin_sysvar_tbl[ i ];
     655             : 
     656           0 :     ulong lamports;
     657           0 :     ulong data_len;
     658           0 :     int   found = read_sysvar( ctx, fork_id, sysvar, &lamports, &data_len );
     659           0 :     if( FD_UNLIKELY( found<0 ) ) return -1;
     660           0 :     if( !found ) continue;
     661             : 
     662             :     /* Same decode as fd_sysvar_cache_restore at boot */
     663           0 :     fd_sysvar_cache_restore_one( cache, sysvar->id, lamports, ctx->staged.data, data_len );
     664           0 :     ulong cached_sz;
     665           0 :     if( FD_UNLIKELY( !fd_sysvar_cache_data_query( cache, sysvar->id->uc, &cached_sz ) ) ) {
     666           0 :       FD_LOG_WARNING(( "%s sysvar account data is corrupt (data_len=%lu)", sysvar->name, data_len ));
     667           0 :       return -1;
     668           0 :     }
     669             : 
     670           0 :     data_lens[ i ] = data_len;
     671           0 :   }
     672             : 
     673           0 :   if( FD_UNLIKELY( verify_rent         ( cache )                                              ) ) return -1;
     674           0 :   if( FD_UNLIKELY( verify_slot_hashes  ( cache,      data_lens[ FD_SYSVAR_slot_hashes_IDX ] ) ) ) return -1;
     675           0 :   if( FD_UNLIKELY( verify_epoch_rewards( ctx, cache )                                         ) ) return -1;
     676             : 
     677           0 :   snapin_sysvar_t const slot_history = { &fd_sysvar_slot_history_id, "SlotHistory", 1 };
     678           0 :   ulong lamports;
     679           0 :   ulong data_len;
     680           0 :   if( FD_UNLIKELY( read_sysvar( ctx, fork_id, &slot_history, &lamports, &data_len )<0 ) ) return -1;
     681           0 :   return verify_slot_deltas_with_slot_history( ctx, ctx->staged.data, data_len );
     682           0 : }
     683             : 
     684             : /* verification of epoch stakes from manifest
     685             :    https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L632 */
     686             : static int
     687           0 : verify_epoch_stakes( fd_snapshot_manifest_t const * manifest ) {
     688           0 :   fd_epoch_schedule_t epoch_schedule = (fd_epoch_schedule_t){
     689           0 :     .slots_per_epoch             = manifest->epoch_schedule_params.slots_per_epoch,
     690           0 :     .leader_schedule_slot_offset = manifest->epoch_schedule_params.leader_schedule_slot_offset,
     691           0 :     .warmup                      = manifest->epoch_schedule_params.warmup,
     692           0 :     .first_normal_epoch          = manifest->epoch_schedule_params.first_normal_epoch,
     693           0 :     .first_normal_slot           = manifest->epoch_schedule_params.first_normal_slot,
     694           0 :   };
     695             : 
     696           0 :   ulong min_required_epoch = fd_slot_to_epoch( &epoch_schedule, manifest->slot, NULL );
     697           0 :   ulong max_required_epoch = fd_slot_to_leader_schedule_epoch( &epoch_schedule, manifest->slot );
     698             : 
     699             :   /* ensure all required epochs are present in epoch stakes */
     700           0 :   for( ulong i=min_required_epoch; i<=max_required_epoch; i++ ) {
     701           0 :     int found = 0;
     702           0 :     for( ulong j=0UL; j<FD_RUNTIME_MANIFEST_EPOCH_STAKES_LEN; j++ ) {
     703           0 :       if( manifest->epoch_stakes[j].epoch==i ) {
     704           0 :         found = 1;
     705           0 :         break;
     706           0 :       }
     707           0 :     }
     708             : 
     709           0 :     if( FD_UNLIKELY( !found ) ) {
     710             :       /* VerifyEpochStakesError::StakesNotFound
     711             :          https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L667 */
     712           0 :       FD_LOG_WARNING(( "stakes not found for epoch %lu in manifest", i ));
     713           0 :       return -1;
     714           0 :     }
     715           0 :   }
     716             : 
     717           0 :   return 0;
     718           0 : }
     719             : 
     720             : static int
     721             : verify_slot_deltas_with_bank_slot( fd_snapin_tile_t * ctx,
     722           0 :                                    ulong              bank_slot ) {
     723           0 :   fd_slot_delta_slot_set_t slot_set = fd_slot_delta_parser_slot_set( ctx->lead.slot_delta_parser );
     724           0 :   for( ulong i=0UL; i<slot_set.ele_cnt; i++ ) {
     725           0 :     ulong slot = slot_set.pool[ i ].slot;
     726             :     /* VerifySlotDeltasError::SlotGreaterThanMaxRoot
     727             :        https://github.com/anza-xyz/agave/blob/v3.1.8/snapshots/src/error.rs#L138
     728             :        https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L550 */
     729           0 :     if( FD_UNLIKELY( slot>bank_slot ) ) {
     730           0 :       FD_LOG_WARNING(( "entry slot %lu is greater than bank slot %lu", slot, bank_slot ));
     731           0 :       return -1;
     732           0 :     }
     733           0 :   }
     734           0 :   return 0;
     735           0 : }
     736             : 
     737             : static int
     738             : verify_bank_hash( fd_snapin_tile_t const *       ctx,
     739           0 :                   fd_snapshot_manifest_t const * manifest ) {
     740           0 :   if( FD_UNLIKELY( manifest->blockhashes_len==0UL ) ) {
     741           0 :     FD_LOG_WARNING(( "%s manifest for epoch %lu and slot %lu has no blockhashes",
     742           0 :                      ctx->full?"full":"incr", ctx->lead.epoch, manifest->slot ));
     743           0 :     return -1;
     744           0 :   }
     745             : 
     746           0 :   if( FD_UNLIKELY( !manifest->has_accounts_lthash ) ) {
     747           0 :     FD_LOG_WARNING(( "%s manifest for epoch %lu and slot %lu is missing accounts lthash",
     748           0 :                      ctx->full?"full":"incr", ctx->lead.epoch, manifest->slot ));
     749           0 :     return -1;
     750           0 :   }
     751             : 
     752             :   /* find the last blockhash */
     753           0 :   ulong max_hash_idx = 0UL;
     754           0 :   ulong last_bh_idx  = 0UL;
     755           0 :   for( ulong i=0UL; i<manifest->blockhashes_len; i++ ) {
     756           0 :     if( FD_LIKELY( manifest->blockhashes[ i ].hash_index > max_hash_idx ) ) {
     757           0 :       max_hash_idx = manifest->blockhashes[ i ].hash_index;
     758           0 :       last_bh_idx  = i;
     759           0 :     }
     760           0 :   }
     761             : 
     762             :   /* fd_lthash_value_t is aligned to 64B but the accounts_lthash in the
     763             :      manifest may not be because its simply a uchar array.  Copy is
     764             :      needed to avoid undefined behavior. */
     765           0 :   fd_lthash_value_t accounts_lthash[ 1UL ];
     766           0 :   fd_memcpy( accounts_lthash, manifest->accounts_lthash, sizeof(fd_lthash_value_t) );
     767             : 
     768           0 :   fd_hash_t const * parent_bank_hash = (fd_hash_t const *)fd_type_pun_const( manifest->parent_bank_hash );
     769           0 :   fd_hash_t const * last_blockhash   = (fd_hash_t const *)fd_type_pun_const( manifest->blockhashes[ last_bh_idx ].hash );
     770           0 :   fd_hash_t         computed_bank_hash[ 1UL ];
     771           0 :   fd_hashes_hash_bank( accounts_lthash, parent_bank_hash, last_blockhash, manifest->signature_count, computed_bank_hash );
     772           0 :   fd_hashes_apply_hard_forks(
     773           0 :       computed_bank_hash,
     774           0 :       manifest->slot,
     775           0 :       manifest->parent_slot,
     776           0 :       manifest->hard_forks,
     777           0 :       manifest->hard_fork_cnt );
     778             : 
     779           0 :   if( FD_UNLIKELY( memcmp( computed_bank_hash, manifest->bank_hash, FD_HASH_FOOTPRINT ) ) ) {
     780           0 :     FD_BASE58_ENCODE_32_BYTES( computed_bank_hash->hash, computed_bank_hash_enc );
     781           0 :     FD_BASE58_ENCODE_32_BYTES( manifest->bank_hash, manifest_bank_hash_enc );
     782           0 :     FD_LOG_WARNING(( "%s manifest for epoch %lu and slot %lu bank hash verification failed: computed %s does not match manifest %s",
     783           0 :                      ctx->full?"full":"incr", ctx->lead.epoch, manifest->slot,
     784           0 :                      computed_bank_hash_enc, manifest_bank_hash_enc ));
     785           0 :     return -1;
     786           0 :   }
     787             : 
     788           0 :   return 0;
     789           0 : }
     790             : 
     791             : static inline void
     792           0 : clear_control_barrier( fd_snapin_tile_t * ctx ) {
     793           0 :   ctx->pending_control = ULONG_MAX;
     794           0 :   fd_memset( ctx->control_seen, 0, sizeof(ctx->control_seen) );
     795           0 : }
     796             : 
     797             : static void
     798             : transition_malformed( fd_snapin_tile_t *  ctx,
     799           0 :                       fd_stem_context_t * stem ) {
     800           0 :   if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_ERROR ) ) return;
     801           0 :   ctx->state = FD_SNAPSHOT_STATE_ERROR;
     802           0 :   fd_stem_publish( stem, ctx->ct_out.idx, FD_SNAPSHOT_MSG_CTRL_ERROR, 0UL, 0UL, 0UL, 0UL, 0UL );
     803           0 : }
     804             : 
     805             : /* Writes (write!=0) or reads the staged groups of slot delta slot_idx
     806             :    between the in-memory buffer and the txncache.  No-op if slot_idx is
     807             :    ULONG_MAX. */
     808             : 
     809             : static void
     810             : txncache_staging_row_io( fd_snapin_tile_t * ctx,
     811             :                          ulong              slot_idx,
     812           0 :                          int                write ) {
     813           0 :   if( FD_UNLIKELY( slot_idx==ULONG_MAX ) ) return;
     814           0 :   ulong sz = fd_ulong_align_up( ctx->lead.txncache_slots[ slot_idx ].group_cnt*sizeof(blockhash_group_t), FD_TXNCACHE_SNAPIN_STAGE_ALIGN );
     815           0 :   if( FD_LIKELY( sz ) ) fd_txncache_snapin_stage( ctx->lead.txncache, slot_idx*txncache_staging_row_sz( ctx->lead.txncache_max_groups_per_slot ), ctx->lead.blockhash_groups, sz, write );
     816           0 : }
     817             : 
     818             : static void
     819           0 : txncache_staging_reset( fd_snapin_tile_t * ctx ) {
     820           0 :   ctx->lead.blockhash_groups_cnt            = 0UL;
     821           0 :   ctx->lead.recent_groups_len               = 0UL;
     822           0 :   ctx->lead.txncache_slots_len              = 0UL;
     823           0 :   ctx->lead.txncache_current_slot_entry_cnt = 0UL;
     824           0 :   ctx->lead.txncache_current_slot_idx       = ULONG_MAX;
     825           0 :   ctx->lead.txncache_current_slot_group_cnt = 0UL;
     826           0 : }
     827             : 
     828             : static ulong
     829             : txncache_staging_slot_begin( fd_snapin_tile_t * ctx,
     830           0 :                              ulong              slot ) {
     831           0 :   txncache_staging_row_io( ctx, ctx->lead.txncache_current_slot_idx, 1 );
     832             : 
     833           0 :   ulong candidate_idx;
     834           0 :   if( FD_LIKELY( ctx->lead.txncache_slots_len<FD_TXNCACHE_MAX_SLOT_DELTAS ) ) {
     835           0 :     candidate_idx = ctx->lead.txncache_slots_len++;
     836           0 :   } else {
     837           0 :     candidate_idx = 0UL;
     838           0 :     for( ulong i=1UL; i<FD_TXNCACHE_MAX_SLOT_DELTAS; i++ ) {
     839           0 :       if( ctx->lead.txncache_slots[ i ].slot<ctx->lead.txncache_slots[ candidate_idx ].slot ) candidate_idx = i;
     840           0 :     }
     841           0 :     if( FD_UNLIKELY( slot<ctx->lead.txncache_slots[ candidate_idx ].slot ) ) candidate_idx = ULONG_MAX;
     842           0 :   }
     843             : 
     844           0 :   if( FD_LIKELY( candidate_idx!=ULONG_MAX ) ) {
     845           0 :     ctx->lead.txncache_slots[ candidate_idx ].slot       = slot;
     846           0 :     ctx->lead.txncache_slots[ candidate_idx ].entry_cnt  = 0UL;
     847           0 :     ctx->lead.txncache_slots[ candidate_idx ].group_cnt  = 0UL;
     848           0 :   }
     849           0 :   ctx->lead.txncache_current_slot_idx       = candidate_idx;
     850           0 :   ctx->lead.txncache_current_slot_entry_cnt = 0UL;
     851           0 :   ctx->lead.txncache_current_slot_group_cnt = 0UL;
     852           0 :   return candidate_idx;
     853           0 : }
     854             : 
     855             : static int
     856             : txncache_staging_group_begin( fd_snapin_tile_t * ctx,
     857             :                               uchar const *      blockhash,
     858           0 :                               ulong              txnhash_offset ) {
     859           0 :   if( FD_UNLIKELY( ctx->lead.txncache_current_slot_group_cnt>=ctx->lead.txncache_max_groups_per_slot ) ) return -1;
     860           0 :   ctx->lead.txncache_current_slot_group_cnt++;
     861           0 :   ctx->lead.blockhash_groups_cnt++;
     862             : 
     863           0 :   ulong slot_idx = ctx->lead.txncache_current_slot_idx;
     864           0 :   if( FD_UNLIKELY( slot_idx==ULONG_MAX ) ) return 0;
     865             : 
     866           0 :   FD_TEST( slot_idx<ctx->lead.txncache_slots_len );
     867           0 :   txncache_staging_slot_t * staging_slot = &ctx->lead.txncache_slots[ slot_idx ];
     868           0 :   FD_TEST( staging_slot->group_cnt<ctx->lead.txncache_max_groups_per_slot );
     869           0 :   blockhash_group_t * group = &ctx->lead.blockhash_groups[ staging_slot->group_cnt ];
     870           0 :   memcpy( group->blockhash, blockhash, 32UL );
     871           0 :   group->txnhash_offset     = (uint)txnhash_offset;
     872           0 :   group->txncache_entry_cnt = 0U;
     873           0 :   staging_slot->group_cnt++;
     874           0 :   return 0;
     875           0 : }
     876             : 
     877             : static int
     878             : txncache_staging_entry_add( fd_snapin_tile_t * ctx,
     879             :                             ulong              slot,
     880           0 :                             uchar const *      txnhash ) {
     881           0 :   if( FD_UNLIKELY( ctx->lead.txncache_current_slot_entry_cnt>=ctx->lead.txncache_max_entries_per_slot ) ) return -1;
     882           0 :   ctx->lead.txncache_current_slot_entry_cnt++;
     883             : 
     884           0 :   ulong slot_idx = ctx->lead.txncache_current_slot_idx;
     885           0 :   if( FD_UNLIKELY( slot_idx==ULONG_MAX ) ) return 0; /* discarded older delta */
     886             : 
     887           0 :   FD_TEST( slot_idx<ctx->lead.txncache_slots_len );
     888           0 :   txncache_staging_slot_t * staging_slot = &ctx->lead.txncache_slots[ slot_idx ];
     889           0 :   FD_TEST( staging_slot->slot==slot );
     890           0 :   FD_TEST( staging_slot->group_cnt );
     891           0 :   FD_TEST( staging_slot->entry_cnt<ctx->lead.txncache_max_entries_per_slot );
     892           0 :   blockhash_group_t * group = &ctx->lead.blockhash_groups[ staging_slot->group_cnt-1UL ];
     893           0 :   memcpy( ctx->lead.txncache_entries[ slot_idx*ctx->lead.txncache_max_entries_per_slot+staging_slot->entry_cnt ].txnhash, txnhash, sizeof(fd_sstxncache_hash_t) );
     894           0 :   staging_slot->entry_cnt++;
     895           0 :   group->txncache_entry_cnt++;
     896           0 :   return 0;
     897           0 : }
     898             : 
     899             : static ulong
     900             : txncache_staging_rank_slots( fd_snapin_tile_t const * ctx,
     901           0 :                              ulong                    execution_bank_i_by_staging_idx[ static FD_TXNCACHE_MAX_SLOT_DELTAS ] ) {
     902           0 :   ulong staging_slot_cnt = ctx->lead.txncache_slots_len;
     903           0 :   ulong staging_idx_by_bank_i[ FD_TXNCACHE_MAX_SLOT_DELTAS ];
     904           0 :   for( ulong staging_idx=0UL; staging_idx<staging_slot_cnt; staging_idx++ ) {
     905           0 :     ulong bank_i = staging_idx;
     906           0 :     while( bank_i>0UL && ctx->lead.txncache_slots[ staging_idx_by_bank_i[ bank_i-1UL ] ].slot<ctx->lead.txncache_slots[ staging_idx ].slot ) {
     907           0 :       staging_idx_by_bank_i[ bank_i ] = staging_idx_by_bank_i[ bank_i-1UL ];
     908           0 :       bank_i--;
     909           0 :     }
     910           0 :     staging_idx_by_bank_i[ bank_i ] = staging_idx;
     911           0 :   }
     912           0 :   for( ulong bank_i=0UL; bank_i<staging_slot_cnt; bank_i++ ) {
     913           0 :     execution_bank_i_by_staging_idx[ staging_idx_by_bank_i[ bank_i ] ] = bank_i;
     914           0 :   }
     915           0 :   return staging_slot_cnt ? ctx->lead.txncache_slots[ staging_idx_by_bank_i[ 0UL ] ].slot : ULONG_MAX;
     916           0 : }
     917             : 
     918             : static int
     919             : txncache_staging_filter_groups( fd_snapin_tile_t *           ctx,
     920             :                                 blockhash_map_t const *      blockhash_map,
     921             :                                 fd_blockhash_entry_t const * blockhash_pool,
     922           0 :                                 ulong                        snapshot_slot ) {
     923           0 :   ulong execution_bank_i_by_staging_idx[ FD_TXNCACHE_MAX_SLOT_DELTAS ];
     924           0 :   ulong newest_staged_slot = txncache_staging_rank_slots( ctx, execution_bank_i_by_staging_idx );
     925           0 :   if( FD_UNLIKELY( newest_staged_slot!=ULONG_MAX && newest_staged_slot!=snapshot_slot ) ) {
     926           0 :     FD_LOG_WARNING(( "corrupt snapshot: newest slot delta is for slot %lu, not the snapshot slot %lu", newest_staged_slot, snapshot_slot ));
     927           0 :     return 1;
     928           0 :   }
     929             : 
     930           0 :   txncache_staging_row_io( ctx, ctx->lead.txncache_current_slot_idx, 1 );
     931             : 
     932           0 :   ctx->lead.recent_groups_len = 0UL;
     933           0 :   for( ulong staging_idx=0UL; staging_idx<ctx->lead.txncache_slots_len; staging_idx++ ) {
     934           0 :     txncache_staging_slot_t const * staging_slot = &ctx->lead.txncache_slots[ staging_idx ];
     935           0 :     txncache_staging_row_io( ctx, staging_idx, 0 );
     936           0 :     blockhash_group_t const *       groups       = ctx->lead.blockhash_groups;
     937           0 :     ulong                           entry_idx    = staging_idx*ctx->lead.txncache_max_entries_per_slot;
     938             : 
     939           0 :     for( ulong i=0UL; i<staging_slot->group_cnt; i++ ) {
     940           0 :       blockhash_group_t const * group = &groups[ i ];
     941           0 :       fd_hash_t key;
     942           0 :       fd_memcpy( key.uc, group->blockhash, 32UL );
     943           0 :       fd_blockhash_entry_t const * entry = blockhash_map_ele_query_const( blockhash_map, &key, NULL, blockhash_pool );
     944           0 :       if( FD_LIKELY( entry ) ) {
     945           0 :         if( FD_UNLIKELY( ctx->lead.recent_groups_len>=FD_SNAPIN_MAX_RECENT_GROUPS ) ) {
     946           0 :           FD_LOG_WARNING(( "corrupt snapshot: more than %lu blockhash groups reference recent blockhashes", FD_SNAPIN_MAX_RECENT_GROUPS ));
     947           0 :           return -1;
     948           0 :         }
     949           0 :         recent_blockhash_group_t * recent = &ctx->lead.recent_groups[ ctx->lead.recent_groups_len++ ];
     950           0 :         recent->blockhash_bank_i   = (ulong)(entry-blockhash_pool);
     951           0 :         recent->execution_slot     = staging_slot->slot;
     952           0 :         recent->execution_bank_i   = execution_bank_i_by_staging_idx[ staging_idx ];
     953           0 :         recent->txnhash_offset     = group->txnhash_offset;
     954           0 :         recent->txncache_entry_idx = entry_idx;
     955           0 :         recent->txncache_entry_cnt = group->txncache_entry_cnt;
     956           0 :       }
     957           0 :       entry_idx += group->txncache_entry_cnt;
     958           0 :     }
     959           0 :     FD_TEST( entry_idx==staging_idx*ctx->lead.txncache_max_entries_per_slot+staging_slot->entry_cnt );
     960           0 :   }
     961           0 :   return 0;
     962           0 : }
     963             : 
     964             : static int
     965             : populate_txncache( fd_snapin_tile_t *                     ctx,
     966             :                    fd_snapshot_manifest_blockhash_t const blockhashes[ static FD_BLOCKHASHES_MAX ],
     967             :                    ulong                                  blockhashes_len,
     968           0 :                    ulong                                  snapshot_slot ) {
     969             :   /* Our txncache internally contains the fork structure for the chain,
     970             :      which we need to recreate here.  Because snapshots are only served
     971             :      for rooted slots, there is actually no forking, and the bank forks
     972             :      are just a single bank, the root, like
     973             : 
     974             :        _root
     975             : 
     976             :      But the txncache also must contain the 150 more recent banks prior
     977             :      to the root (151 rooted banks total), looking like,
     978             : 
     979             : 
     980             :        _root_150 -> _root_149 -> ... -> _root_2 -> _root_1 -> _root
     981             : 
     982             :      Our txncache is "slot agnostic" meaning there is no concept of a
     983             :      slot number in it.  It just has a fork tree structure.  So long as
     984             :      the fork tree is isomorphic to the actual bank forks, and each bank
     985             :      has the correct blockhash, it works.
     986             : 
     987             :      So the challenge is simply to create this chain of 151 forks in the
     988             :      txncache, with correct blockhashes, and then insert all the
     989             :      transactions into it.
     990             : 
     991             :      Constructing the chain of blockhashes is easy.  It is just the
     992             :      BLOCKHASH_QUEUE array in the manifest.  This array is unfortunately
     993             :      not sorted and appears in random order, but it has a hash_index
     994             :      field, starting at some arbitrary offset, so we can back out the
     995             :      blockhashes we need from this, by first finding the max hash_index
     996             :      as _max and then collecting hash entries via,
     997             : 
     998             :        _root_150 -> _root_149 -> ... -> _root_2 -> _root_1 -> _root
     999             :        _max-150  -> _max-149  -> ... -> _max-2  -> _max-1  -> _max
    1000             : 
    1001             :      Usually the index is gapless and this is exactly 151 links.  An
    1002             :      index can be missing, though: a hole (see fd_blockhashes.h), left
    1003             :      when the same blockhash was registered again and Agave's queue kept
    1004             :      only the newer index.  A hole has no blockhash of its own, so it is
    1005             :      not a link.  The chain is the live entries in descending hash_index
    1006             :      order, and the recent window is still bounded by index distance, so
    1007             :      it is the live entries within _max-150 ... _max, which may be fewer
    1008             :      than 151 links.
    1009             : 
    1010             :      Now the remaining problem is inserting transactions into this
    1011             :      chain.  Remember each transaction needs to be inserted with:
    1012             : 
    1013             :       (a) The fork ID (position of the bank in the chain) it was executed in.
    1014             :       (b) The blockhash of the bank it referenced.
    1015             : 
    1016             :     (b) is trivial to retrieve, as it's in the actual slot_deltas entry
    1017             :     in the manifest served by Agave.  But (a) is mildly annoying.  Agave
    1018             :     serves slot_deltas based on slot, so we need an additional mapping
    1019             :     from slot to position in our banks chain.  It turns out we have to
    1020             :     go to yet another structure in the manifest to retrieve this, the
    1021             :     ancestors array.  This is just an array of slot values,  so we need
    1022             :     to sort it, and line it up against our banks chain like so (shown
    1023             :     for the usual gapless case),
    1024             : 
    1025             :        _root_150  -> _root_149  -> ... -> _root_2  -> _root_1  -> _root
    1026             :        _max-150   -> _max-149   -> ... -> _max-2   -> _max-1   -> _max
    1027             :        _slots_150 -> _slots_149 -> ... -> _slots_2 -> _slots_1 -> _slots
    1028             : 
    1029             :     That is what populate does, with one shortcut.  Every rooted slot
    1030             :     with a block has a slot delta, and every rooted block registered
    1031             :     exactly one blockhash, consuming one hash_index.  Both sequences end
    1032             :     at the snapshot slot, so the slot delta of rank r (newest first)
    1033             :     executed in the block that registered hash_index _max-r, without
    1034             :     consulting the ancestors array.  That index is turned into a chain
    1035             :     position through the queue: a live index is its own link, and a
    1036             :     hole (see fd_blockhashes.h) maps to the nearest newer live link,
    1037             :     since the block that registered it shares that link's blockhash.
    1038             : 
    1039             :     From there we are done.
    1040             : 
    1041             :     The Agave status cache stores 20 bytes of each transaction hash.
    1042             :     We retrieve the offset from the slot_deltas groups and key it into
    1043             :     the bank of the referenced blockhash in our chain, checking that
    1044             :     every group for a blockhash agrees on it. */
    1045             : 
    1046           0 :   if( FD_UNLIKELY( blockhashes_len>FD_BLOCKHASHES_MAX ) ) {
    1047           0 :     FD_LOG_WARNING(( "corrupt snapshot: blockhash queue length %lu exceeds maximum %lu", blockhashes_len, FD_BLOCKHASHES_MAX ));
    1048           0 :     return 1;
    1049           0 :   }
    1050           0 :   if( FD_UNLIKELY( !blockhashes_len ) ) {
    1051           0 :     FD_LOG_WARNING(( "corrupt snapshot: blockhash queue is empty" ));
    1052           0 :     return 1;
    1053           0 :   }
    1054             : 
    1055             :   /* hash_index values may skip (see fd_blockhashes.h).  A skipped index
    1056             :      has no blockhash of its own, so it is not a link: the chain is the
    1057             :      entries in descending hash_index order, so copy them out and sort.
    1058             :      The manifest buffer is shared with other tiles, so it is not
    1059             :      reordered in place.  The newest index is seq_max, and the recent
    1060             :      window is still bounded by index distance, so it is the entries
    1061             :      within seq_max-150 ... seq_max. */
    1062             : 
    1063           0 :   fd_snapin_bank_t banks[ FD_BLOCKHASHES_MAX ];
    1064           0 :   for( ulong i=0UL; i<blockhashes_len; i++ ) {
    1065           0 :     banks[ i ].hash_index     = blockhashes[ i ].hash_index;
    1066           0 :     banks[ i ].fork_id.val    = USHORT_MAX;
    1067           0 :     banks[ i ].txnhash_offset = ULONG_MAX;
    1068           0 :     memcpy( banks[ i ].blockhash, blockhashes[ i ].hash, 32UL );
    1069           0 :   }
    1070           0 :   fd_snapin_bank_sort_newest_first_inplace( banks, blockhashes_len );
    1071             : 
    1072           0 :   ulong chain_cnt  = blockhashes_len;
    1073           0 :   ulong seq_max    = banks[ 0UL ].hash_index;
    1074           0 :   ulong recent_cnt = 0UL;
    1075           0 :   for( ulong i=0UL; i<chain_cnt; i++ ) {
    1076           0 :     if( FD_UNLIKELY( i && banks[ i ].hash_index==banks[ i-1UL ].hash_index ) ) {
    1077           0 :       FD_LOG_WARNING(( "corrupt snapshot: duplicate blockhash queue hash_index %lu", banks[ i ].hash_index ));
    1078           0 :       return 1;
    1079           0 :     }
    1080           0 :     if( FD_LIKELY( seq_max-banks[ i ].hash_index<151UL ) ) recent_cnt++;
    1081           0 :   }
    1082           0 :   FD_TEST( chain_cnt==blockhashes_len );
    1083           0 :   FD_TEST( recent_cnt<=151UL );
    1084             : 
    1085           0 :   ulong chain_len = recent_cnt;
    1086             : 
    1087             :   /* Chain position of the block that registered each hash_index, by
    1088             :      slot delta rank r (hash_index seq_max-r): the newest link whose
    1089             :      hash_index is at most seq_max-r.  For a present index that is its
    1090             :      own link; for a skipped one it is the nearest newer link, since the
    1091             :      block that registered it shares that link's blockhash.  Ranks past
    1092             :      the oldest entry cannot reference a recent blockhash; they map past
    1093             :      the chain so that the age check below rejects them if they try. */
    1094             : 
    1095           0 :   ulong seq_span = seq_max - banks[ chain_cnt-1UL ].hash_index; /* oldest entry is the last link */
    1096           0 :   ulong exec_pos_by_rank[ FD_TXNCACHE_MAX_SLOT_DELTAS ];
    1097           0 :   ulong link = 0UL;
    1098           0 :   for( ulong r=0UL; r<FD_TXNCACHE_MAX_SLOT_DELTAS; r++ ) {
    1099           0 :     if( FD_UNLIKELY( r>seq_span ) ) { exec_pos_by_rank[ r ] = chain_cnt; continue; }
    1100           0 :     ulong target = seq_max-r; /* in [oldest,seq_max], so the walk stops before chain_cnt */
    1101           0 :     while( banks[ link ].hash_index>target ) link++;
    1102           0 :     exec_pos_by_rank[ r ] = fd_ulong_if( banks[ link ].hash_index==target, link, link-1UL ); /* link>0 when skipped: seq_max is link 0 */
    1103           0 :   }
    1104             : 
    1105             :   /* Now we need a hashset of just the 151 most recent blockhashes,
    1106             :      anything else is a nonce transaction which we do not insert, or an
    1107             :      already expired transaction which can also be discarded. */
    1108             : 
    1109           0 :   uchar __attribute__((aligned(alignof(blockhash_map_t)))) _map[ blockhash_map_footprint( 1024UL ) ];
    1110           0 :   blockhash_map_t * blockhash_map = blockhash_map_join( blockhash_map_new( _map, 1024UL, ctx->lead.seed ) );
    1111           0 :   if( FD_UNLIKELY( !blockhash_map ) ) FD_LOG_ERR(( "failed to create blockhash map" ));
    1112             : 
    1113           0 :   fd_blockhash_entry_t blockhash_pool[ 151UL ];
    1114           0 :   for( ulong i=0UL; i<chain_len; i++ ) {
    1115           0 :     fd_memcpy( blockhash_pool[ i ].blockhash.uc, banks[ i ].blockhash, 32UL );
    1116             : 
    1117           0 :     if( FD_UNLIKELY( blockhash_map_ele_query_const( blockhash_map, &blockhash_pool[ i ].blockhash, NULL, blockhash_pool ) ) ) {
    1118           0 :       FD_BASE58_ENCODE_32_BYTES( banks[ i ].blockhash, blockhash_b58 );
    1119           0 :       FD_LOG_WARNING(( "corrupt snapshot: duplicate blockhash %s in 151 most recent blockhashes", blockhash_b58 ));
    1120           0 :       return 1;
    1121           0 :     }
    1122             : 
    1123           0 :     blockhash_map_ele_insert( blockhash_map, &blockhash_pool[ i ], blockhash_pool );
    1124           0 :   }
    1125             : 
    1126             :   /* The staged groups live in the txncache, so filtering must finish
    1127             :      before the first txncache insert. */
    1128           0 :   if( FD_UNLIKELY( txncache_staging_filter_groups( ctx, blockhash_map, blockhash_pool, snapshot_slot ) ) ) return 1;
    1129           0 :   for( ulong i=0UL; i<ctx->lead.recent_groups_len; i++ ) {
    1130           0 :     recent_blockhash_group_t * group = &ctx->lead.recent_groups[ i ];
    1131           0 :     FD_TEST( group->execution_bank_i<FD_TXNCACHE_MAX_SLOT_DELTAS );
    1132           0 :     group->execution_bank_i = exec_pos_by_rank[ group->execution_bank_i ];
    1133           0 :   }
    1134             : 
    1135             :   /* Now load the blockhash offsets for these blockhashes ... */
    1136           0 :   if( FD_UNLIKELY( !ctx->lead.blockhash_groups_cnt ) ) {
    1137           0 :     fd_slot_delta_slot_set_t ss = fd_slot_delta_parser_slot_set( ctx->lead.slot_delta_parser );
    1138             :     /* Rooted slots with no groups represent an empty status cache.
    1139             :        No restored entry needs a hash offset, so finalization uses zero. */
    1140           0 :     if( FD_UNLIKELY( !ss.ele_cnt ) ) {
    1141           0 :       FD_LOG_WARNING(( "corrupt snapshot: no blockhash offsets found (rooted_slots=%lu)", ss.ele_cnt ));
    1142           0 :       return 1;
    1143           0 :     }
    1144           0 :     FD_LOG_WARNING(( "status cache has no blockhash groups (rooted_slots=%lu); defaulting transaction hash offsets to zero", ss.ele_cnt ));
    1145           0 :   }
    1146           0 :   for( ulong i=0UL; i<ctx->lead.recent_groups_len; i++ ) {
    1147           0 :     recent_blockhash_group_t const * group = &ctx->lead.recent_groups[ i ];
    1148           0 :     ulong blockhash_bank_i = group->blockhash_bank_i;
    1149           0 :     FD_TEST( blockhash_bank_i<chain_len );
    1150             : 
    1151           0 :     if( FD_UNLIKELY( banks[ blockhash_bank_i ].txnhash_offset!=ULONG_MAX && banks[ blockhash_bank_i ].txnhash_offset!=group->txnhash_offset ) ) {
    1152           0 :       FD_BASE58_ENCODE_32_BYTES( banks[ blockhash_bank_i ].blockhash, blockhash_b58 );
    1153           0 :       FD_LOG_WARNING(( "corrupt snapshot: conflicting txnhash offsets for blockhash %s", blockhash_b58 ));
    1154           0 :       return 1;
    1155           0 :     }
    1156             : 
    1157           0 :     if( FD_UNLIKELY( group->txncache_entry_cnt && blockhash_bank_i<=group->execution_bank_i ) ) {
    1158           0 :       FD_BASE58_ENCODE_32_BYTES( banks[ blockhash_bank_i ].blockhash, blockhash_b58 );
    1159           0 :       FD_LOG_WARNING(( "corrupt snapshot: %lu status cache entries of slot %lu reference blockhash %s, which is not older than that slot", group->txncache_entry_cnt, group->execution_slot, blockhash_b58 ));
    1160           0 :       return 1;
    1161           0 :     }
    1162             : 
    1163           0 :     banks[ blockhash_bank_i ].txnhash_offset = group->txnhash_offset;
    1164           0 :   }
    1165             : 
    1166             :   /* Construct the linear fork chain in the txncache. */
    1167             : 
    1168           0 :   fd_txncache_fork_id_t parent = { .val = USHORT_MAX };
    1169           0 :   for( ulong i=0UL; i<chain_len; i++ ) banks[ chain_len-1UL-i ].fork_id = parent = fd_txncache_attach_child( ctx->lead.txncache, parent );
    1170           0 :   for( ulong i=0UL; i<chain_len; i++ ) fd_txncache_attach_blockhash( ctx->lead.txncache, banks[ i ].fork_id, banks[ i ].blockhash );
    1171             : 
    1172           0 :   for( ulong i=0UL; i<ctx->lead.recent_groups_len; i++ ) {
    1173           0 :     recent_blockhash_group_t const * group   = &ctx->lead.recent_groups[ i ];
    1174           0 :     fd_sstxncache_hash_t const *     entries = &ctx->lead.txncache_entries[ group->txncache_entry_idx ];
    1175           0 :     for( ulong j=0UL; j<group->txncache_entry_cnt; j++ ) {
    1176           0 :       fd_txncache_insert( ctx->lead.txncache, banks[ group->execution_bank_i ].fork_id, banks[ group->blockhash_bank_i ].blockhash, entries[ j ].txnhash );
    1177           0 :     }
    1178           0 :   }
    1179             : 
    1180             :   /* Then finalize all the banks (freezing them) and setting the txnhash
    1181             :      offset so future queries use the correct offset.  If the offset is
    1182             :      ULONG_MAX this is valid, it means the blockhash had no transactions
    1183             :      in it, so there's nothing in the status cache under that blockhash.
    1184             : 
    1185             :      Just set the offset to 0 in this case. */
    1186           0 :   for( ulong i=0UL; i<chain_len; i++ ) {
    1187           0 :     ulong txnhash_offset = banks[ chain_len-1UL-i ].txnhash_offset==ULONG_MAX ? 0UL : banks[ chain_len-1UL-i ].txnhash_offset;
    1188           0 :     fd_txncache_finalize_fork( ctx->lead.txncache, banks[ chain_len-1UL-i ].fork_id, txnhash_offset, banks[ chain_len-1UL-i ].blockhash );
    1189           0 :   }
    1190             : 
    1191           0 :   for( ulong i=1UL; i<chain_len; i++ ) fd_txncache_advance_root( ctx->lead.txncache, banks[ chain_len-1UL-i ].fork_id );
    1192             : 
    1193           0 :   ctx->lead.txncache_root_fork_id = parent;
    1194             : 
    1195           0 :   return 0;
    1196           0 : }
    1197             : 
    1198             : static void
    1199             : process_manifest( fd_snapin_tile_t *  ctx,
    1200           0 :                   fd_stem_context_t * stem ) {
    1201           0 :   fd_snapshot_manifest_t * manifest = fd_chunk_to_laddr( ctx->lead.manifest_out.mem, ctx->lead.manifest_out.chunk );
    1202             : 
    1203           0 :   if( FD_UNLIKELY( ctx->lead.advertised_slot!=manifest->slot ) ) {
    1204             :     /* SnapshotError::MismatchedSlot
    1205             :        https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L472 */
    1206           0 :     FD_LOG_WARNING(( "snapshot manifest bank slot %lu does not match advertised slot %lu from snapshot peer",
    1207           0 :                      manifest->slot, ctx->lead.advertised_slot ));
    1208           0 :     transition_malformed( ctx, stem );
    1209           0 :     return;
    1210           0 :   }
    1211             : 
    1212           0 :   if( FD_UNLIKELY( !manifest->has_accounts_lthash ) ) {
    1213             :     /* The manifest must contain accounts lthash, irrespective of
    1214             :        whether lthash verification is disabled or not.
    1215             :        https://github.com/anza-xyz/agave/blob/v3.1.9/runtime/src/serde_snapshot.rs#L482 */
    1216           0 :     FD_LOG_WARNING(( "snapshot manifest missing accounts lthash" ));
    1217           0 :     transition_malformed( ctx, stem );
    1218           0 :     return;
    1219           0 :   }
    1220             : 
    1221           0 :   if( FD_UNLIKELY( !manifest->has_block_id ) ) {
    1222           0 :     FD_LOG_WARNING(( "snapshot manifest missing block id" ));
    1223           0 :     transition_malformed( ctx, stem );
    1224           0 :     return;
    1225           0 :   }
    1226             : 
    1227           0 :   uchar const * sum = manifest->accounts_lthash;
    1228           0 :   uchar hash32[32]; fd_blake3_hash( sum, FD_LTHASH_LEN_BYTES, hash32 );
    1229           0 :   FD_BASE58_ENCODE_32_BYTES( sum,    sum_enc    );
    1230           0 :   FD_BASE58_ENCODE_32_BYTES( hash32, hash32_enc );
    1231           0 :   FD_LOG_INFO(( "snapshot manifest slot=%lu indicates lthash[..32]=%s blake3(lthash)=%s",
    1232           0 :                 manifest->slot, sum_enc, hash32_enc ));
    1233             : 
    1234           0 :   if( FD_UNLIKELY( memcmp( ctx->lead.advertised_hash, hash32, FD_HASH_FOOTPRINT ) ) ) {
    1235             :     /* SnapshotError::MismatchedHash
    1236             :         https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L479 */
    1237           0 :     FD_BASE58_ENCODE_32_BYTES( ctx->lead.advertised_hash, advertised_hash_enc );
    1238           0 :     FD_LOG_WARNING(( "snapshot manifest accounts lthash %s does not match advertised hash from snapshot peer %s",
    1239           0 :                      hash32_enc, advertised_hash_enc ));
    1240           0 :     transition_malformed( ctx, stem );
    1241           0 :     return;
    1242           0 :   }
    1243             : 
    1244           0 :   ctx->lead.bank_slot = manifest->slot;
    1245           0 :   ctx->lead.manifest_capitalization = manifest->capitalization;
    1246           0 :   if( FD_UNLIKELY( ctx->lead.manifest_capitalization>LONG_MAX ) ) {
    1247             :     /* Calculations downstream require capitalization to be treated
    1248             :        as long (to handle addition and subtraction). */
    1249           0 :     FD_LOG_WARNING(( "snapshot manifest capitalization %lu exceeds LONG_MAX", ctx->lead.manifest_capitalization ));
    1250           0 :     transition_malformed( ctx, stem );
    1251           0 :     return;
    1252           0 :   }
    1253             : 
    1254           0 :   if( FD_UNLIKELY( fd_ssload_manifest_validate( manifest, FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS, FD_RUNTIME_MAX_STAKE_ACCOUNTS ) ) ) {
    1255           0 :     FD_LOG_WARNING(( "snapshot manifest validation failed" ));
    1256           0 :     transition_malformed( ctx, stem );
    1257           0 :     return;
    1258           0 :   }
    1259             : 
    1260           0 :   fd_epoch_schedule_t epoch_schedule = (fd_epoch_schedule_t){
    1261           0 :     .slots_per_epoch             = manifest->epoch_schedule_params.slots_per_epoch,
    1262           0 :     .leader_schedule_slot_offset = manifest->epoch_schedule_params.leader_schedule_slot_offset,
    1263           0 :     .warmup                      = manifest->epoch_schedule_params.warmup,
    1264           0 :     .first_normal_epoch          = manifest->epoch_schedule_params.first_normal_epoch,
    1265           0 :     .first_normal_slot           = manifest->epoch_schedule_params.first_normal_slot,
    1266           0 :   };
    1267           0 :   ctx->lead.epoch          = fd_slot_to_epoch( &epoch_schedule, manifest->slot, NULL );
    1268           0 :   ctx->lead.epoch_schedule = epoch_schedule;
    1269             : 
    1270           0 :   if( FD_UNLIKELY( verify_bank_hash( ctx, manifest ) ) ) {
    1271             :     /* https://github.com/anza-xyz/agave/blob/v3.1.9/runtime/src/bank.rs#L4682 */
    1272           0 :     transition_malformed( ctx, stem );
    1273           0 :     return;
    1274           0 :   }
    1275             : 
    1276             :   /* Classify this manifest against WFS (see fd_wfs.h).  A verdict on a
    1277             :      full is provisional (an incremental may supersede it); only on an
    1278             :      incremental is it final, so ERROR rejects only an incremental. */
    1279           0 :   int wfs_mode = fd_wfs_mode( ctx->wfs_slot, ctx->wfs_hash_is_zero, (ulong)ctx->wfs_shred_version, manifest->slot );
    1280           0 :   switch( wfs_mode ) {
    1281           0 :     case FD_WFS_MODE_ERROR: {
    1282           0 :       if( FD_UNLIKELY( !ctx->full ) ) {
    1283           0 :         FD_LOG_WARNING(( "incremental snapshot manifest slot %lu is behind "
    1284           0 :                          "[consensus.wait_for_supermajority_at_slot] %lu; rejecting this snapshot",
    1285           0 :                          manifest->slot, ctx->wfs_slot ));
    1286           0 :         transition_malformed( ctx, stem );
    1287           0 :         return;
    1288           0 :       }
    1289           0 :       FD_LOG_NOTICE(( "full snapshot manifest slot %lu is behind "
    1290           0 :                       "[consensus.wait_for_supermajority_at_slot] %lu; an incremental snapshot must "
    1291           0 :                       "bridge the gap or the boot will be rejected",
    1292           0 :                       manifest->slot, ctx->wfs_slot ));
    1293           0 :       break;
    1294           0 :     }
    1295           0 :     case FD_WFS_MODE_NOOP: {
    1296           0 :       FD_BASE58_ENCODE_32_BYTES( ctx->wfs_bank_hash.uc, expected_hash_enc );
    1297           0 :       FD_LOG_WARNING(( "%s snapshot manifest slot %lu is ahead of [consensus.wait_for_supermajority_at_slot] %lu "
    1298           0 :                        "(wait_for_supermajority_with_bank_hash=%s, expected_shred_version=%lu); configured slot and bank hash not verified",
    1299           0 :                        ctx->full ? "full" : "incremental",
    1300           0 :                        manifest->slot, ctx->wfs_slot, expected_hash_enc, (ulong)ctx->wfs_shred_version ));
    1301           0 :       break;
    1302           0 :     }
    1303           0 :     case FD_WFS_MODE_MATCH:
    1304             :       /* Replay verifies the bank hash of the slot we boot at. */
    1305           0 :     case FD_WFS_MODE_DISABLED:
    1306           0 :     case FD_WFS_MODE_UNRESOLVED:
    1307           0 :       break;
    1308           0 :     default:
    1309           0 :       FD_LOG_CRIT(( "unexpected WFS mode %d (%s) at manifest slot %lu",
    1310           0 :                     wfs_mode, fd_wfs_mode_str( wfs_mode ), manifest->slot ));
    1311           0 :   }
    1312             : 
    1313           0 :   if( FD_UNLIKELY( verify_slot_deltas_with_bank_slot( ctx, manifest->slot ) ) ) {
    1314           0 :     FD_LOG_WARNING(( "slot deltas verification failed" ));
    1315           0 :     transition_malformed( ctx, stem );
    1316           0 :     return;
    1317           0 :   }
    1318             : 
    1319           0 :   if( FD_UNLIKELY( verify_epoch_stakes( manifest ) ) ) {
    1320           0 :     FD_LOG_WARNING(( "epoch stakes verification failed" ));
    1321           0 :     transition_malformed( ctx, stem );
    1322           0 :     return;
    1323           0 :   }
    1324             : 
    1325           0 :   if( FD_UNLIKELY( populate_txncache( ctx, manifest->blockhashes, manifest->blockhashes_len, manifest->slot ) ) ) {
    1326           0 :     FD_LOG_WARNING(( "populating txncache failed" ));
    1327           0 :     transition_malformed( ctx, stem );
    1328           0 :     return;
    1329           0 :   }
    1330             : 
    1331           0 :   if( ctx->full ) {
    1332           0 :     ctx->lead.full_genesis_creation_time_seconds = manifest->creation_time_seconds;
    1333           0 :   } else {
    1334           0 :     if( FD_UNLIKELY( manifest->creation_time_seconds!=ctx->lead.full_genesis_creation_time_seconds ) ) {
    1335           0 :       FD_LOG_WARNING(( "snapshot manifest genesis creation time seconds %lu does not match full snapshot genesis creation time seconds %lu",
    1336           0 :                        manifest->creation_time_seconds, ctx->lead.full_genesis_creation_time_seconds ));
    1337           0 :       transition_malformed( ctx, stem );
    1338           0 :       return;
    1339           0 :     }
    1340           0 :   }
    1341             : 
    1342           0 :   manifest->accdb_fork_id    = fd_ushort_if( ctx->full, ctx->lead.accdb_root_fork_id.val, ctx->lead.accdb_incr_fork_id.val );
    1343           0 :   manifest->txncache_fork_id = ctx->lead.txncache_root_fork_id.val;
    1344             : 
    1345           0 :   ulong sig = ctx->full ? fd_ssmsg_sig( FD_SSMSG_MANIFEST_FULL ) :
    1346           0 :                           fd_ssmsg_sig( FD_SSMSG_MANIFEST_INCREMENTAL );
    1347           0 :   fd_stem_publish( stem, ctx->lead.manifest_out.idx, sig, ctx->lead.manifest_out.chunk, sizeof(fd_snapshot_manifest_t), 0UL, 0UL, 0UL );
    1348           0 :   ctx->lead.manifest_out.chunk = fd_dcache_compact_next( ctx->lead.manifest_out.chunk, sizeof(fd_snapshot_manifest_t), ctx->lead.manifest_out.chunk0, ctx->lead.manifest_out.wmark );
    1349           0 : }
    1350             : 
    1351             : static void
    1352             : snoop_stake_delegation( fd_snapin_tile_t *       ctx,
    1353             :                         ushort                   stake_fork,
    1354             :                         ulong                    slot,
    1355             :                         fd_pubkey_t const *      stake_account,
    1356             :                         ulong                    lamports,
    1357             :                         fd_stake_state_t const * stake_state,
    1358           0 :                         ulong                    data_sz ) {
    1359           0 :   fd_delegation_t const * delegation = &stake_state->stake.stake.delegation;
    1360           0 :   FD_CHECK_ERR( delegation->activation_epoch  ==ULONG_MAX || delegation->activation_epoch  <(ulong)USHORT_MAX, "activation_epoch overflow"   );
    1361           0 :   FD_CHECK_ERR( delegation->deactivation_epoch==ULONG_MAX || delegation->deactivation_epoch<(ulong)USHORT_MAX, "deactivation_epoch overflow" );
    1362             : 
    1363           0 :   fd_stake_delegations_fork_update(
    1364           0 :       ctx->stake_delegations,
    1365           0 :       stake_fork,
    1366           0 :       slot,
    1367           0 :       stake_account,
    1368           0 :       &delegation->voter_pubkey,
    1369           0 :       delegation->stake,
    1370           0 :       delegation->activation_epoch,
    1371           0 :       delegation->deactivation_epoch,
    1372           0 :       stake_state->stake.stake.credits_observed,
    1373           0 :       lamports,
    1374           0 :       (uint)data_sz );
    1375           0 : }
    1376             : 
    1377             : /* Write engine */
    1378             : 
    1379             : static void
    1380             : writer_pwrite( fd_snapin_tile_t * ctx,
    1381             :                uchar const *      buf,
    1382             :                ulong              sz,
    1383           0 :                ulong              off ) {
    1384           0 :   ulong done = 0UL;
    1385           0 :   while( done<sz ) {
    1386           0 :     long res = pwrite( ctx->writer.accdb_direct_fd, buf+done, sz-done, (long)(off+done) );
    1387           0 :     if( FD_UNLIKELY( res<=0L ) ) {
    1388           0 :       int err = res<0L ? errno : EIO;
    1389           0 :       if( res<0L && err==EINTR ) continue;
    1390           0 :       FD_LOG_ERR(( "snapshot write failed at offset %lu (%d-%s)", off+done, err, fd_io_strerror( err ) ));
    1391           0 :     }
    1392           0 :     done += (ulong)res;
    1393           0 :     ctx->metrics.disk_bytes_written += (ulong)res;
    1394           0 :   }
    1395           0 : }
    1396             : 
    1397             : static int
    1398           0 : writer_flush( fd_snapin_tile_t * ctx ) {
    1399           0 :   if( FD_UNLIKELY( !ctx->writer.buf_used ) ) return 0;
    1400             : 
    1401             :   /* Pad the range to FD_SNAPIN_DIRECT_ALIGN for O_DIRECT.  The padding
    1402             :      is a dead record so compaction reclaims it after snapshot loading
    1403             :      finishes. */
    1404           0 :   ulong used   = ctx->writer.buf_used;
    1405           0 :   ulong padded = fd_ulong_align_up( used+sizeof(fd_accdb_disk_meta_t), FD_SNAPIN_DIRECT_ALIGN );
    1406           0 :   FD_TEST( padded<=FD_SNAPIN_WRITE_BUF_SZ );
    1407           0 :   fd_memset( ctx->writer.buf+used, 0, padded-used );
    1408           0 :   fd_accdb_disk_meta_t * dummy_record = (fd_accdb_disk_meta_t *)( ctx->writer.buf+used );
    1409           0 :   dummy_record->size = (uint)( padded-used-sizeof(fd_accdb_disk_meta_t) );
    1410             : 
    1411             :   /* The offset is aligned because partition sizes are multiples of
    1412             :      FD_SNAPIN_DIRECT_ALIGN (whole GiB), the snapin tiles are the only
    1413             :      layer-0 writers during the load, and every reservation they make
    1414             :      is a multiple of FD_SNAPIN_DIRECT_ALIGN. */
    1415           0 :   ulong base_off = fd_accdb_snapshot_reserve_write( ctx->accdb, padded, padded-used );
    1416           0 :   FD_TEST( fd_ulong_is_aligned( base_off, FD_SNAPIN_DIRECT_ALIGN ) );
    1417           0 :   writer_pwrite( ctx, ctx->writer.buf, padded, base_off );
    1418             : 
    1419           0 :   fd_accdb_fork_id_t          fork_id    = { .val = ctx->full ? USHORT_MAX : (ushort)ctx->incr_fork };
    1420           0 :   ushort                      stake_fork = FD_VOLATILE_CONST( ctx->shmem->stake_fork );
    1421           0 :   fd_snapin_account_batch_t * batch      = &ctx->writer.batch;
    1422           0 :   ulong                       tile_idx   = ctx->tile_idx;
    1423             : 
    1424           0 :   uchar const * pubkeys[ FD_SSPARSE_ACC_BATCH_MAX ];
    1425           0 :   ulong slots          [ FD_SSPARSE_ACC_BATCH_MAX ];
    1426           0 :   ulong data_lens      [ FD_SSPARSE_ACC_BATCH_MAX ];
    1427           0 :   ulong file_offsets   [ FD_SSPARSE_ACC_BATCH_MAX ];
    1428           0 :   uchar results        [ FD_SSPARSE_ACC_BATCH_MAX ];
    1429           0 :   ulong buf_off = 0UL;
    1430             : 
    1431             :   /* Flush accounts in batches of 8 */
    1432           0 :   for( ulong batch_off=0UL; batch_off<batch->cnt; batch_off+=FD_SSPARSE_ACC_BATCH_MAX ) {
    1433           0 :     ulong cnt = fd_ulong_min( FD_SSPARSE_ACC_BATCH_MAX, batch->cnt-batch_off );
    1434           0 :     ulong input_lamports_hi = 0UL;
    1435           0 :     ulong input_lamports    = 0UL;
    1436             : 
    1437           0 :     for( ulong i=0UL; i<cnt; i++ ) {
    1438           0 :       ulong idx = batch_off+i;
    1439             : 
    1440           0 :       pubkeys     [ i ] = ctx->writer.buf+buf_off;
    1441           0 :       slots       [ i ] = (ulong)batch->slots    [ idx ];
    1442           0 :       data_lens   [ i ] = (ulong)batch->data_lens[ idx ];
    1443           0 :       file_offsets[ i ] = base_off+buf_off;
    1444             : 
    1445           0 :       buf_off += sizeof(fd_accdb_disk_meta_t)+data_lens[ i ];
    1446           0 :       fd_uwide_inc( &input_lamports_hi, &input_lamports, input_lamports_hi, input_lamports, batch->lamports[ idx ] );
    1447           0 :     }
    1448             : 
    1449           0 :     ulong accounts_ignored, accounts_replaced, accounts_loaded, replaced_lamports, ignored_lamports;
    1450           0 :     if( FD_UNLIKELY( fd_accdb_snapshot_write_batch( ctx->accdb, fork_id, cnt, pubkeys,
    1451           0 :                                                     slots, batch->lamports+batch_off,
    1452           0 :                                                     data_lens, batch->executables+batch_off,
    1453           0 :                                                     file_offsets, &accounts_ignored, &accounts_replaced,
    1454           0 :                                                     &accounts_loaded, &replaced_lamports, &ignored_lamports, results ) ) ) {
    1455           0 :       return 1;
    1456           0 :     }
    1457             : 
    1458             :     /* Update the snooped stake delegations.  Anything that is not a
    1459             :        delegation and replaced a funded version tombstones it, so a tile
    1460             :        still holding the older version cannot leave it behind. */
    1461           0 :     for( ulong i=0UL; i<cnt; i++ ) {
    1462           0 :       if( FD_UNLIKELY( results[ i ]==FD_ACCDB_SNAPSHOT_WRITE_IGNORED ) ) continue;
    1463             : 
    1464           0 :       ulong               lamports = batch->lamports[ batch_off+i ];
    1465           0 :       fd_pubkey_t const * pubkey   = (fd_pubkey_t const *)pubkeys[ i ];
    1466           0 :       uchar const *       owner    = pubkeys[ i ]+offsetof(fd_accdb_disk_meta_t, owner);
    1467           0 :       uchar const *       data     = pubkeys[ i ]+sizeof(fd_accdb_disk_meta_t);
    1468             : 
    1469           0 :       if( lamports && !memcmp( owner, fd_solana_stake_program_id.uc, 32UL ) ) {
    1470           0 :         fd_stake_state_t const * stake_state = fd_stake_state_view( data, data_lens[ i ] );
    1471           0 :         if( stake_state && stake_state->stake_type==FD_STAKE_STATE_STAKE ) {
    1472           0 :           snoop_stake_delegation( ctx, stake_fork, slots[ i ], pubkey, lamports, stake_state, data_lens[ i ] );
    1473           0 :           continue;
    1474           0 :         }
    1475           0 :       }
    1476             : 
    1477           0 :       int replacing_full_entry = results[ i ]==FD_ACCDB_SNAPSHOT_WRITE_REPLACED_CROSS;
    1478           0 :       if( FD_UNLIKELY( replacing_full_entry || results[ i ]==FD_ACCDB_SNAPSHOT_WRITE_REPLACED ) ) {
    1479           0 :         fd_stake_delegations_fork_remove( ctx->stake_delegations, stake_fork, slots[ i ], pubkey, replacing_full_entry );
    1480           0 :       }
    1481           0 :     }
    1482             : 
    1483           0 :     ctx->metrics.accounts_ignored  += accounts_ignored;
    1484           0 :     ctx->metrics.accounts_replaced += accounts_replaced;
    1485           0 :     ctx->metrics.accounts_loaded   += accounts_loaded;
    1486           0 :     ctx->shmem->values[ tile_idx ].loaded            += accounts_loaded;
    1487           0 :     ctx->shmem->values[ tile_idx ].duplicates        += accounts_ignored + accounts_replaced;
    1488           0 :     ulong duplicate_lamports_hi, duplicate_lamports;
    1489           0 :     fd_uwide_inc( &duplicate_lamports_hi, &duplicate_lamports, 0UL, replaced_lamports, ignored_lamports );
    1490           0 :     ulong * in_hi  = &ctx->shmem->values[ tile_idx ].input_lamports_hi;     ulong * in_lo  = &ctx->shmem->values[ tile_idx ].input_lamports;
    1491           0 :     ulong * dup_hi = &ctx->shmem->values[ tile_idx ].duplicate_lamports_hi; ulong * dup_lo = &ctx->shmem->values[ tile_idx ].duplicate_lamports;
    1492           0 :     fd_uwide_add( in_hi,  in_lo,  *in_hi,  *in_lo,  input_lamports_hi,     input_lamports,     0UL );
    1493           0 :     fd_uwide_add( dup_hi, dup_lo, *dup_hi, *dup_lo, duplicate_lamports_hi, duplicate_lamports, 0UL );
    1494           0 :   }
    1495             : 
    1496           0 :   FD_TEST( buf_off==ctx->writer.buf_used );
    1497           0 :   ctx->writer.buf_used  = 0UL;
    1498           0 :   ctx->writer.batch.cnt = 0UL;
    1499           0 :   return 0;
    1500           0 : }
    1501             : 
    1502             : static int
    1503             : writer_append_account( fd_snapin_tile_t * ctx,
    1504             :                        uchar const *      pubkey,
    1505             :                        uchar const *      owner,
    1506             :                        uchar const *      data,
    1507             :                        ulong              slot,
    1508             :                        ulong              lamports,
    1509             :                        ulong              data_len,
    1510           0 :                        int                executable ) {
    1511           0 :   FD_TEST( slot<=UINT_MAX );
    1512           0 :   ulong account_sz = sizeof(fd_accdb_disk_meta_t)+data_len;
    1513           0 :   FD_TEST( account_sz<=FD_SNAPIN_WRITE_BUF_MAX );
    1514             : 
    1515           0 :   if( FD_UNLIKELY( account_sz>FD_SNAPIN_WRITE_BUF_MAX-ctx->writer.buf_used && writer_flush( ctx ) ) ) {
    1516           0 :     return 1;
    1517           0 :   }
    1518             : 
    1519           0 :   FD_TEST( ctx->writer.batch.cnt<FD_SNAPIN_WRITE_ACCOUNT_MAX );
    1520             : 
    1521           0 :   ulong idx     = ctx->writer.batch.cnt++;
    1522           0 :   ulong buf_off = ctx->writer.buf_used;
    1523             : 
    1524             :   /* Serialize the account metadata into the buffer */
    1525           0 :   fd_accdb_disk_meta_t meta = {
    1526           0 :     .size       = (uint)data_len,
    1527           0 :     .generation = 0U,
    1528           0 :   };
    1529           0 :   uchar * meta_ptr = ctx->writer.buf+buf_off;
    1530           0 :   uchar * data_ptr = meta_ptr+sizeof(fd_accdb_disk_meta_t);
    1531           0 :   fd_memcpy( meta.pubkey, pubkey, 32UL );
    1532           0 :   fd_memcpy( meta.owner, owner, 32UL );
    1533           0 :   fd_memcpy( meta_ptr, meta.b, sizeof(fd_accdb_disk_meta_t) );
    1534           0 :   fd_memcpy( data_ptr, data, data_len );
    1535           0 :   ctx->writer.buf_used += account_sz;
    1536             : 
    1537             :   /* Stage fields needed for index publication after pwrite. */
    1538           0 :   ctx->writer.batch.lamports    [ idx ] = lamports;
    1539           0 :   ctx->writer.batch.slots       [ idx ] = (uint)slot;
    1540           0 :   ctx->writer.batch.data_lens   [ idx ] = (uint)data_len;
    1541           0 :   ctx->writer.batch.executables [ idx ] = executable;
    1542           0 :   ctx->metrics.total_accounts_processed++;
    1543           0 :   return 0;
    1544           0 : }
    1545             : 
    1546             : static int
    1547           0 : writer_append_staged_account( fd_snapin_tile_t * ctx ) {
    1548           0 :   if( FD_UNLIKELY( writer_append_account( ctx, ctx->staged.pubkey, ctx->staged.owner, ctx->staged.data,
    1549           0 :                                           ctx->staged.slot, ctx->staged.lamports, ctx->staged.data_len,
    1550           0 :                                           ctx->staged.executable ) ) ) {
    1551           0 :     return 1;
    1552           0 :   }
    1553           0 :   ctx->metrics.total_account_batches_processed++;
    1554           0 :   return 0;
    1555           0 : }
    1556             : 
    1557             : static int
    1558             : process_account_batch( fd_snapin_tile_t *            ctx,
    1559           0 :                        fd_ssparse_advance_result_t * result ) {
    1560           0 :   uchar const * const * entries    = result->account_batch.batch;
    1561           0 :   ulong                 cnt        = result->account_batch.batch_cnt;
    1562           0 :   ulong                 batch_slot = result->account_batch.slot;
    1563             : 
    1564           0 :   for( ulong i=0UL; i<cnt; i++ ) {
    1565           0 :     uchar const * e = entries[ i ];
    1566           0 :     if( FD_UNLIKELY( writer_append_account( ctx, e+16UL, e+64UL, e+136UL, batch_slot,
    1567           0 :                                             fd_ulong_load_8_fast( e+48UL ), fd_ulong_load_8_fast( e+8UL ),
    1568           0 :                                             (int)e[ 96UL ] ) ) ) {
    1569           0 :       return 1;
    1570           0 :     }
    1571           0 :   }
    1572           0 :   ctx->metrics.total_account_batches_processed++;
    1573           0 :   return 0;
    1574           0 : }
    1575             : 
    1576             : static int
    1577             : process_account_header( fd_snapin_tile_t *            ctx,
    1578           0 :                         fd_ssparse_advance_result_t * result ) {
    1579           0 :   FD_TEST( ctx->staged.bytes_received==ctx->staged.data_len );
    1580           0 :   FD_TEST( result->account_header.data_len<=FD_RUNTIME_ACC_SZ_MAX );
    1581             : 
    1582           0 :   ctx->staged.executable     = result->account_header.executable;
    1583           0 :   ctx->staged.slot           = result->account_header.slot;
    1584           0 :   ctx->staged.lamports       = result->account_header.lamports;
    1585           0 :   ctx->staged.data_len       = result->account_header.data_len;
    1586           0 :   ctx->staged.bytes_received = 0UL;
    1587           0 :   fd_memcpy( ctx->staged.pubkey, result->account_header.pubkey, 32UL );
    1588           0 :   fd_memcpy( ctx->staged.owner,  result->account_header.owner,  32UL );
    1589             : 
    1590           0 :   if( FD_LIKELY( ctx->staged.data_len ) ) {
    1591           0 :     return 0;
    1592           0 :   }
    1593             : 
    1594             :   /* No account data to receive, so we can just publish the account now. */
    1595           0 :   return writer_append_staged_account( ctx );
    1596           0 : }
    1597             : 
    1598             : static int
    1599             : process_account_data( fd_snapin_tile_t *            ctx,
    1600           0 :                       fd_ssparse_advance_result_t * result ) {
    1601           0 :   FD_TEST( ctx->staged.bytes_received<ctx->staged.data_len );
    1602           0 :   FD_TEST( result->account_data.data_sz<=ctx->staged.data_len-ctx->staged.bytes_received );
    1603             : 
    1604           0 :   fd_memcpy( ctx->staged.data+ctx->staged.bytes_received, result->account_data.data, result->account_data.data_sz );
    1605           0 :   ctx->staged.bytes_received += result->account_data.data_sz;
    1606             : 
    1607             :   /* More bytes remain in account data, don't publish yet */
    1608           0 :   if( FD_LIKELY( ctx->staged.bytes_received<ctx->staged.data_len ) ) {
    1609           0 :     return 0;
    1610           0 :   }
    1611             : 
    1612           0 :   return writer_append_staged_account( ctx );
    1613           0 : }
    1614             : 
    1615             : static int
    1616             : handle_data_frag( fd_snapin_tile_t *  ctx,
    1617             :                   ulong               in_idx,
    1618             :                   ulong               chunk,
    1619             :                   ulong               sz,
    1620           0 :                   fd_stem_context_t * stem ) {
    1621           0 :   if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_FINISHING ) ) {
    1622           0 :     FD_LOG_WARNING(( "received unexpected data frag while in state %s (%lu)",
    1623           0 :                      fd_ssctrl_state_str( (ulong)ctx->state ), (ulong)ctx->state  ));
    1624           0 :     transition_malformed( ctx, stem );
    1625           0 :     return 0;
    1626           0 :   }
    1627           0 :   if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_ERROR ) ) {
    1628             :     /* Ignore all data frags after observing an error in the stream until
    1629             :        we receive fail & init control messages to restart processing. */
    1630           0 :     return 0;
    1631           0 :   }
    1632           0 :   if( FD_UNLIKELY( ctx->state!=FD_SNAPSHOT_STATE_PROCESSING ) ) {
    1633           0 :     FD_LOG_ERR(( "received data frag during invalid state %s (%lu)",
    1634           0 :                  fd_ssctrl_state_str( (ulong)ctx->state ), (ulong)ctx->state ));
    1635           0 :   }
    1636             : 
    1637           0 :   if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) ) {
    1638           0 :     FD_LOG_ERR(( "invalid data frag bounds (chunk=%lu chunk0=%lu wmark=%lu sz=%lu mtu=%lu)", chunk, ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark, sz, ctx->in[ in_idx ].mtu ));
    1639           0 :   }
    1640             : 
    1641           0 :   for(;;) {
    1642           0 :     if( FD_UNLIKELY( sz-ctx->in[ in_idx ].pos==0UL ) ) break;
    1643             : 
    1644           0 :     uchar const * data = (uchar const *)fd_chunk_to_laddr_const( ctx->in[ in_idx ].wksp, chunk ) + ctx->in[ in_idx ].pos;
    1645             : 
    1646           0 :     int early_exit = 0;
    1647           0 :     fd_ssparse_advance_result_t result[1];
    1648           0 :     int res = fd_ssparse_advance( ctx->ssparse, data, sz-ctx->in[ in_idx ].pos, result );
    1649           0 :     switch( res ) {
    1650           0 :       case FD_SSPARSE_ADVANCE_ERROR:
    1651           0 :         FD_LOG_WARNING(( "error while parsing snapshot stream" ));
    1652           0 :         transition_malformed( ctx, stem );
    1653           0 :         return 0;
    1654           0 :       case FD_SSPARSE_ADVANCE_AGAIN:
    1655           0 :         break;
    1656           0 :       case FD_SSPARSE_ADVANCE_APPENDVEC: {
    1657             :         /* Parse only this tile's claimed appendvecs. */
    1658           0 :         ulong appendvec_idx = ctx->appendvec_seq++;
    1659           0 :         if( FD_UNLIKELY( appendvec_idx==ctx->claimed_appendvec ) ) {
    1660           0 :           ctx->claimed_appendvec = FD_ATOMIC_FETCH_AND_ADD( &ctx->shmem->next_appendvec_ticket, 1UL );
    1661           0 :           fd_ssparse_appendvec_parse( ctx->ssparse );
    1662           0 :         }
    1663           0 :         break;
    1664           0 :       }
    1665           0 :       case FD_SSPARSE_ADVANCE_MANIFEST:
    1666           0 :       case FD_SSPARSE_ADVANCE_MANIFEST_DONE: {
    1667             :          /* Tile 0 only. */
    1668           0 :         if( FD_LIKELY( !is_lead( ctx ) ) ) break;
    1669             : 
    1670           0 :         if( FD_UNLIKELY( ctx->lead.flags.manifest_done ) ) {
    1671           0 :           FD_LOG_WARNING(( "excess data after manifest" ));
    1672           0 :           transition_malformed( ctx, stem );
    1673           0 :           return 0;
    1674           0 :         }
    1675           0 :         int parser_res = fd_ssmanifest_parser_consume( ctx->lead.manifest_parser,
    1676           0 :                                                        result->manifest.data,
    1677           0 :                                                        result->manifest.data_sz );
    1678           0 :         if( FD_UNLIKELY( parser_res==FD_SSMANIFEST_PARSER_ADVANCE_ERROR ) ) {
    1679           0 :           FD_LOG_WARNING(( "error while parsing snapshot manifest" ));
    1680           0 :           transition_malformed( ctx, stem );
    1681           0 :           return 0;
    1682           0 :         }
    1683           0 :         if( res==FD_SSPARSE_ADVANCE_MANIFEST_DONE ) {
    1684           0 :           if( FD_UNLIKELY( fd_ssmanifest_parser_fini( ctx->lead.manifest_parser )!=FD_SSMANIFEST_PARSER_ADVANCE_DONE ) ) {
    1685           0 :             FD_LOG_WARNING(( "manifest stream ended before parser was done" ));
    1686           0 :             transition_malformed( ctx, stem );
    1687           0 :             return 0;
    1688           0 :           }
    1689           0 :           ctx->lead.flags.manifest_done = 1;
    1690           0 :         }
    1691           0 :         break;
    1692           0 :       }
    1693           0 :       case FD_SSPARSE_ADVANCE_STATUS_CACHE: {
    1694             :         /* Tile 0 only. */
    1695           0 :         if( FD_LIKELY( !is_lead( ctx ) ) ) break;
    1696             : 
    1697           0 :         fd_slot_delta_parser_advance_result_t sd_result[1];
    1698           0 :         ulong bytes_remaining = result->status_cache.data_sz;
    1699             : 
    1700           0 :         while( bytes_remaining ) {
    1701           0 :           int res = fd_slot_delta_parser_consume( ctx->lead.slot_delta_parser,
    1702           0 :                                                   result->status_cache.data,
    1703           0 :                                                   bytes_remaining,
    1704           0 :                                                   sd_result );
    1705           0 :           if( FD_UNLIKELY( res<0 ) ) {
    1706           0 :             FD_LOG_WARNING(( "error while parsing slot deltas in status cache" ));
    1707           0 :             transition_malformed( ctx, stem );
    1708           0 :             return 0;
    1709           0 :           } else if( FD_LIKELY( res==FD_SLOT_DELTA_PARSER_ADVANCE_SLOT ) ) {
    1710           0 :             txncache_staging_slot_begin( ctx, sd_result->slot );
    1711           0 :           } else if( FD_LIKELY( res==FD_SLOT_DELTA_PARSER_ADVANCE_GROUP ) ) {
    1712           0 :             if( FD_UNLIKELY( txncache_staging_group_begin( ctx, sd_result->group.blockhash, sd_result->group.txnhash_offset ) ) ) {
    1713           0 :               FD_LOG_WARNING(( "blockhash groups overflow for slot %lu, max is %lu", sd_result->group.slot, ctx->lead.txncache_max_groups_per_slot ));
    1714           0 :               transition_malformed( ctx, stem );
    1715           0 :               return 0;
    1716           0 :             }
    1717           0 :           } else if( FD_LIKELY( res==FD_SLOT_DELTA_PARSER_ADVANCE_ENTRY ) ) {
    1718           0 :             if( FD_UNLIKELY( txncache_staging_entry_add( ctx, sd_result->entry->slot, sd_result->entry->txnhash ) ) ) {
    1719           0 :               FD_LOG_WARNING(( "status cache slot %lu has more than %lu entries", sd_result->entry->slot, ctx->lead.txncache_max_entries_per_slot ));
    1720           0 :               transition_malformed( ctx, stem );
    1721           0 :               return 0;
    1722           0 :             }
    1723           0 :           }
    1724             : 
    1725           0 :           bytes_remaining           -= sd_result->bytes_consumed;
    1726           0 :           result->status_cache.data += sd_result->bytes_consumed;
    1727           0 :         }
    1728             : 
    1729           0 :         if( FD_UNLIKELY( result->status_cache.done ) ) {
    1730           0 :           int fini_res = fd_slot_delta_parser_consume( ctx->lead.slot_delta_parser, result->status_cache.data, 0UL, sd_result );
    1731           0 :           if( FD_UNLIKELY( fini_res<0 ) ) {
    1732           0 :             FD_LOG_WARNING(( "error while finalizing slot deltas in status cache" ));
    1733           0 :             transition_malformed( ctx, stem );
    1734           0 :             return 0;
    1735           0 :           }
    1736           0 :           ctx->lead.flags.status_cache_done = fini_res==FD_SLOT_DELTA_PARSER_ADVANCE_DONE;
    1737           0 :         }
    1738           0 :         break;
    1739           0 :       }
    1740           0 :       case FD_SSPARSE_ADVANCE_ACCOUNT_HEADER:
    1741           0 :         early_exit = process_account_header( ctx, result );
    1742           0 :         if( FD_UNLIKELY( early_exit ) ) {
    1743           0 :           transition_malformed( ctx, stem );
    1744           0 :           return 0;
    1745           0 :         }
    1746           0 :         break;
    1747           0 :       case FD_SSPARSE_ADVANCE_ACCOUNT_DATA:
    1748           0 :         if( FD_UNLIKELY( process_account_data( ctx, result ) ) ) {
    1749           0 :           transition_malformed( ctx, stem );
    1750           0 :           return 0;
    1751           0 :         }
    1752             : 
    1753             :         /* Account data may span multiple input chunks.  Once complete,
    1754             :            copy the staged account into gui_out and publish it once.
    1755             : 
    1756             :            We expect ConfigKeys Vec to be length 2 (checked via the
    1757             :            first byte of the accumulated data).  We expect the size of
    1758             :            ConfigProgram-owned accounts to be at most
    1759             :            FD_GUI_CONFIG_PARSE_MAX_VALID_ACCT_SZ, since this is the
    1760             :            size that the Solana CLI allocates for them. Although the
    1761             :            ConfigProgram itself does not enforce these invariants, the
    1762             :            vast majority of accounts (with a tiny number of exceptions
    1763             :            on devnet) are maintained with the Solana CLI. */
    1764           0 :         if( FD_UNLIKELY( ctx->staged.bytes_received==ctx->staged.data_len
    1765           0 :                       && ctx->gui_out.idx!=ULONG_MAX
    1766           0 :                       && !memcmp( ctx->staged.owner, fd_solana_config_program_id.key, sizeof(fd_hash_t) )
    1767           0 :                       && ctx->staged.data_len
    1768           0 :                       && ctx->staged.data_len<=FD_GUI_CONFIG_PARSE_MAX_VALID_ACCT_SZ
    1769           0 :                       && ctx->staged.data[ 0 ]==2UL ) ) {
    1770           0 :           uchar * acct = fd_chunk_to_laddr( ctx->gui_out.mem, ctx->gui_out.chunk );
    1771           0 :           fd_memcpy( acct, ctx->staged.data, ctx->staged.data_len );
    1772           0 :           fd_stem_publish( stem, ctx->gui_out.idx, ctx->staged.slot, ctx->gui_out.chunk, ctx->staged.data_len, 0UL, 0UL, 0UL ); /* sig is the account slot */
    1773           0 :           ctx->gui_out.chunk = fd_dcache_compact_next( ctx->gui_out.chunk, ctx->staged.data_len, ctx->gui_out.chunk0, ctx->gui_out.wmark );
    1774           0 :           early_exit = 1;
    1775           0 :         }
    1776           0 :         break;
    1777           0 :       case FD_SSPARSE_ADVANCE_ACCOUNT_BATCH:
    1778           0 :         if( FD_UNLIKELY( process_account_batch( ctx, result ) ) ) {
    1779           0 :           transition_malformed( ctx, stem );
    1780           0 :           return 0;
    1781           0 :         }
    1782           0 :         break;
    1783           0 :       case FD_SSPARSE_ADVANCE_DONE:
    1784           0 :         ctx->state = FD_SNAPSHOT_STATE_FINISHING;
    1785           0 :         break;
    1786           0 :       default:
    1787           0 :         FD_LOG_ERR(( "unexpected fd_ssparse_advance result %d", res ));
    1788           0 :         break;
    1789           0 :     }
    1790             : 
    1791           0 :     if( FD_UNLIKELY( is_lead( ctx ) && !ctx->lead.flags.manifest_processed && ctx->lead.flags.manifest_done && ctx->lead.flags.status_cache_done ) ) {
    1792           0 :       process_manifest( ctx, stem );
    1793           0 :       if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_ERROR ) ) break;
    1794           0 :       ctx->lead.flags.manifest_processed = 1;
    1795           0 :     }
    1796             : 
    1797           0 :     ctx->in[ in_idx ].pos += result->bytes_consumed;
    1798           0 :     if( FD_LIKELY( ctx->full ) ) ctx->metrics.full_bytes_read        += result->bytes_consumed;
    1799           0 :     else                         ctx->metrics.incremental_bytes_read += result->bytes_consumed;
    1800             : 
    1801           0 :     if( FD_UNLIKELY( early_exit ) ) break;
    1802           0 :   }
    1803             : 
    1804           0 :   int reprocess_frag = ctx->in[ in_idx ].pos<sz;
    1805           0 :   if( FD_LIKELY( !reprocess_frag ) ) ctx->in[ in_idx ].pos = 0UL;
    1806           0 :   return reprocess_frag;
    1807           0 : }
    1808             : 
    1809             : /* Resets local worker state */
    1810             : 
    1811             : static void
    1812           0 : reset_attempt_state( fd_snapin_tile_t * ctx ) {
    1813           0 :   for( ulong lane=0UL; lane<ctx->lane_cnt; lane++ ) {
    1814           0 :     ctx->in[ lane ].pos = 0UL;
    1815           0 :   }
    1816           0 :   ctx->expected_frame         = 0UL;
    1817           0 :   ctx->appendvec_seq          = 0UL;
    1818           0 :   ctx->incr_fork              = ULONG_MAX;
    1819           0 :   ctx->writer.buf_used        = 0UL;
    1820           0 :   ctx->writer.batch.cnt       = 0UL;
    1821           0 :   ctx->staged.data_len        = 0UL;
    1822           0 :   ctx->staged.bytes_received  = 0UL;
    1823             : 
    1824           0 :   fd_ssparse_init( ctx->ssparse );
    1825           0 :   fd_ssparse_batch_enable( ctx->ssparse, 1 );
    1826           0 : }
    1827             : 
    1828             : static void
    1829           0 : start_processing_attempt( fd_snapin_tile_t * ctx ) {
    1830           0 :   FD_COMPILER_MFENCE();
    1831           0 :   ctx->incr_fork = FD_VOLATILE_CONST( ctx->shmem->fork_id );
    1832           0 :   FD_TEST( ctx->full ? ctx->incr_fork==(ulong)USHORT_MAX : ctx->incr_fork<(ulong)USHORT_MAX );
    1833             : 
    1834             :   /* Claim before the first data fragment. */
    1835           0 :   ctx->claimed_appendvec = FD_ATOMIC_FETCH_AND_ADD( &ctx->shmem->next_appendvec_ticket, 1UL );
    1836           0 : }
    1837             : 
    1838             : static int
    1839           0 : validate_capitalization( fd_snapin_tile_t * ctx ) {
    1840             :   /* Sum exactly: saturating at 2^64 computed 0 for an honest snapshot
    1841             :      and could accept a wrong one. */
    1842           0 :   ulong input_hi = 0UL, input_lo = 0UL;
    1843           0 :   ulong dup_hi   = 0UL, dup_lo   = 0UL;
    1844           0 :   FD_COMPILER_MFENCE();
    1845           0 :   for( ulong i=0UL; i<FD_TOPO_MAX_TILE_IN_LINKS; i++ ) {
    1846           0 :     fd_uwide_add( &input_hi, &input_lo, input_hi, input_lo, ctx->shmem->values[ i ].input_lamports_hi,     ctx->shmem->values[ i ].input_lamports,     0UL );
    1847           0 :     fd_uwide_add( &dup_hi,   &dup_lo,   dup_hi,   dup_lo,   ctx->shmem->values[ i ].duplicate_lamports_hi, ctx->shmem->values[ i ].duplicate_lamports, 0UL );
    1848           0 :   }
    1849             : 
    1850           0 :   ulong cap_hi = 0UL;
    1851           0 :   ulong cap_lo = fd_ulong_if( ctx->full, 0UL, ctx->lead.recovery.capitalization );
    1852           0 :   fd_uwide_add( &cap_hi, &cap_lo, cap_hi, cap_lo, input_hi, input_lo, 0UL );
    1853           0 :   ulong borrow = fd_uwide_sub( &cap_hi, &cap_lo, cap_hi, cap_lo, dup_hi, dup_lo, 0UL );
    1854           0 :   if( FD_UNLIKELY( borrow || cap_hi || cap_lo!=ctx->lead.manifest_capitalization ) ) {
    1855             :     /* SnapshotError::MismatchedCapitalization
    1856             :         https://github.com/anza-xyz/agave/blob/v4.0.0-beta.2/runtime/src/snapshot_bank_utils.rs#L217 */
    1857           0 :     if( FD_LIKELY( !borrow && !cap_hi ) ) {
    1858           0 :       FD_LOG_WARNING(( "%s snapshot manifest capitalization %lu does not match computed capitalization %lu",
    1859           0 :                        ctx->full?"full":"incr", ctx->lead.manifest_capitalization, cap_lo ));
    1860           0 :     } else {
    1861           0 :       FD_LOG_WARNING(( "%s snapshot manifest capitalization %lu does not match computed capitalization, which is out of range "
    1862           0 :                        "(input lamports %lu*2^64+%lu, duplicate lamports %lu*2^64+%lu)",
    1863           0 :                        ctx->full?"full":"incr", ctx->lead.manifest_capitalization, input_hi, input_lo, dup_hi, dup_lo ));
    1864           0 :     }
    1865           0 :     return -1;
    1866           0 :   }
    1867           0 :   return 0;
    1868           0 : }
    1869             : 
    1870             : static void
    1871           0 : fold_account_counts( fd_snapin_tile_t * ctx ) {
    1872           0 :   for( ulong i=0UL; i<FD_TOPO_MAX_TILE_IN_LINKS; i++ ) {
    1873           0 :     ctx->lead.account_counts.loaded     += ctx->shmem->values[ i ].loaded;
    1874           0 :     ctx->lead.account_counts.duplicates += ctx->shmem->values[ i ].duplicates;
    1875           0 :   }
    1876           0 : }
    1877             : 
    1878             : static void
    1879             : handle_control_frag( fd_snapin_tile_t *  ctx,
    1880             :                      fd_stem_context_t * stem,
    1881             :                      ulong               in_idx,
    1882             :                      ulong               sig,
    1883             :                      ulong               chunk,
    1884           0 :                      ulong               sz ) {
    1885           0 :   if( ctx->state==FD_SNAPSHOT_STATE_ERROR && sig!=FD_SNAPSHOT_MSG_CTRL_FAIL ) {
    1886             :     /* Control messages move along the snapshot load pipeline.  Since
    1887             :        error conditions can be triggered by any tile in the pipeline,
    1888             :        it is possible to be in error state and still receive otherwise
    1889             :        valid messages.  Only a fail message can revert this. */
    1890           0 :     return;
    1891           0 :   };
    1892             : 
    1893           0 :   int forward_msg = 1;
    1894             : 
    1895           0 :   switch( sig ) {
    1896           0 :     case FD_SNAPSHOT_MSG_CTRL_INIT_FULL:
    1897           0 :     case FD_SNAPSHOT_MSG_CTRL_INIT_INCR: {
    1898           0 :       FD_TEST( ctx->state==FD_SNAPSHOT_STATE_IDLE );
    1899           0 :       ctx->state = FD_SNAPSHOT_STATE_PROCESSING;
    1900           0 :       ctx->full = sig==FD_SNAPSHOT_MSG_CTRL_INIT_FULL;
    1901             : 
    1902           0 :       reset_attempt_state( ctx );
    1903             : 
    1904             :       /* Rewind metric counters (no-op unless recovering from a fail) */
    1905           0 :       if( sig==FD_SNAPSHOT_MSG_CTRL_INIT_FULL ) {
    1906           0 :         ctx->metrics.accounts_loaded   = ctx->metrics.full_accounts_loaded   = 0;
    1907           0 :         ctx->metrics.accounts_replaced = ctx->metrics.full_accounts_replaced = 0;
    1908           0 :         ctx->metrics.accounts_ignored  = ctx->metrics.full_accounts_ignored  = 0;
    1909           0 :         ctx->metrics.full_bytes_read   = 0UL;
    1910           0 :         ctx->metrics.incremental_bytes_read = 0UL;
    1911           0 :       } else {
    1912           0 :         ctx->metrics.accounts_loaded   = ctx->metrics.full_accounts_loaded;
    1913           0 :         ctx->metrics.accounts_replaced = ctx->metrics.full_accounts_replaced;
    1914           0 :         ctx->metrics.accounts_ignored  = ctx->metrics.full_accounts_ignored;
    1915           0 :         ctx->metrics.incremental_bytes_read = 0UL;
    1916           0 :       }
    1917             : 
    1918           0 :       if( FD_LIKELY( !is_lead( ctx ) ) ) break;
    1919             : 
    1920             :       /* Roll back before publishing this attempt. */
    1921           0 :       if( FD_UNLIKELY( ctx->lead.rollback.pending ) ) {
    1922           0 :         ctx->lead.rollback.pending = 0;
    1923             :         /* Purge failed incremental state unless a full reset follows. */
    1924           0 :         if( !ctx->lead.rollback.full && FD_LIKELY( !ctx->full ) ) {
    1925           0 :           fd_accdb_purge( ctx->accdb, ctx->lead.rollback.fork );
    1926           0 :           fd_accdb_snapshot_revert_whead( ctx->accdb, &ctx->lead.recovery.accdb_metadata );
    1927           0 :           fd_stake_delegations_evict_fork( ctx->stake_delegations, FD_VOLATILE_CONST( ctx->shmem->stake_fork ) );
    1928           0 :         }
    1929           0 :       }
    1930             : 
    1931           0 :       ctx->lead.manifest_capitalization = 0UL;
    1932             : 
    1933           0 :       fd_txncache_reset( ctx->lead.txncache );
    1934           0 :       txncache_staging_reset( ctx );
    1935           0 :       fd_ssmanifest_parser_init( ctx->lead.manifest_parser, fd_chunk_to_laddr( ctx->lead.manifest_out.mem, ctx->lead.manifest_out.chunk ) );
    1936           0 :       fd_slot_delta_parser_init( ctx->lead.slot_delta_parser );
    1937           0 :       fd_memset( &ctx->lead.flags,    0, sizeof(ctx->lead.flags)    );
    1938             : 
    1939           0 :       ushort stake_fork = USHORT_MAX;
    1940           0 :       if( sig==FD_SNAPSHOT_MSG_CTRL_INIT_FULL ) {
    1941           0 :         ctx->lead.full_genesis_creation_time_seconds = 0UL;
    1942           0 :         ctx->lead.recovery.capitalization            = 0UL;
    1943           0 :         fd_memset( &ctx->lead.account_counts, 0, sizeof(ctx->lead.account_counts) );
    1944             : 
    1945           0 :         fd_stake_delegations_reset( ctx->stake_delegations );
    1946           0 :         fd_accdb_reset( ctx->accdb );
    1947           0 :         fd_accdb_fork_id_t null_fork_id = (fd_accdb_fork_id_t){ .val = USHORT_MAX };
    1948           0 :         ctx->lead.accdb_root_fork_id = fd_accdb_attach_child( ctx->accdb, null_fork_id );
    1949             : 
    1950           0 :         fd_accdb_snapshot_load_begin( ctx->accdb );
    1951           0 :       } else {
    1952             :         /* Create a child fork for incremental writes.  On failure,
    1953             :            fd_accdb_purge(child) reverts just the incremental changes.
    1954             :            On success, fd_accdb_advance_root(child) promotes them.  The
    1955             :            stake delegations get a fork likewise. */
    1956           0 :         ctx->lead.accdb_incr_fork_id = fd_accdb_attach_child( ctx->accdb, ctx->lead.accdb_root_fork_id );
    1957           0 :         stake_fork                   = fd_stake_delegations_new_fork( ctx->stake_delegations, USHORT_MAX );
    1958           0 :       }
    1959             : 
    1960             :       /* Save the slot advertised by the snapshot peer and verify it
    1961             :          against the slot in the snapshot manifest.  For redirect-based
    1962             :          HTTP downloads, these are initial estimates from gossip and
    1963             :          will be updated by the META message below once the redirect
    1964             :          resolves to a concrete snapshot filename. */
    1965           0 :       fd_ssctrl_init_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].wksp, chunk );
    1966           0 :       ctx->lead.advertised_slot = msg->slot;
    1967           0 :       fd_memcpy( ctx->lead.advertised_hash, msg->snapshot_hash, FD_HASH_FOOTPRINT );
    1968           0 :       ctx->lead.init_completed = 1;
    1969             : 
    1970             :       /* Reset shared state before publishing the fork. */
    1971           0 :       fd_memset( &ctx->shmem->values, 0, sizeof(ctx->shmem->values) );
    1972           0 :       FD_VOLATILE( ctx->shmem->next_appendvec_ticket ) = 0UL;
    1973           0 :       FD_COMPILER_MFENCE();
    1974             : 
    1975             :       /* Publish before acknowledging INIT. */
    1976           0 :       FD_VOLATILE( ctx->shmem->fork_id    ) = ctx->full ? (ulong)USHORT_MAX : (ulong)ctx->lead.accdb_incr_fork_id.val;
    1977           0 :       FD_VOLATILE( ctx->shmem->stake_fork ) = stake_fork;
    1978           0 :       FD_COMPILER_MFENCE();
    1979           0 :       break;
    1980           0 :     }
    1981             : 
    1982           0 :     case FD_SNAPSHOT_MSG_META: {
    1983           0 :       forward_msg = 0; /* snapct already receives META directly from snapld */
    1984           0 :       if( FD_LIKELY( !is_lead( ctx ) ) ) break;
    1985             : 
    1986             :       /* For redirect-based HTTP downloads, the META message carries
    1987             :          the resolved slot and hash from the actual snapshot filename
    1988             :          the server redirected to.  Update the advertised values so
    1989             :          that process_manifest can verify the manifest against them. */
    1990           0 :       FD_TEST( sz==sizeof(fd_ssctrl_meta_t) );
    1991           0 :       fd_ssctrl_meta_t const * meta = fd_chunk_to_laddr_const( ctx->in[ in_idx ].wksp, chunk );
    1992           0 :       if( meta->resolved_slot!=ULONG_MAX ) {
    1993           0 :         ctx->lead.advertised_slot = meta->resolved_slot;
    1994           0 :         fd_memcpy( ctx->lead.advertised_hash, meta->resolved_hash, FD_HASH_FOOTPRINT );
    1995           0 :       }
    1996           0 :       break;
    1997           0 :     }
    1998             : 
    1999           0 :     case FD_SNAPSHOT_MSG_CTRL_FINI: {
    2000             :       /* This is a special case: handle_data_frag must have already
    2001             :          processed FD_SSPARSE_ADVANCE_DONE and moved the state into
    2002             :          FD_SNAPSHOT_STATE_FINISHING.  Otherwise, treat this as a
    2003             :          malformed snapshot so that the pipeline can retry. */
    2004           0 :       if( FD_UNLIKELY( ctx->state!=FD_SNAPSHOT_STATE_FINISHING ) ) {
    2005           0 :         FD_LOG_WARNING(( "received FINI while in state %s (%lu), expected FINISHING (possibly truncated tar stream)",
    2006           0 :                          fd_ssctrl_state_str( (ulong)ctx->state ), (ulong)ctx->state ));
    2007           0 :         transition_malformed( ctx, stem );
    2008           0 :         forward_msg = 0;
    2009           0 :         break;
    2010           0 :       }
    2011             : 
    2012             :       /* Flush records before the FINI ack. */
    2013           0 :       if( FD_UNLIKELY( writer_flush( ctx ) ) ) {
    2014           0 :         transition_malformed( ctx, stem );
    2015           0 :         forward_msg = 0;
    2016           0 :         break;
    2017           0 :       }
    2018             : 
    2019           0 :       fd_accdb_flush_metrics( ctx->accdb );
    2020             : 
    2021             :       /* Publish prior total updates before the FINI ack. */
    2022           0 :       FD_COMPILER_MFENCE(); /* publish before ack */
    2023             : 
    2024             :       /* Keep per-tile gauges. Dashboards sum them. */
    2025           0 :       break;
    2026           0 :     }
    2027             : 
    2028           0 :     case FD_SNAPSHOT_MSG_CTRL_NEXT: {
    2029           0 :       FD_TEST( ctx->state==FD_SNAPSHOT_STATE_FINISHING );
    2030           0 :       ctx->state = FD_SNAPSHOT_STATE_IDLE;
    2031             : 
    2032             :       /* Backup metric counters */
    2033           0 :       ctx->metrics.full_accounts_loaded   = ctx->metrics.accounts_loaded;
    2034           0 :       ctx->metrics.full_accounts_replaced = ctx->metrics.accounts_replaced;
    2035           0 :       ctx->metrics.full_accounts_ignored  = ctx->metrics.accounts_ignored;
    2036             : 
    2037             :       /* Tile 0 only. */
    2038           0 :       if( FD_LIKELY( !is_lead( ctx ) ) ) break;
    2039             : 
    2040             :       /* FINI acks make shared data stable. */
    2041           0 :       if( FD_UNLIKELY( verify_sysvars( ctx ) ) ) {
    2042           0 :         FD_LOG_WARNING(( "sysvar verification failed for full snapshot" ));
    2043           0 :         transition_malformed( ctx, stem );
    2044           0 :         forward_msg = 0;
    2045           0 :         break;
    2046           0 :       }
    2047             : 
    2048           0 :       if( FD_UNLIKELY( validate_capitalization( ctx ) ) ) {
    2049           0 :         transition_malformed( ctx, stem );
    2050           0 :         forward_msg = 0;
    2051           0 :         break;
    2052           0 :       }
    2053             : 
    2054           0 :       fold_account_counts( ctx );
    2055           0 :       ctx->lead.recovery.capitalization    = ctx->lead.manifest_capitalization;
    2056           0 :       fd_accdb_snapshot_save_whead( ctx->accdb, &ctx->lead.recovery.accdb_metadata );
    2057           0 :       ctx->lead.init_completed = 0;
    2058           0 :       break;
    2059           0 :     }
    2060             : 
    2061           0 :     case FD_SNAPSHOT_MSG_CTRL_DONE: {
    2062           0 :       FD_TEST( ctx->state==FD_SNAPSHOT_STATE_FINISHING );
    2063           0 :       ctx->state = FD_SNAPSHOT_STATE_IDLE;
    2064           0 :       if( FD_LIKELY( !is_lead( ctx ) ) ) break;
    2065             : 
    2066           0 :       if( FD_UNLIKELY( verify_sysvars( ctx ) ) ) {
    2067           0 :         if( ctx->full ) FD_LOG_WARNING(( "sysvar verification failed for full snapshot" ));
    2068           0 :         else            FD_LOG_WARNING(( "sysvar verification failed for incremental snapshot" ));
    2069           0 :         transition_malformed( ctx, stem );
    2070           0 :         forward_msg = 0;
    2071           0 :         break;
    2072           0 :       }
    2073             : 
    2074           0 :       if( FD_UNLIKELY( validate_capitalization( ctx ) ) ) {
    2075           0 :         transition_malformed( ctx, stem );
    2076           0 :         forward_msg = 0;
    2077           0 :         break;
    2078           0 :       }
    2079             : 
    2080           0 :       fold_account_counts( ctx );
    2081           0 :       if( !ctx->full ) {
    2082           0 :         fd_accdb_snapshot_recover_delta( ctx->accdb, ctx->lead.accdb_incr_fork_id );
    2083             :         /* ensure that snapin tile sees all delta changes before rooting */
    2084           0 :         __atomic_thread_fence( __ATOMIC_SEQ_CST );
    2085           0 :         fd_accdb_advance_root( ctx->accdb, ctx->lead.accdb_incr_fork_id );
    2086           0 :         ctx->lead.accdb_root_fork_id = ctx->lead.accdb_incr_fork_id;
    2087           0 :         ctx->lead.accdb_incr_fork_id = (fd_accdb_fork_id_t){ .val = USHORT_MAX };
    2088           0 :         ushort stake_fork = FD_VOLATILE_CONST( ctx->shmem->stake_fork );
    2089           0 :         fd_stake_delegations_advance_root( 0UL, NULL, NULL, 1, ctx->stake_delegations, stake_fork, NULL );
    2090           0 :         fd_stake_delegations_evict_fork( ctx->stake_delegations, stake_fork );
    2091           0 :       }
    2092             : 
    2093           0 :       fd_accdb_snapshot_load_end( ctx->accdb );
    2094             : 
    2095             :       /* TODO: Pass in tile_idx and tile_cnt when parallelizing snapin */
    2096           0 :       fd_features_restore_chunk( &ctx->lead.bank->f.features, ctx->accdb, ctx->lead.accdb_root_fork_id, ctx->lead.bank_slot, &ctx->lead.epoch_schedule, 0UL, 1UL );
    2097             : 
    2098             :       /* Notify replay when snapshot is fully loaded and verified. */
    2099           0 :       fd_stem_publish( stem, ctx->lead.manifest_out.idx, fd_ssmsg_sig( FD_SSMSG_DONE ), 0UL, 0UL, 0UL, 0UL, 0UL );
    2100           0 :       ctx->lead.init_completed = 0;
    2101           0 :       break;
    2102           0 :     }
    2103             : 
    2104           0 :     case FD_SNAPSHOT_MSG_CTRL_ERROR: {
    2105           0 :       FD_TEST( ctx->state!=FD_SNAPSHOT_STATE_SHUTDOWN );
    2106           0 :       ctx->state = FD_SNAPSHOT_STATE_ERROR;
    2107           0 :       break;
    2108           0 :     }
    2109             : 
    2110           0 :     case FD_SNAPSHOT_MSG_CTRL_FAIL: {
    2111           0 :       FD_TEST( ctx->state!=FD_SNAPSHOT_STATE_SHUTDOWN );
    2112           0 :       fd_accdb_flush_metrics( ctx->accdb );
    2113             : 
    2114           0 :       FD_COMPILER_MFENCE();
    2115             : 
    2116             :       /* Reset the worker state for this full/incr loading attempt */
    2117           0 :       reset_attempt_state( ctx );
    2118             : 
    2119             :       /* Defer rollback until the next INIT, which is triggered after
    2120             :          all workers have sent their FAIL acks. */
    2121           0 :       if( FD_UNLIKELY( is_lead( ctx ) ) ) {
    2122           0 :         FD_VOLATILE( ctx->shmem->fork_id ) = ULONG_MAX;
    2123           0 :         FD_COMPILER_MFENCE();
    2124             : 
    2125             :         /* Only a completed INIT has valid state to roll back. */
    2126           0 :         if( FD_LIKELY( ctx->lead.init_completed ) ) {
    2127           0 :           ctx->lead.rollback.pending = 1;
    2128           0 :           ctx->lead.rollback.full    = ctx->full;
    2129           0 :           ctx->lead.rollback.fork    = ctx->lead.accdb_incr_fork_id;
    2130           0 :           if( ctx->full ) ctx->lead.accdb_root_fork_id = (fd_accdb_fork_id_t){ .val = USHORT_MAX };
    2131           0 :           ctx->lead.accdb_incr_fork_id = (fd_accdb_fork_id_t){ .val = USHORT_MAX };
    2132           0 :         }
    2133           0 :         ctx->lead.init_completed = 0;
    2134           0 :       }
    2135             : 
    2136           0 :       ctx->state = FD_SNAPSHOT_STATE_IDLE;
    2137           0 :       break;
    2138           0 :     }
    2139             : 
    2140           0 :     case FD_SNAPSHOT_MSG_CTRL_SHUTDOWN: {
    2141           0 :       FD_TEST( ctx->state==FD_SNAPSHOT_STATE_IDLE );
    2142           0 :       ctx->state = FD_SNAPSHOT_STATE_SHUTDOWN;
    2143           0 :       break;
    2144           0 :     }
    2145             : 
    2146           0 :     default: {
    2147           0 :       FD_LOG_ERR(( "unexpected control frag %s (%lu) in state %s (%lu)",
    2148           0 :                    fd_ssctrl_msg_ctrl_str( sig ), sig,
    2149           0 :                    fd_ssctrl_state_str( (ulong)ctx->state ), (ulong)ctx->state ));
    2150           0 :       break;
    2151           0 :     }
    2152           0 :   }
    2153             : 
    2154             :   /* Forward the control message down the pipeline */
    2155           0 :   if( FD_LIKELY( forward_msg ) ) {
    2156           0 :     fd_stem_publish( stem, ctx->ct_out.idx, sig, 0UL, 0UL, 0UL, 0UL, 0UL );
    2157           0 :   }
    2158           0 : }
    2159             : 
    2160             : static inline int
    2161           0 : all_controls_seen( fd_snapin_tile_t const * ctx ) {
    2162           0 :   int all_seen = 1;
    2163           0 :   for( ulong i=0UL; i<ctx->lane_cnt; i++ ) {
    2164           0 :     all_seen &= !!ctx->control_seen[ i ];
    2165           0 :   }
    2166           0 :   return all_seen;
    2167           0 : }
    2168             : 
    2169             : static inline int
    2170             : before_frag( fd_snapin_tile_t * ctx,
    2171             :              ulong              in_idx,
    2172             :              ulong              seq    FD_PARAM_UNUSED,
    2173           0 :              ulong              sig ) {
    2174             :   /* If we're currently in ERROR state we should only process FAIL
    2175             :      control frags */
    2176           0 :   if( FD_UNLIKELY( ctx->state==FD_SNAPSHOT_STATE_ERROR ) ) {
    2177           0 :     return sig!=FD_SNAPSHOT_MSG_CTRL_FAIL;
    2178           0 :   }
    2179             : 
    2180           0 :   if( FD_UNLIKELY( sig==FD_SNAPSHOT_MSG_CTRL_ERROR ) ) {
    2181           0 :     return 0;
    2182           0 :   }
    2183             : 
    2184             :   /* Once this lane sends the pending control, hold its later frags
    2185             :      until all snapdc lanes send the same control. */
    2186           0 :   if( FD_UNLIKELY( ctx->pending_control!=ULONG_MAX && ctx->control_seen[ in_idx ] ) ) {
    2187           0 :     FD_TEST( sig!=ctx->pending_control );
    2188           0 :     return -1;
    2189           0 :   }
    2190             : 
    2191           0 :   if( FD_LIKELY( fd_snapdc_sig_type( sig )==FD_SNAPSHOT_MSG_DATA ) ) {
    2192             :     /* Only accept DATA frags for the expected frame */
    2193           0 :     ulong frame = fd_snapdc_sig_frame( sig );
    2194           0 :     FD_CHECK_ERR( frame>=ctx->expected_frame, "snapdc lane repeated a frame" );
    2195           0 :     if( FD_UNLIKELY( frame!=ctx->expected_frame ) ) return -1;
    2196           0 :   }
    2197             : 
    2198           0 :   return 0;
    2199           0 : }
    2200             : 
    2201             : static inline int
    2202             : handle_lane_data_frag( fd_snapin_tile_t *  ctx,
    2203             :                        fd_stem_context_t * stem,
    2204             :                        ulong               in_idx,
    2205             :                        ulong               chunk,
    2206             :                        ulong               sz,
    2207           0 :                        ulong               ctl ) {
    2208           0 :   if( FD_UNLIKELY( ctx->incr_fork==ULONG_MAX ) ) {
    2209           0 :     start_processing_attempt( ctx );
    2210           0 :   }
    2211             : 
    2212             :   /* EOM marks the end of a frame */
    2213           0 :   int eom = !!fd_frag_meta_ctl_eom( ctl );
    2214             : 
    2215             :   /* The tar parser can reach EOF before snapdc reports the end of the
    2216             :      zstd frame.  Only the empty EOM is valid (any payload after EOF is
    2217             :      malformed). */
    2218           0 :   int trailing_eom = ctx->state==FD_SNAPSHOT_STATE_FINISHING && eom && !sz;
    2219           0 :   if( FD_UNLIKELY( !trailing_eom && handle_data_frag( ctx, in_idx, chunk, sz, stem ) ) ) {
    2220           0 :     return 1;
    2221           0 :   }
    2222             : 
    2223           0 :   if( FD_UNLIKELY( eom ) ) {
    2224           0 :     ctx->expected_frame++;
    2225           0 :   }
    2226             : 
    2227           0 :   return 0;
    2228           0 : }
    2229             : 
    2230             : static inline void
    2231             : handle_control_barrier( fd_snapin_tile_t *  ctx,
    2232             :                         fd_stem_context_t * stem,
    2233             :                         ulong               in_idx,
    2234             :                         ulong               sig,
    2235             :                         ulong               chunk,
    2236           0 :                         ulong               sz ) {
    2237             :   /* Error control frags must be immediately handled. */
    2238           0 :   if( FD_UNLIKELY( sig==FD_SNAPSHOT_MSG_CTRL_ERROR ) ) {
    2239           0 :     handle_control_frag( ctx, stem, in_idx, sig, chunk, sz );
    2240           0 :     return;
    2241           0 :   }
    2242             : 
    2243           0 :   if( FD_UNLIKELY( sig!=ctx->pending_control ) ) {
    2244           0 :     FD_TEST( ctx->pending_control==ULONG_MAX || sig==FD_SNAPSHOT_MSG_CTRL_FAIL );
    2245           0 :     clear_control_barrier( ctx );
    2246           0 :     ctx->pending_control = sig;
    2247           0 :   }
    2248             : 
    2249             :   /* Only process the control frag when all upstream tiles have sent
    2250             :      the same control message. */
    2251           0 :   FD_TEST( !ctx->control_seen[ in_idx ] );
    2252           0 :   ctx->control_seen[ in_idx ] = 1U;
    2253           0 :   if( FD_LIKELY( !all_controls_seen( ctx ) ) ) {
    2254           0 :     return;
    2255           0 :   }
    2256             : 
    2257             :   /* All controls received, process the control frag. */
    2258           0 :   clear_control_barrier( ctx );
    2259           0 :   handle_control_frag( ctx, stem, in_idx, sig, chunk, sz );
    2260           0 : }
    2261             : 
    2262             : static inline int
    2263             : returnable_frag( fd_snapin_tile_t *  ctx,
    2264             :                  ulong               in_idx,
    2265             :                  ulong               seq    FD_PARAM_UNUSED,
    2266             :                  ulong               sig,
    2267             :                  ulong               chunk,
    2268             :                  ulong               sz,
    2269             :                  ulong               ctl,
    2270             :                  ulong               tsorig FD_PARAM_UNUSED,
    2271             :                  ulong               tspub  FD_PARAM_UNUSED,
    2272           0 :                  fd_stem_context_t * stem ) {
    2273           0 :   FD_TEST( ctx->state!=FD_SNAPSHOT_STATE_SHUTDOWN );
    2274             : 
    2275           0 :   if( FD_LIKELY( fd_snapdc_sig_type( sig )==FD_SNAPSHOT_MSG_DATA ) ) return handle_lane_data_frag( ctx, stem, in_idx, chunk, sz, ctl );
    2276           0 :   else                                                               handle_control_barrier( ctx, stem, in_idx, sig, chunk, sz );
    2277             : 
    2278           0 :   return 0;
    2279           0 : }
    2280             : 
    2281             : static ulong
    2282             : populate_allowed_fds( fd_topo_t      const * topo,
    2283             :                       fd_topo_tile_t const * tile,
    2284             :                       ulong                  out_fds_cnt,
    2285           0 :                       int *                  out_fds ) {
    2286           0 :   if( FD_UNLIKELY( out_fds_cnt<6UL ) ) FD_LOG_ERR(( "invalid out_fds_cnt %lu", out_fds_cnt ));
    2287           0 :   fd_snapin_tile_t const * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    2288             : 
    2289           0 :   ulong out_cnt = 0;
    2290           0 :   out_fds[ out_cnt++ ] = 2UL; /* stderr */
    2291           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) ) {
    2292           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
    2293           0 :   }
    2294           0 :   out_fds[ out_cnt++ ] = FD_ACCDB_FD_RW; /* accounts db */
    2295           0 :   out_fds[ out_cnt++ ] = FD_STAKE_DELEGATIONS_FD; /* stake delegation disk spill */
    2296           0 :   out_fds[ out_cnt++ ] = ctx->writer.accdb_direct_fd; /* accounts db, O_DIRECT */
    2297           0 :   if( FD_LIKELY( !tile->kind_id ) ) out_fds[ out_cnt++ ] = FD_TXNCACHE_FD;
    2298             : 
    2299           0 :   return out_cnt;
    2300           0 : }
    2301             : 
    2302             : static ulong
    2303             : populate_allowed_seccomp( fd_topo_t const *      topo,
    2304             :                           fd_topo_tile_t const * tile,
    2305             :                           ulong                  out_cnt,
    2306           0 :                           struct sock_filter *   out ) {
    2307           0 :   fd_snapin_tile_t const * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    2308           0 :   populate_sock_filter_policy_fd_snapin_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), FD_ACCDB_FD_RW, (uint)ctx->writer.accdb_direct_fd, FD_STAKE_DELEGATIONS_FD, (uint)FD_TXNCACHE_FD );
    2309           0 :   return sock_filter_policy_fd_snapin_tile_instr_cnt;
    2310           0 : }
    2311             : 
    2312             : static void
    2313             : privileged_init( fd_topo_t const *      topo,
    2314           0 :                  fd_topo_tile_t const * tile ) {
    2315           0 :   fd_snapin_tile_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    2316           0 :   memset( ctx, 0, sizeof(fd_snapin_tile_t) );
    2317           0 :   FD_TEST( fd_rng_secure( &ctx->lead.seed, 8UL ) );
    2318             : 
    2319           0 :   char path[ 64 ];
    2320           0 :   FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "/proc/self/fd/%d", FD_ACCDB_FD_RW ) );
    2321           0 :   ctx->writer.accdb_direct_fd = open( path, O_WRONLY|O_DIRECT|O_CLOEXEC );
    2322           0 :   FD_CHECK_ERR( ctx->writer.accdb_direct_fd>=0, "open(accounts.db, O_DIRECT) failed; the filesystem holding [paths.accounts] must support direct IO" );
    2323           0 : }
    2324             : 
    2325             : static inline fd_snapin_out_link_t
    2326             : out1( fd_topo_t const *      topo,
    2327             :       fd_topo_tile_t const * tile,
    2328             :       char const *           name,
    2329           0 :       ulong                  kind_id ) {
    2330           0 :   ulong idx = fd_topo_find_tile_out_link( topo, tile, name, kind_id );
    2331             : 
    2332           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 };
    2333             : 
    2334           0 :   ulong mtu = topo->links[ tile->out_link_id[ idx ] ].mtu;
    2335           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 };
    2336             : 
    2337           0 :   void * mem   = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp;
    2338           0 :   ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache );
    2339           0 :   ulong wmark  = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, mtu );
    2340           0 :   return (fd_snapin_out_link_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0, .mtu = mtu };
    2341           0 : }
    2342             : 
    2343             : static void
    2344             : unprivileged_init( fd_topo_t const *      topo,
    2345           0 :                    fd_topo_tile_t const * tile ) {
    2346           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    2347             : 
    2348           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    2349           0 :   fd_snapin_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_snapin_tile_t), sizeof(fd_snapin_tile_t) );
    2350           0 :   void * _accdb          = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(),          fd_accdb_footprint( tile->snapin.max_live_slots, 0 ) );
    2351             : 
    2352           0 :   ctx->tile_idx = tile->kind_id;
    2353           0 :   if( FD_UNLIKELY( ctx->tile_idx>=FD_TOPO_MAX_TILE_IN_LINKS ) ) FD_LOG_ERR(( "tile `" NAME "` has unsupported kind id %lu", tile->kind_id ));
    2354             : 
    2355           0 :   ctx->full     = 1;
    2356           0 :   ctx->state    = FD_SNAPSHOT_STATE_IDLE;
    2357           0 :   ctx->lane_cnt = tile->in_cnt;
    2358           0 :   clear_control_barrier( ctx );
    2359           0 :   fd_memset( &ctx->metrics, 0, sizeof(ctx->metrics) );
    2360             : 
    2361           0 :   void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->snapin.accdb_obj_id );
    2362           0 :   fd_accdb_shmem_t * accdb_shmem = fd_accdb_shmem_join( _accdb_shmem );
    2363           0 :   FD_TEST( accdb_shmem );
    2364             : 
    2365           0 :   fd_sleep_t * accdb_sleep = topo->sleep_obj_id!=ULONG_MAX ? fd_sleep_join( fd_topo_obj_laddr( topo, topo->sleep_obj_id ) ) : NULL;
    2366           0 :   ctx->accdb = fd_accdb_join( fd_accdb_new( _accdb, accdb_shmem, FD_ACCDB_FD_RW, 0UL, NULL, accdb_sleep, fd_topo_find_tile( topo, "accdb", 0UL ), 0 ) );
    2367           0 :   FD_TEST( ctx->accdb );
    2368             : 
    2369           0 :   ctx->shmem = fd_topo_obj_laddr( topo, tile->snapin.shmem_obj_id );
    2370             : 
    2371             :   /* Every tile updates stakes. Only tile 0 owns the bank. */
    2372           0 :   fd_banks_t * banks = fd_banks_join( fd_topo_obj_laddr( topo, tile->snapin.banks_obj_id ) );
    2373           0 :   FD_TEST( banks );
    2374           0 :   ctx->stake_delegations = fd_banks_stake_delegations_root_query( banks );
    2375           0 :   FD_TEST( ctx->stake_delegations );
    2376             : 
    2377           0 :   ctx->ct_out = out1( topo, tile, "snapin_ct", ctx->tile_idx );
    2378           0 :   if( FD_UNLIKELY( ctx->ct_out.idx==ULONG_MAX ) ) FD_LOG_ERR(( "tile `" NAME ":%lu` missing required out link `snapin_ct`", ctx->tile_idx ));
    2379             : 
    2380           0 :   ctx->gui_out = out1( topo, tile, "snapin_gui", ctx->tile_idx );
    2381             : 
    2382           0 :   for( ulong i=0UL; i<ctx->lane_cnt; i++ ) {
    2383           0 :     fd_topo_link_t const * in_link = &topo->links[ tile->in_link_id[ i ] ];
    2384           0 :     FD_TEST( 0==strcmp( in_link->name, "snapdc_in" ) );
    2385           0 :     FD_TEST( in_link->kind_id==i );
    2386           0 :     fd_topo_wksp_t const * in_wksp = &topo->workspaces[ topo->objs[ in_link->dcache_obj_id ].wksp_id ];
    2387           0 :     ctx->in[ i ].wksp   = in_wksp->wksp;
    2388           0 :     ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].wksp, in_link->dcache );
    2389           0 :     ctx->in[ i ].wmark  = fd_dcache_compact_wmark( ctx->in[ i ].wksp, in_link->dcache, in_link->mtu );
    2390           0 :     ctx->in[ i ].mtu    = in_link->mtu;
    2391           0 :     ctx->in[ i ].pos    = 0UL;
    2392           0 :   }
    2393             : 
    2394           0 :   reset_attempt_state( ctx );
    2395           0 :   if( FD_LIKELY( !is_lead( ctx ) ) ) return;
    2396             : 
    2397             :   /* Tile 0 state. */
    2398           0 :   ctx->lead.init_completed = 0;
    2399           0 :   ctx->lead.txncache_max_groups_per_slot = tile->snapin.max_txn_per_slot;
    2400           0 :   ctx->lead.txncache_max_entries_per_slot = 2UL*tile->snapin.max_txn_per_slot;
    2401             : 
    2402           0 :   void * _txncache           = FD_SCRATCH_ALLOC_APPEND( l, fd_txncache_align(),               fd_txncache_footprint( tile->snapin.max_live_slots ) );
    2403           0 :   void * _manifest_parser    = FD_SCRATCH_ALLOC_APPEND( l, fd_ssmanifest_parser_align(),      fd_ssmanifest_parser_footprint()                             );
    2404           0 :   void * _sd_parser          = FD_SCRATCH_ALLOC_APPEND( l, fd_slot_delta_parser_align(),      fd_slot_delta_parser_footprint()                             );
    2405           0 :   ctx->lead.recent_groups    = FD_SCRATCH_ALLOC_APPEND( l, alignof(recent_blockhash_group_t), sizeof(recent_blockhash_group_t)*FD_SNAPIN_MAX_RECENT_GROUPS );
    2406           0 :   ctx->lead.txncache_entries = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sstxncache_hash_t),     sizeof(fd_sstxncache_hash_t)*FD_TXNCACHE_MAX_SLOT_DELTAS*2UL*tile->snapin.max_txn_per_slot );
    2407           0 :   ctx->lead.blockhash_groups = FD_SCRATCH_ALLOC_APPEND( l, FD_TXNCACHE_SNAPIN_STAGE_ALIGN,    txncache_staging_row_sz( tile->snapin.max_txn_per_slot )     );
    2408             : 
    2409           0 :   void * _txncache_shmem = fd_topo_obj_laddr( topo, tile->snapin.txncache_obj_id );
    2410           0 :   fd_txncache_shmem_t * txncache_shmem = fd_txncache_shmem_join( _txncache_shmem );
    2411           0 :   FD_TEST( txncache_shmem );
    2412           0 :   ctx->lead.txncache = fd_txncache_join( fd_txncache_new( _txncache, txncache_shmem, FD_TXNCACHE_FD ) );
    2413           0 :   FD_TEST( ctx->lead.txncache );
    2414             : 
    2415           0 :   txncache_staging_reset( ctx );
    2416             : 
    2417           0 :   ctx->lead.bank = fd_banks_init_bank( banks );
    2418           0 :   FD_TEST( ctx->lead.bank );
    2419           0 :   FD_TEST( ctx->lead.bank->idx==0UL );
    2420             : 
    2421           0 :   ctx->lead.manifest_parser = fd_ssmanifest_parser_join( fd_ssmanifest_parser_new( _manifest_parser ) );
    2422           0 :   FD_TEST( ctx->lead.manifest_parser );
    2423             : 
    2424           0 :   ctx->lead.slot_delta_parser = fd_slot_delta_parser_join( fd_slot_delta_parser_new( _sd_parser ) );
    2425           0 :   FD_TEST( ctx->lead.slot_delta_parser );
    2426             : 
    2427           0 :   ctx->lead.manifest_out = out1( topo, tile, "snapin_manif", 0UL );
    2428           0 :   if( FD_UNLIKELY( ctx->lead.manifest_out.idx==ULONG_MAX ) ) FD_LOG_ERR(( "tile `" NAME "` missing required out link `snapin_manif`" ));
    2429             : 
    2430           0 :   fd_ssmanifest_parser_init( ctx->lead.manifest_parser, fd_chunk_to_laddr( ctx->lead.manifest_out.mem, ctx->lead.manifest_out.chunk ) );
    2431           0 :   fd_slot_delta_parser_init( ctx->lead.slot_delta_parser );
    2432             : 
    2433           0 :   ctx->lead.advertised_slot = 0UL;
    2434           0 :   ctx->lead.bank_slot       = 0UL;
    2435           0 :   ctx->lead.epoch           = 0UL;
    2436           0 :   ctx->lead.full_genesis_creation_time_seconds = 0UL;
    2437           0 :   ctx->lead.manifest_capitalization            = 0UL;
    2438           0 :   ctx->lead.recovery.capitalization            = 0UL;
    2439           0 :   fd_memset( &ctx->lead.account_counts, 0, sizeof(ctx->lead.account_counts) );
    2440           0 :   ctx->lead.accdb_root_fork_id = (fd_accdb_fork_id_t){ .val = USHORT_MAX };
    2441           0 :   ctx->lead.accdb_incr_fork_id = (fd_accdb_fork_id_t){ .val = USHORT_MAX };
    2442           0 :   fd_memset( &ctx->lead.recovery.accdb_metadata, 0, sizeof(ctx->lead.recovery.accdb_metadata) );
    2443           0 :   fd_memset( &ctx->lead.flags, 0, sizeof(ctx->lead.flags) );
    2444           0 :   ctx->lead.boot_timestamp = fd_log_wallclock();
    2445             : 
    2446           0 :   ctx->wfs_slot          = tile->snapin.wait_for_supermajority_at_slot;
    2447           0 :   ctx->wfs_bank_hash     = tile->snapin.wait_for_supermajority_with_bank_hash;
    2448           0 :   ctx->wfs_shred_version = tile->snapin.expected_shred_version;
    2449           0 :   ctx->wfs_hash_is_zero  = !memcmp( ctx->wfs_bank_hash.uc, ((fd_hash_t){0}).uc, sizeof(fd_hash_t) );
    2450           0 : }
    2451             : 
    2452             : /* There are 3 output links that affect the calculation of STEM_BURST:
    2453             :     1. snapin_ct    - worst case: 1 message (ack or unsolicited ERROR)
    2454             :     2. snapin_manif - worst case: 1 message (tile 0 only)
    2455             :     3. snapin_gui   - worst case: 1 message (config program account)
    2456             :    The STEM_BURST is the max value across these 3 links (not the sum). */
    2457           0 : #define STEM_BURST 1UL
    2458             : 
    2459           0 : #define STEM_LAZY  (128L*3000L)
    2460             : 
    2461           0 : #define STEM_CALLBACK_CONTEXT_TYPE  fd_snapin_tile_t
    2462           0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_snapin_tile_t)
    2463             : 
    2464           0 : #define STEM_CALLBACK_SHOULD_SHUTDOWN should_shutdown
    2465           0 : #define STEM_CALLBACK_METRICS_WRITE   metrics_write
    2466           0 : #define STEM_CALLBACK_BEFORE_FRAG     before_frag
    2467           0 : #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
    2468             : 
    2469             : #include "../../disco/stem/fd_stem.c"
    2470             : 
    2471             : static ulong
    2472           0 : max_event_sz( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
    2473           0 :   return sizeof(fd_event_accdb_partition_added_t);
    2474           0 : }
    2475             : 
    2476             : static ulong
    2477             : snapin_shmem_footprint( fd_topo_t const *     topo FD_PARAM_UNUSED,
    2478           0 :                         fd_topo_obj_t const * obj  FD_PARAM_UNUSED ) {
    2479           0 :   return sizeof(fd_snapin_shmem_t);
    2480           0 : }
    2481             : 
    2482             : static ulong
    2483             : snapin_shmem_align( fd_topo_t const *     topo FD_PARAM_UNUSED,
    2484           0 :                     fd_topo_obj_t const * obj  FD_PARAM_UNUSED ) {
    2485           0 :   return alignof(fd_snapin_shmem_t);
    2486           0 : }
    2487             : 
    2488             : static void
    2489             : snapin_shmem_new( fd_topo_t const *     topo,
    2490           0 :                   fd_topo_obj_t const * obj ) {
    2491           0 :   fd_snapin_shmem_t * shmem = fd_topo_obj_laddr( topo, obj->id );
    2492           0 :   fd_memset( shmem, 0, sizeof(fd_snapin_shmem_t) );
    2493           0 :   shmem->fork_id = ULONG_MAX;
    2494           0 : }
    2495             : 
    2496             : fd_topo_obj_callbacks_t fd_obj_cb_snapin_shmem = {
    2497             :   .name      = "snapin_shmem",
    2498             :   .footprint = snapin_shmem_footprint,
    2499             :   .align     = snapin_shmem_align,
    2500             :   .new       = snapin_shmem_new,
    2501             : };
    2502             : 
    2503             : fd_topo_run_tile_t fd_tile_snapin = {
    2504             :   .name                     = NAME,
    2505             :   .populate_allowed_fds     = populate_allowed_fds,
    2506             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    2507             :   .scratch_align            = scratch_align,
    2508             :   .scratch_footprint        = scratch_footprint,
    2509             :   .privileged_init          = privileged_init,
    2510             :   .unprivileged_init        = unprivileged_init,
    2511             :   .max_event_sz             = max_event_sz,
    2512             :   .run                      = stem_run,
    2513             : };
    2514             : 
    2515             : #undef NAME

Generated by: LCOV version 1.14