LCOV - code coverage report
Current view: top level - discof/restore/utils - fd_ssload.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 325 402 80.8 %
Date: 2026-09-17 04:28:31 Functions: 3 5 60.0 %

          Line data    Source code
       1             : #include "fd_ssload.h"
       2             : 
       3             : #include "../../../disco/genesis/fd_genesis_cluster.h"
       4             : #include "../../../flamenco/runtime/fd_runtime_const.h"
       5             : #include "../../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h"
       6             : #include "fd_ssmsg.h"
       7             : 
       8             : FD_STATIC_ASSERT( FD_HARD_FORKS_MAX==sizeof(((fd_snapshot_manifest_t *)0)->hard_forks)/sizeof(fd_hard_fork_t), hard_forks_max );
       9             : FD_STATIC_ASSERT( FD_BLOCKHASHES_MAX==sizeof(((fd_snapshot_manifest_t *)0)->blockhashes)/sizeof(fd_snapshot_manifest_blockhash_t), blockhashes_max );
      10             : FD_STATIC_ASSERT( FD_RUNTIME_MAX_SNAPSHOT_VOTE_ACCOUNTS==sizeof(((fd_snapshot_manifest_t *)0)->vote_accounts)/sizeof(fd_snapshot_manifest_vote_account_t), vote_accounts_max );
      11             : FD_STATIC_ASSERT( FD_RUNTIME_MANIFEST_EPOCH_STAKES_LEN==sizeof(((fd_snapshot_manifest_t *)0)->epoch_stakes)/sizeof(fd_snapshot_manifest_epoch_stakes_t), epoch_stakes_len );
      12             : FD_STATIC_ASSERT( FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS==sizeof(((fd_snapshot_manifest_epoch_stakes_t *)0)->vote_stakes)/sizeof(fd_snapshot_manifest_vote_stakes_t), epoch_vote_stakes_max );
      13             : FD_STATIC_ASSERT( FD_EPOCH_CREDITS_MAX==sizeof(((fd_snapshot_manifest_vote_stakes_t *)0)->epoch_credits)/sizeof(epoch_credits_t), vote_stakes_epoch_credits_max );
      14             : 
      15             : int
      16             : fd_ssload_manifest_validate( fd_snapshot_manifest_t const * manifest,
      17             :                              ulong                          max_vote_accounts,
      18         177 :                              ulong                          max_stake_accounts ) {
      19             : 
      20         177 :   if( FD_UNLIKELY( max_vote_accounts!=FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS ||
      21         177 :                    max_stake_accounts!=FD_RUNTIME_MAX_STAKE_ACCOUNTS ) ) {
      22           6 :     FD_LOG_WARNING(( "banks capacity mismatch: max_vote_accounts=%lu (expected %lu) max_stake_accounts=%lu (expected %lu)",
      23           6 :                      max_vote_accounts,  FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS,
      24           6 :                      max_stake_accounts, FD_RUNTIME_MAX_STAKE_ACCOUNTS ));
      25           6 :     return -1;
      26           6 :   }
      27             : 
      28         171 :   if( FD_UNLIKELY( !manifest->ticks_per_slot ) ) {
      29           3 :     FD_LOG_WARNING(( "corrupt snapshot: ticks_per_slot must be non-zero" ));
      30           3 :     return -1;
      31           3 :   }
      32             : 
      33             :   /* slots_per_epoch must be at least FD_EPOCH_LEN_MIN, so that
      34             :      fd_slot_to_epoch and related functions produce valid data.
      35             :      This check must come before any epoch computation. */
      36             : 
      37         168 :   if( FD_UNLIKELY( manifest->epoch_schedule_params.slots_per_epoch<FD_EPOCH_LEN_MIN ) ) {
      38           6 :     FD_LOG_WARNING(( "corrupt snapshot: slots_per_epoch %lu below minimum %lu",
      39           6 :                      manifest->epoch_schedule_params.slots_per_epoch, FD_EPOCH_LEN_MIN ));
      40           6 :     return -1;
      41           6 :   }
      42             : 
      43         162 :   if( FD_UNLIKELY( manifest->epoch_schedule_params.warmup>1 ) ) {
      44           3 :     FD_LOG_WARNING(( "corrupt snapshot: warmup %u is not boolean", (uint)manifest->epoch_schedule_params.warmup ));
      45           3 :     return -1;
      46           3 :   }
      47             : 
      48             :   /* Validate that the manifest's first_normal_{epoch,slot} are
      49             :      consistent with the derivation from slots_per_epoch and warmup. */
      50             : 
      51         159 :   fd_epoch_schedule_t derived;
      52         159 :   if( FD_UNLIKELY( !fd_epoch_schedule_derive( &derived,
      53         159 :                                               manifest->epoch_schedule_params.slots_per_epoch,
      54         159 :                                               manifest->epoch_schedule_params.leader_schedule_slot_offset,
      55         159 :                                               manifest->epoch_schedule_params.warmup ) ) ) {
      56           3 :     FD_LOG_WARNING(( "corrupt snapshot: fd_epoch_schedule_derive failed" ));
      57           3 :     return -1;
      58           3 :   }
      59         156 :   if( FD_UNLIKELY( derived.first_normal_epoch!=manifest->epoch_schedule_params.first_normal_epoch ) ) {
      60           9 :     FD_LOG_WARNING(( "corrupt snapshot: first_normal_epoch mismatch (manifest=%lu derived=%lu)",
      61           9 :                      manifest->epoch_schedule_params.first_normal_epoch, derived.first_normal_epoch ));
      62           9 :     return -1;
      63           9 :   }
      64         147 :   if( FD_UNLIKELY( derived.first_normal_slot!=manifest->epoch_schedule_params.first_normal_slot ) ) {
      65           0 :     FD_LOG_WARNING(( "corrupt snapshot: first_normal_slot mismatch (manifest=%lu derived=%lu)",
      66           0 :                      manifest->epoch_schedule_params.first_normal_slot, derived.first_normal_slot ));
      67           0 :     return -1;
      68           0 :   }
      69             : 
      70             :   /* Blockhash queue structural validation */
      71             : 
      72         147 :   ulong const age_cnt = manifest->blockhashes_len;
      73         147 :   fd_snapshot_manifest_blockhash_t const * ages = manifest->blockhashes;
      74             : 
      75         147 :   if( FD_UNLIKELY( !age_cnt || age_cnt>FD_BLOCKHASHES_MAX ) ) {
      76           6 :     FD_LOG_WARNING(( "corrupt snapshot: invalid blockhash age count %lu (max %lu)", age_cnt, FD_BLOCKHASHES_MAX ));
      77           6 :     return -1;
      78           6 :   }
      79             : 
      80         141 :   ulong seq_min = ULONG_MAX;
      81        1209 :   for( ulong i=0UL; i<age_cnt; i++ ) {
      82        1068 :     seq_min = fd_ulong_min( seq_min, ages[ i ].hash_index );
      83        1068 :   }
      84         141 :   ulong seq_max;
      85         141 :   if( FD_UNLIKELY( __builtin_uaddl_overflow( seq_min, age_cnt, &seq_max ) ) ) {
      86           6 :     FD_LOG_WARNING(( "corrupt snapshot: blockhash queue sequence number wraparound (seq_min=%lu age_cnt=%lu)", seq_min, age_cnt ));
      87           6 :     return -1;
      88           6 :   }
      89             : 
      90             :   /* Check for gaps and duplicates using a bitset (max 301 entries). */
      91             : 
      92         135 :   ulong seen[ (FD_BLOCKHASHES_MAX+63UL)/64UL ];
      93         135 :   fd_memset( seen, 0, sizeof(seen) );
      94        1188 :   for( ulong i=0UL; i<age_cnt; i++ ) {
      95        1059 :     ulong idx;
      96        1059 :     if( FD_UNLIKELY( __builtin_usubl_overflow( ages[ i ].hash_index, seq_min, &idx ) || idx>=age_cnt ) ) {
      97           3 :       FD_LOG_WARNING(( "corrupt snapshot: gap in blockhash queue (seq=[%lu,%lu) hash_index=%lu)",
      98           3 :                        seq_min, seq_max, ages[ i ].hash_index ));
      99           3 :       return -1;
     100           3 :     }
     101        1056 :     ulong word = idx/64UL;
     102        1056 :     ulong bit  = idx%64UL;
     103        1056 :     if( FD_UNLIKELY( seen[ word ] & (1UL<<bit) ) ) {
     104           3 :       FD_LOG_WARNING(( "corrupt snapshot: duplicate blockhash queue hash_index=%lu (relative_idx=%lu seq_min=%lu)",
     105           3 :                        ages[ i ].hash_index, idx, seq_min ));
     106           3 :       return -1;
     107           3 :     }
     108        1053 :     seen[ word ] |= (1UL<<bit);
     109        1053 :   }
     110             : 
     111             :   /* Array bounds checks, reject manifests whose counts exceed the
     112             :      fixed-size arrays in fd_snapshot_manifest_t.  Validating here
     113             :      enables early recovery from malformed snapshots. */
     114             : 
     115         129 :   if( FD_UNLIKELY( manifest->hard_fork_cnt>FD_HARD_FORKS_MAX ) ) {
     116           3 :     FD_LOG_WARNING(( "corrupt snapshot: hard_fork_cnt %lu exceeds max %lu",
     117           3 :                      manifest->hard_fork_cnt, FD_HARD_FORKS_MAX ));
     118           3 :     return -1;
     119           3 :   }
     120             : 
     121         126 :   if( FD_UNLIKELY( manifest->vote_accounts_len>FD_RUNTIME_MAX_SNAPSHOT_VOTE_ACCOUNTS ) ) {
     122           3 :     FD_LOG_WARNING(( "corrupt snapshot: vote_accounts_len %lu exceeds max %lu",
     123           3 :                      manifest->vote_accounts_len, FD_RUNTIME_MAX_SNAPSHOT_VOTE_ACCOUNTS ));
     124           3 :     return -1;
     125           3 :   }
     126             : 
     127             :   /* Epoch credits downcasting validation */
     128             : 
     129         378 :   for( ulong i=0UL; i<FD_RUNTIME_MANIFEST_EPOCH_STAKES_LEN; i++ ) {
     130         294 :     if( FD_UNLIKELY( manifest->epoch_stakes[i].vote_stakes_len>FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS ) ) {
     131           9 :       FD_LOG_WARNING(( "corrupt snapshot: epoch_stakes[%lu].vote_stakes_len %lu exceeds max %lu",
     132           9 :                        i, manifest->epoch_stakes[i].vote_stakes_len, FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS ));
     133           9 :       return -1;
     134           9 :     }
     135         324 :     for( ulong j=0UL; j<manifest->epoch_stakes[i].vote_stakes_len; j++ ) {
     136          69 :       fd_snapshot_manifest_vote_stakes_t const * vs = &manifest->epoch_stakes[i].vote_stakes[j];
     137          69 :       if( FD_UNLIKELY( vs->epoch_credits_history_len>FD_EPOCH_CREDITS_MAX ) ) {
     138           3 :         FD_LOG_WARNING(( "corrupt snapshot: epoch_stakes[%lu].vote_stakes[%lu].epoch_credits_history_len %lu exceeds max %lu",
     139           3 :                          i, j, vs->epoch_credits_history_len, FD_EPOCH_CREDITS_MAX ));
     140           3 :         return -1;
     141           3 :       }
     142             :       /* The Alpenglow migration marker is a special credits record that
     143             :          separates pre-Alpenglow and post-Alpenglow credits.  Note
     144             :          Alpenglow is for a slot, so the credit epoch before and after
     145             :          can be the same.  Validators who earned credits before and
     146             :          after will have two entries for the same epoch. */
     147             : 
     148          66 :       ulong ec_base     = 0UL;
     149          66 :       ulong prev        = ULONG_MAX; /* index of the previous non-marker entry */
     150          66 :       int   marker_seen = 0;
     151             : 
     152         141 :       for( ulong k=0UL; k<vs->epoch_credits_history_len; k++ ) {
     153         102 :         epoch_credits_t const * epc = &vs->epoch_credits[k];
     154         102 :         if( FD_UNLIKELY( fd_epoch_credits_is_alpenglow_marker( epc ) ) ) {
     155          21 :           if( FD_UNLIKELY( marker_seen ) ) {
     156           3 :             FD_LOG_WARNING(( "corrupt snapshot: epoch_stakes[%lu].vote_stakes[%lu] has more than one alpenglow migration marker", i, j ));
     157           3 :             return -1;
     158           3 :           }
     159          18 :           marker_seen = 1;
     160          18 :           continue;
     161          21 :         }
     162          81 :         if( FD_UNLIKELY( prev==ULONG_MAX ) ) ec_base = epc->prev_credits;
     163          81 :         if( FD_UNLIKELY( epc->prev_credits>epc->credits ) ) {
     164           6 :           FD_LOG_WARNING(( "corrupt snapshot: epoch_stakes[%lu].vote_stakes[%lu].epoch_credits[%lu].prev_credits %lu exceeds credits %lu",
     165           6 :                            i, j, k, epc->prev_credits, epc->credits ));
     166           6 :           return -1;
     167           6 :         }
     168          75 :         if( FD_UNLIKELY( prev!=ULONG_MAX && epc->epoch<vs->epoch_credits[prev].epoch ) ) {
     169           6 :           FD_LOG_WARNING(( "corrupt snapshot: epoch_stakes[%lu].vote_stakes[%lu].epoch_credits[%lu].epoch %lu is less than previous epoch %lu",
     170           6 :                            i, j, k, epc->epoch, vs->epoch_credits[prev].epoch ));
     171           6 :           return -1;
     172           6 :         }
     173          69 :         if( FD_UNLIKELY( prev!=ULONG_MAX && epc->prev_credits!=vs->epoch_credits[prev].credits ) ) {
     174           6 :           FD_LOG_WARNING(( "corrupt snapshot: epoch_stakes[%lu].vote_stakes[%lu].epoch_credits[%lu].prev_credits %lu does not equal previous credits %lu",
     175           6 :                            i, j, k, epc->prev_credits, vs->epoch_credits[prev].credits ));
     176           6 :           return -1;
     177           6 :         }
     178          63 :         if( FD_UNLIKELY( epc->epoch>(ulong)USHORT_MAX ) ) {
     179           6 :           FD_LOG_WARNING(( "corrupt snapshot: epoch_stakes[%lu].vote_stakes[%lu].epoch_credits[%lu].epoch %lu exceeds USHORT_MAX",
     180           6 :                            i, j, k, epc->epoch ));
     181           6 :           return -1;
     182           6 :         }
     183          57 :         if( FD_UNLIKELY( epc->credits<ec_base ) ) {
     184           0 :           FD_LOG_WARNING(( "corrupt snapshot: epoch_stakes[%lu].vote_stakes[%lu].epoch_credits[%lu].credits %lu is below base %lu",
     185           0 :                            i, j, k, epc->credits, ec_base ));
     186           0 :           return -1;
     187           0 :         }
     188          57 :         prev = k;
     189          57 :       }
     190          66 :     }
     191         285 :   }
     192             : 
     193             :   /* Epoch stakes index validation.  fd_slot_to_leader_schedule_epoch
     194             :      is inlined here with overflow-safe arithmetic. */
     195             : 
     196          84 :   fd_epoch_schedule_t epoch_schedule = (fd_epoch_schedule_t){
     197          84 :     .slots_per_epoch             = manifest->epoch_schedule_params.slots_per_epoch,
     198          84 :     .leader_schedule_slot_offset = manifest->epoch_schedule_params.leader_schedule_slot_offset,
     199          84 :     .warmup                      = manifest->epoch_schedule_params.warmup,
     200          84 :     .first_normal_epoch          = manifest->epoch_schedule_params.first_normal_epoch,
     201          84 :     .first_normal_slot           = manifest->epoch_schedule_params.first_normal_slot,
     202          84 :   };
     203             : 
     204          84 :   ulong epoch = fd_slot_to_epoch( &epoch_schedule, manifest->slot, NULL );
     205             : 
     206             :   /* Compute leader_schedule_epoch with overflow safety.  Mirrors
     207             :      fd_slot_to_leader_schedule_epoch but rejects overflow instead
     208             :      of silently wrapping. */
     209             : 
     210          84 :   ulong leader_schedule_epoch;
     211          84 :   if( FD_UNLIKELY( manifest->slot<epoch_schedule.first_normal_slot ) ) {
     212           9 :     if( FD_UNLIKELY( __builtin_uaddl_overflow( epoch, 1UL, &leader_schedule_epoch ) ) ) {
     213           0 :       FD_LOG_WARNING(( "corrupt snapshot: leader_schedule_epoch overflow (epoch=%lu)", epoch ));
     214           0 :       return -1;
     215           0 :     }
     216          75 :   } else {
     217          75 :     ulong delta = manifest->slot-epoch_schedule.first_normal_slot;
     218          75 :     ulong sum;
     219          75 :     if( FD_UNLIKELY( __builtin_uaddl_overflow( delta, epoch_schedule.leader_schedule_slot_offset, &sum ) ) ) {
     220           3 :       FD_LOG_WARNING(( "corrupt snapshot: leader_schedule_slot_offset overflow "
     221           3 :                        "(slot_delta=%lu leader_schedule_slot_offset=%lu)",
     222           3 :                        delta, epoch_schedule.leader_schedule_slot_offset ));
     223           3 :       return -1;
     224           3 :     }
     225          72 :     ulong n_epochs = sum/epoch_schedule.slots_per_epoch;
     226          72 :     if( FD_UNLIKELY( __builtin_uaddl_overflow( epoch_schedule.first_normal_epoch, n_epochs, &leader_schedule_epoch ) ) ) {
     227           0 :       FD_LOG_WARNING(( "corrupt snapshot: leader_schedule_epoch overflow "
     228           0 :                        "(first_normal_epoch=%lu n_epochs=%lu)",
     229           0 :                        epoch_schedule.first_normal_epoch, n_epochs ));
     230           0 :       return -1;
     231           0 :     }
     232          72 :   }
     233             : 
     234          81 :   ulong epoch_stakes_base = epoch>0UL ? epoch-1UL : 0UL;
     235             : 
     236          81 :   if( FD_UNLIKELY( leader_schedule_epoch<epoch_stakes_base ) ) {
     237           0 :     FD_LOG_WARNING(( "corrupt snapshot: leader_schedule_epoch %lu < epoch_stakes_base %lu",
     238           0 :                      leader_schedule_epoch, epoch_stakes_base ));
     239           0 :     return -1;
     240           0 :   }
     241          81 :   ulong t_1_idx = leader_schedule_epoch-epoch_stakes_base;
     242          81 :   if( FD_UNLIKELY( t_1_idx>=FD_RUNTIME_MANIFEST_EPOCH_STAKES_LEN ) ) {
     243           6 :     FD_LOG_WARNING(( "corrupt snapshot: epoch stakes index %lu out of range (max %lu)",
     244           6 :                      t_1_idx, FD_RUNTIME_MANIFEST_EPOCH_STAKES_LEN ));
     245           6 :     return -1;
     246           6 :   }
     247             : 
     248          75 :   if( FD_UNLIKELY( manifest->epoch_stakes[t_1_idx].vote_stakes_len>max_vote_accounts ) ) {
     249           0 :     FD_LOG_WARNING(( "corrupt snapshot: T-1 epoch stakes length %lu exceeds max_vote_accounts %lu",
     250           0 :                      manifest->epoch_stakes[t_1_idx].vote_stakes_len, max_vote_accounts ));
     251           0 :     return -1;
     252           0 :   }
     253             : 
     254          75 :   if( FD_UNLIKELY( t_1_idx>0UL && manifest->epoch_stakes[t_1_idx-1UL].vote_stakes_len>max_vote_accounts ) ) {
     255           0 :     FD_LOG_WARNING(( "corrupt snapshot: T-2 epoch stakes length %lu exceeds max_vote_accounts %lu",
     256           0 :                      manifest->epoch_stakes[t_1_idx-1UL].vote_stakes_len, max_vote_accounts ));
     257           0 :     return -1;
     258           0 :   }
     259             : 
     260          84 :   for( ulong j=0UL; j<manifest->epoch_stakes[t_1_idx].vote_stakes_len; j++ ) {
     261           9 :     if( FD_UNLIKELY( !manifest->epoch_stakes[t_1_idx].vote_stakes[j].stake ) ) {
     262           0 :       FD_LOG_WARNING(( "corrupt snapshot: T-1 epoch stakes entry %lu has zero stake", j ));
     263           0 :       return -1;
     264           0 :     }
     265           9 :   }
     266             : 
     267          75 :   return 0;
     268          75 : }
     269             : 
     270             : static int
     271             : blockhashes_recover( fd_blockhashes_t *                       blockhashes,
     272             :                      fd_snapshot_manifest_blockhash_t const * ages,
     273             :                      ulong                                    age_cnt,
     274           9 :                      ulong                                    seed ) {
     275             : 
     276             :   /* The caller must guarantee that fd_ssload_manifest_validate has
     277             :      already been invoked, verifying that age_cnt is in the range
     278             :      (0, FD_BLOCKHASHES_MAX], that there are no gaps or duplicates in
     279             :      the sequence numbers, and that seq_min+age_cnt does not overflow. */
     280             : 
     281           9 :   if( FD_UNLIKELY( !fd_blockhashes_init( blockhashes, seed ) ) ) {
     282           0 :     FD_LOG_WARNING(( "failed to initialize blockhash queue" ));
     283           0 :     return -1;
     284           0 :   }
     285             : 
     286           9 :   ulong seq_min = ULONG_MAX;
     287          18 :   for( ulong i=0UL; i<age_cnt; i++ ) {
     288           9 :     seq_min = fd_ulong_min( seq_min, ages[ i ].hash_index );
     289           9 :   }
     290             : 
     291             :   /* Reset */
     292             : 
     293          18 :   for( ulong i=0UL; i<age_cnt; i++ ) {
     294           9 :     fd_blockhash_info_t * ele = fd_blockhash_deq_push_tail_nocopy( blockhashes->d.deque );
     295           9 :     fd_memset( ele, 0, sizeof(fd_blockhash_info_t) );
     296           9 :   }
     297             : 
     298             :   /* Load hashes */
     299             : 
     300          18 :   for( ulong i=0UL; i<age_cnt; i++ ) {
     301           9 :     fd_snapshot_manifest_blockhash_t const * elem = &ages[ i ];
     302           9 :     ulong idx = elem->hash_index - seq_min;
     303           9 :     fd_blockhash_info_t * info = &blockhashes->d.deque[ idx ];
     304           9 :     info->exists = 1;
     305           9 :     fd_memcpy( info->hash.uc, elem->hash, 32UL );
     306           9 :     info->lamports_per_signature = elem->lamports_per_signature;
     307           9 :     fd_blockhash_map_idx_insert( blockhashes->map, idx, blockhashes->d.deque );
     308           9 :   }
     309             : 
     310           9 :   return 0;
     311           9 : }
     312             : 
     313             : int
     314             : fd_ssload_recover_validate( fd_snapshot_manifest_t const * manifest,
     315           0 :                             fd_banks_t const *             banks ) {
     316           0 :   return fd_ssload_manifest_validate( manifest, banks->max_vote_accounts, banks->max_stake_accounts );
     317           0 : }
     318             : 
     319             : int
     320             : fd_ssload_recover_apply( fd_snapshot_manifest_t * manifest,
     321             :                          fd_bank_t *              bank,
     322           9 :                          ulong                    blockhash_seed ) {
     323             : 
     324             :   /* Slot */
     325             : 
     326           9 :   bank->f.slot = manifest->slot;
     327           9 :   bank->f.parent_slot = manifest->parent_slot;
     328             : 
     329             :   /* Bank Hash */
     330             : 
     331           9 :   fd_hash_t hash;
     332           9 :   fd_memcpy( &hash.uc, manifest->bank_hash, 32UL );
     333           9 :   bank->f.bank_hash = hash;
     334             : 
     335           9 :   fd_hash_t parent_hash;
     336           9 :   fd_memcpy( &parent_hash.uc, manifest->parent_bank_hash, 32UL );
     337           9 :   bank->f.prev_bank_hash = parent_hash;
     338             : 
     339           9 :   fd_fee_rate_governor_t * fee_rate_governor = &bank->f.fee_rate_governor;
     340           9 :   fee_rate_governor->target_lamports_per_signature = manifest->fee_rate_governor.target_lamports_per_signature;
     341           9 :   fee_rate_governor->target_signatures_per_slot    = manifest->fee_rate_governor.target_signatures_per_slot;
     342           9 :   fee_rate_governor->min_lamports_per_signature    = manifest->fee_rate_governor.min_lamports_per_signature;
     343           9 :   fee_rate_governor->max_lamports_per_signature    = manifest->fee_rate_governor.max_lamports_per_signature;
     344           9 :   fee_rate_governor->burn_percent                  = manifest->fee_rate_governor.burn_percent;
     345             :   /* https://github.com/anza-xyz/agave/blob/v3.0.3/runtime/src/serde_snapshot.rs#L464-L466 */
     346           9 :   bank->f.rbh_lamports_per_sig = manifest->lamports_per_signature;
     347             : 
     348           9 :   fd_inflation_t * inflation = &bank->f.inflation;
     349           9 :   inflation->initial         = manifest->inflation_params.initial;
     350           9 :   inflation->terminal        = manifest->inflation_params.terminal;
     351           9 :   inflation->taper           = manifest->inflation_params.taper;
     352           9 :   inflation->foundation      = manifest->inflation_params.foundation;
     353           9 :   inflation->foundation_term = manifest->inflation_params.foundation_term;
     354           9 :   inflation->unused          = 0.0;
     355             : 
     356           9 :   fd_epoch_schedule_t * epoch_schedule = &bank->f.epoch_schedule;
     357           9 :   epoch_schedule->slots_per_epoch             = manifest->epoch_schedule_params.slots_per_epoch;
     358           9 :   epoch_schedule->leader_schedule_slot_offset = manifest->epoch_schedule_params.leader_schedule_slot_offset;
     359           9 :   epoch_schedule->warmup                      = manifest->epoch_schedule_params.warmup;
     360           9 :   epoch_schedule->first_normal_epoch          = manifest->epoch_schedule_params.first_normal_epoch;
     361           9 :   epoch_schedule->first_normal_slot           = manifest->epoch_schedule_params.first_normal_slot;
     362             : 
     363           9 :   ulong epoch = fd_slot_to_epoch( epoch_schedule, manifest->slot, NULL );
     364           9 :   bank->f.epoch = epoch;
     365             : 
     366           9 :   fd_rent_t * rent = &bank->f.rent;
     367           9 :   rent->lamports_per_uint8_year = manifest->rent_params.lamports_per_uint8_year;
     368           9 :   rent->exemption_threshold     = manifest->rent_params.exemption_threshold;
     369           9 :   rent->burn_percent            = manifest->rent_params.burn_percent;
     370             : 
     371             :   /* https://github.com/anza-xyz/agave/blob/v3.0.6/ledger/src/blockstore_processor.rs#L1118
     372             :      None gets treated as 0 for hash verification. */
     373           9 :   ulong restored_hashes_per_tick = manifest->has_hashes_per_tick ? manifest->hashes_per_tick : 0UL;
     374             : 
     375           9 :   fd_lthash_value_t * lthash = fd_bank_lthash_locking_modify( bank );
     376           9 :   if( FD_LIKELY( manifest->has_accounts_lthash ) ) {
     377           0 :     fd_memcpy( lthash, manifest->accounts_lthash, sizeof(fd_lthash_value_t) );
     378           9 :   } else {
     379           9 :     fd_memset( lthash, 0, sizeof(fd_lthash_value_t) );
     380           9 :   }
     381           9 :   fd_bank_lthash_end_locking_modify( bank );
     382             : 
     383           9 :   fd_blockhashes_t * blockhashes = &bank->f.block_hash_queue;
     384           9 :   if( FD_UNLIKELY( blockhashes_recover( blockhashes, manifest->blockhashes, manifest->blockhashes_len, blockhash_seed ) ) ) {
     385           0 :     FD_LOG_WARNING(( "blockhash queue recovery failed" ));
     386           0 :     return -1;
     387           0 :   }
     388             : 
     389             :   /* PoH */
     390           9 :   fd_blockhashes_t const * bhq = &bank->f.block_hash_queue;
     391           9 :   fd_hash_t const * last_hash = fd_blockhashes_peek_last_hash( bhq );
     392           9 :   if( FD_LIKELY( last_hash ) ) bank->f.poh = *last_hash;
     393             : 
     394           9 :   bank->f.capitalization                   = manifest->capitalization;
     395           9 :   bank->f.txn_count                        = manifest->transaction_count;
     396           9 :   bank->f.signature_count                  = manifest->signature_count;
     397           9 :   bank->f.tick_height                      = manifest->tick_height;
     398           9 :   bank->f.max_tick_height                  = manifest->max_tick_height;
     399           9 :   bank->f.ticks_per_slot                   = manifest->ticks_per_slot;
     400           9 :   bank->f.genesis_creation_time            = manifest->creation_time_seconds;
     401           9 :   bank->f.slot_params                      = FD_SLOT_PARAMS_400MS;
     402           9 :   bank->f.slot_params.ns_per_slot          = manifest->ns_per_slot;
     403           9 :   bank->f.slot_params.ns_per_slot_adjusted = fd_ulong_sat_sub( bank->f.slot_params.ns_per_slot, FD_TARGET_SLOT_ADJUSTMENT_NS );
     404           9 :   bank->f.slot_params.slots_per_year       = manifest->slots_per_year;
     405           9 :   bank->f.slot_params.hashes_per_tick      = restored_hashes_per_tick;
     406           9 :   bank->f.block_height                     = manifest->block_height;
     407           9 :   bank->f.execution_fees                   = manifest->collector_fees;
     408           9 :   bank->f.priority_fees                    = 0UL;
     409             : 
     410             :   /* Set the cluster type based on the genesis creation time.  This is
     411             :      later cross referenced against the genesis hash. */
     412           9 :   switch( bank->f.genesis_creation_time ) {
     413           0 :     case FD_RUNTIME_GENESIS_CREATION_TIME_TESTNET:
     414           0 :       bank->f.cluster_type = FD_CLUSTER_TESTNET;
     415           0 :       break;
     416           0 :     case FD_RUNTIME_GENESIS_CREATION_TIME_MAINNET:
     417           0 :       bank->f.cluster_type = FD_CLUSTER_MAINNET_BETA;
     418           0 :       break;
     419           0 :     case FD_RUNTIME_GENESIS_CREATION_TIME_DEVNET:
     420           0 :       bank->f.cluster_type = FD_CLUSTER_DEVNET;
     421           0 :       break;
     422           9 :     default:
     423           9 :       bank->f.cluster_type = FD_CLUSTER_UNKNOWN;
     424           9 :   }
     425             : 
     426             :   /* Update last restart slot
     427             :      https://github.com/solana-labs/solana/blob/30531d7a5b74f914dde53bfbb0bc2144f2ac92bb/runtime/src/bank.rs#L2152
     428             : 
     429             :      old_bank->hard_forks is sorted ascending by slot number.
     430             :      To find the last restart slot, take the highest hard fork slot
     431             :      number that is less or equal than the current slot number.
     432             :      (There might be some hard forks in the future, ignore these)
     433             : 
     434             :      SIMD-0047: The first restart slot should be `0` */
     435           9 :   bank->f.hard_fork_cnt = manifest->hard_fork_cnt;
     436           9 :   if( FD_LIKELY( manifest->hard_fork_cnt ) ) {
     437           0 :     for( ulong i=0UL; i<manifest->hard_fork_cnt; i++ ) {
     438           0 :       bank->f.hard_forks[ i ] = manifest->hard_forks[ i ];
     439           0 :     }
     440             : 
     441           0 :     for( ulong i=0UL; i<manifest->hard_fork_cnt; i++ ) {
     442           0 :       ulong slot = manifest->hard_forks[ manifest->hard_fork_cnt-1UL-i ].slot;
     443           0 :       if( FD_LIKELY( slot<=manifest->slot ) ) {
     444           0 :         break;
     445           0 :       }
     446           0 :     }
     447           0 :   }
     448             : 
     449             :   /* snapin populates the root stake delegation cache directly from the
     450             :      account stream.  The manifest's primary stake delegations are
     451             :      intentionally ignored. */
     452             : 
     453             :   /* We also want to set the total stake to be the total amount of stake
     454             :      at the end of the previous epoch. This value is used for the
     455             :      get_epoch_stake syscall.
     456             : 
     457             :      A note on Agave's indexing scheme for their epoch_stakes
     458             :      structure:
     459             : 
     460             :      https://github.com/anza-xyz/agave/blob/v2.2.14/runtime/src/bank.rs#L6175
     461             : 
     462             :      If we are loading a snapshot and replaying in the middle of
     463             :      epoch 7, the syscall is supposed to return the total stake at
     464             :      the end of epoch 6.  The epoch_stakes structure is indexed in
     465             :      Agave by the epoch number of the leader schedule that the
     466             :      stakes are meant to determine.  For instance, to get the
     467             :      stakes at the end of epoch 6, we should query by 8, because
     468             :      the leader schedule for epoch 8 is determined based on the
     469             :      stakes at the end of epoch 6.  Therefore, we save the total
     470             :      epoch stake by querying for epoch+1.  This logic is encapsulated
     471             :      in fd_ssmanifest_parser.c. */
     472             : 
     473           9 :   fd_collector_overrides_t * overrides = fd_bank_collector_overrides( bank );
     474           9 :   fd_collector_overrides_reset( overrides );
     475           9 :   bank->collector_overrides_fork_id = fd_collector_overrides_get_root_idx( overrides );
     476           9 :   ushort co_root = bank->collector_overrides_fork_id;
     477             : 
     478           9 :   fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
     479           9 :   fd_vote_stakes_reset( vote_stakes );
     480           9 :   bank->vote_stakes_fork_id = fd_vote_stakes_init( vote_stakes, bank->f.epoch );
     481           9 :   ulong vote_stakes_fork_id = bank->vote_stakes_fork_id;
     482             : 
     483           9 :   ulong leader_schedule_epoch = fd_slot_to_leader_schedule_epoch( epoch_schedule, manifest->slot );
     484           9 :   ulong epoch_stakes_base     = epoch > 0UL ? epoch - 1UL : 0UL;
     485           9 :   ulong t_1_idx = leader_schedule_epoch - epoch_stakes_base;
     486             : 
     487           9 :   int   has_t_2 = (t_1_idx > 0UL);
     488           9 :   ulong t_2_idx = has_t_2 ? t_1_idx - 1UL : 0UL;
     489             : 
     490           9 :   bank->f.total_epoch_stake = manifest->epoch_stakes[t_1_idx].total_stake;
     491             : 
     492           9 :   fd_bank_epoch_credits_new_fork( bank );
     493           9 :   ulong epoch_credits_len = 0UL;
     494             : 
     495             :   /* Populate the top votes for the end of the T-1 epoch if the
     496             :      snapshot is in epoch T. */
     497          18 :   for( ulong i=0UL; i<manifest->epoch_stakes[t_1_idx].vote_stakes_len; i++ ) {
     498           9 :     fd_snapshot_manifest_vote_stakes_t const * elem = &manifest->epoch_stakes[t_1_idx].vote_stakes[i];
     499             : 
     500           9 :     fd_vote_stakes_snap_insert_t_1( vote_stakes, vote_stakes_fork_id, (fd_pubkey_t *)elem->vote, (fd_pubkey_t *)elem->identity, elem->stake, elem->commission, elem->identity_bls );
     501             : 
     502             :     /* Record SIMD-0232 collector overrides for the t_1 set (tag
     503             :        bank->f.epoch). */
     504           9 :     {
     505           9 :       int has_inflation = !!memcmp( elem->commission_inflation, elem->vote,     32UL );
     506           9 :       int has_block     = !!memcmp( elem->commission_block,     elem->identity, 32UL );
     507           9 :       if( FD_UNLIKELY( has_inflation | has_block ) ) {
     508           9 :         fd_collector_overrides_upsert( overrides, co_root, bank->f.epoch, (fd_pubkey_t const *)elem->vote,
     509           9 :                                        has_inflation, (fd_pubkey_t const *)elem->commission_inflation,
     510           9 :                                        has_block, (fd_pubkey_t const *)elem->commission_block );
     511           9 :       }
     512           9 :     }
     513             : 
     514             :     /* Reward recalculation resolves every epoch credits entry against
     515             :        the t_1 set, so only admitted accounts may get one. */
     516           9 :     if( FD_UNLIKELY( !fd_vote_stakes_query_t_1( vote_stakes, vote_stakes_fork_id, (fd_pubkey_t const *)elem->vote,
     517           9 :                                                 NULL, NULL, NULL ) ) ) continue;
     518             : 
     519           9 :     if( FD_UNLIKELY( epoch_credits_len>=FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS ) ) {
     520           0 :       FD_LOG_WARNING(( "corrupt snapshot: more vote accounts than the epoch credits store holds (%lu)", FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS ));
     521           0 :       return -1;
     522           0 :     }
     523           9 :     fd_epoch_credits_t * ec = &fd_bank_epoch_credits( bank )[epoch_credits_len];
     524           9 :     fd_memcpy( ec->pubkey, elem->vote, 32UL );
     525             : 
     526           9 :     ulong cnt        = 0UL;
     527           9 :     ec->base_credits = 0UL;
     528           9 :     for( ulong j=0UL; j<elem->epoch_credits_history_len; j++ ) {
     529           0 :       if( FD_UNLIKELY( fd_epoch_credits_is_alpenglow_marker( &elem->epoch_credits[ j ] ) ) ) continue;
     530           0 :       if( FD_UNLIKELY( !cnt ) ) ec->base_credits = elem->epoch_credits[ j ].prev_credits;
     531           0 :       ec->epoch[ cnt ]              = (ushort)elem->epoch_credits[ j ].epoch;
     532           0 :       ec->credits_delta[ cnt ]      = elem->epoch_credits[ j ].credits      - ec->base_credits;
     533           0 :       ec->prev_credits_delta[ cnt ] = elem->epoch_credits[ j ].prev_credits - ec->base_credits;
     534           0 :       cnt++;
     535           0 :     }
     536             :     /* Manifest validation already rejects non-increasing epochs (except
     537             :        Alpenglow marker). */
     538           9 :     ec->cnt = (uchar)cnt;
     539           9 :     ec->fast_path_ok = fd_epoch_credits_fast_path_ok( ec );
     540           9 :     FD_TEST( ec->fast_path_ok ); /* manifest validation enforces all three invariants */
     541           9 :     epoch_credits_len++;
     542           9 :   }
     543           9 :   *fd_bank_epoch_credits_len( bank ) = epoch_credits_len;
     544             : 
     545             :   /* Populate the top votes for the end of the T-2 epoch if the
     546             :      snapshot is in epoch T. */
     547           9 :   if( has_t_2 ) {
     548          12 :     for( ulong i=0UL; i<manifest->epoch_stakes[t_2_idx].vote_stakes_len; i++ ) {
     549           3 :       fd_snapshot_manifest_vote_stakes_t const * elem = &manifest->epoch_stakes[t_2_idx].vote_stakes[i];
     550           3 :       fd_vote_stakes_snap_insert_t_2( vote_stakes, vote_stakes_fork_id, (fd_pubkey_t *)elem->vote, (fd_pubkey_t *)elem->identity, elem->stake, elem->commission, elem->identity_bls );
     551             : 
     552             :       /* Record SIMD-0232 collector overrides for the t_2 set (tag
     553             :          bank->f.epoch-1, the leader schedule source state). */
     554           3 :       {
     555           3 :         int has_inflation = !!memcmp( elem->commission_inflation, elem->vote,     32UL );
     556           3 :         int has_block     = !!memcmp( elem->commission_block,     elem->identity, 32UL );
     557           3 :         if( FD_UNLIKELY( has_inflation | has_block ) ) {
     558           3 :           fd_collector_overrides_upsert( overrides, co_root, fd_ulong_sat_sub( bank->f.epoch, 1UL ), (fd_pubkey_t const *)elem->vote,
     559           3 :                                          has_inflation, (fd_pubkey_t const *)elem->commission_inflation,
     560           3 :                                          has_block, (fd_pubkey_t const *)elem->commission_block );
     561           3 :         }
     562           3 :       }
     563           3 :     }
     564           9 :     fd_vote_stakes_finalize( vote_stakes, epoch );
     565           9 :   }
     566             : 
     567             :   /* Populate the top votes for the end of the T-3 epoch if the
     568             :      snapshot is in epoch T. */
     569          12 :   for( ulong i=0UL; i<manifest->epoch_stakes[0].vote_stakes_len; i++ ) {
     570           3 :     fd_snapshot_manifest_vote_stakes_t const * elem = &manifest->epoch_stakes[0].vote_stakes[i];
     571           3 :     fd_vote_stakes_snap_insert_t_3( vote_stakes, vote_stakes_fork_id, (fd_pubkey_t *)elem->vote, (fd_pubkey_t *)elem->identity, elem->stake, elem->commission, elem->identity_bls );
     572           3 :   }
     573           9 :   if( FD_LIKELY( epoch ) ) fd_vote_stakes_finalize( vote_stakes, epoch-1UL );
     574             : 
     575           9 :   bank->accdb_fork_id        = (fd_accdb_fork_id_t){ .val = manifest->accdb_fork_id };
     576           9 :   bank->parent_accdb_fork_id = bank->accdb_fork_id;
     577           9 :   bank->txncache_fork_id     = (fd_txncache_fork_id_t){ .val = manifest->txncache_fork_id };
     578             : 
     579           9 :   return 0;
     580           9 : }
     581             : 
     582             : int
     583             : fd_ssload_recover( fd_snapshot_manifest_t * manifest,
     584             :                    fd_banks_t *             banks,
     585             :                    fd_bank_t *              bank,
     586           0 :                    ulong                    blockhash_seed ) {
     587             : 
     588           0 :   if( FD_UNLIKELY( fd_ssload_recover_validate( manifest, banks ) ) ) {
     589           0 :     FD_LOG_WARNING(( "snapshot manifest validation failed" ));
     590           0 :     return -1;
     591           0 :   }
     592             : 
     593           0 :   return fd_ssload_recover_apply( manifest, bank, blockhash_seed );
     594           0 : }

Generated by: LCOV version 1.14