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