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