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