1use crate::{
7 common::SnapRecord, repair::StoredRepairs, AccountCoverage, BalStateUpdate, DownloadedAccount,
8 SnapAccountStore, SnapAttemptStore, SnapBytecodeStore, SnapStorageStore, SnapSyncError,
9 SnapWrite, StorageProgress,
10};
11use alloy_eip7928::AccountChanges;
12use alloy_eips::BlockNumHash;
13use alloy_primitives::{keccak256, B256, KECCAK256_EMPTY};
14use reth_db_api::{
15 tables,
16 transaction::{DbTx, DbTxMut},
17};
18use reth_primitives_traits::Account;
19use reth_storage_api::{
20 BlockHashReader, DBProvider, MetadataProvider, MetadataWriter, SnapAttemptId, StateWriter,
21};
22use serde::{Deserialize, Serialize};
23
24pub trait SnapCatchUpStore {
29 fn catch_up_progress(&self, write: SnapWrite)
31 -> Result<Option<CatchUpProgress>, SnapSyncError>;
32
33 fn downloaded_account(
35 &self,
36 coverage: AccountCoverage,
37 hashed_address: B256,
38 ) -> Result<DownloadedAccount, SnapSyncError>
39 where
40 Self: DBProvider;
41
42 fn block_access_list_update(
47 &self,
48 coverage: AccountCoverage,
49 storage: StorageProgress,
50 bal: &[AccountChanges],
51 ) -> Result<BalStateUpdate, SnapSyncError>
52 where
53 Self: DBProvider;
54
55 fn commit_block_access_list(
60 &self,
61 write: SnapWrite,
62 block: BlockNumHash,
63 parent: B256,
64 bal: &[AccountChanges],
65 ) -> Result<CatchUpProgress, SnapSyncError>
66 where
67 Self: BlockHashReader + MetadataWriter + StateWriter + DBProvider<Tx: DbTxMut>;
68}
69
70#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72pub struct CatchUpProgress {
73 applied: BlockNumHash,
75}
76
77impl CatchUpProgress {
78 pub const fn at_pivot(pivot: BlockNumHash) -> Self {
80 Self { applied: pivot }
81 }
82
83 pub const fn applied(&self) -> BlockNumHash {
85 self.applied
86 }
87
88 pub const fn resume_after(&self, ancestor: BlockNumHash) -> BlockNumHash {
90 if self.applied.number <= ancestor.number {
91 self.applied
92 } else {
93 ancestor
94 }
95 }
96
97 pub const fn next(&self) -> u64 {
99 self.applied.number + 1
100 }
101
102 fn advance(&self, block: BlockNumHash, parent: B256) -> Result<Self, SnapSyncError> {
104 if block.number != self.next() {
105 return Err(SnapSyncError::OutOfOrderBlock { expected: self.next(), got: block.number })
106 }
107 if parent != self.applied.hash {
108 return Err(SnapSyncError::ForkedBlock { expected: self.applied.hash, got: parent })
109 }
110 Ok(Self { applied: block })
111 }
112
113 pub(crate) fn write(
115 &self,
116 provider: &impl MetadataWriter,
117 attempt: SnapAttemptId,
118 ) -> Result<(), SnapSyncError> {
119 StoredCatchUpProgress::new(attempt, *self).write(provider)
120 }
121}
122
123#[derive(Serialize, Deserialize)]
125struct StoredCatchUpProgress {
126 version: u32,
128 attempt: SnapAttemptId,
130 block: u64,
132 hash: B256,
134}
135
136impl SnapRecord for StoredCatchUpProgress {
137 const KEY: &'static str = "snap_catch_up_progress";
138 const VERSION: u32 = 1;
139}
140
141impl StoredCatchUpProgress {
142 const fn new(attempt: SnapAttemptId, progress: CatchUpProgress) -> Self {
144 Self {
145 version: Self::VERSION,
146 attempt,
147 block: progress.applied.number,
148 hash: progress.applied.hash,
149 }
150 }
151
152 const fn progress(&self) -> CatchUpProgress {
153 CatchUpProgress::at_pivot(BlockNumHash::new(self.block, self.hash))
154 }
155}
156
157impl<T: MetadataProvider> SnapCatchUpStore for T {
158 fn catch_up_progress(
160 &self,
161 write: SnapWrite,
162 ) -> Result<Option<CatchUpProgress>, SnapSyncError> {
163 self.authorize_snap_write(write)?;
164 let Some(stored) = StoredCatchUpProgress::read(self)? else { return Ok(None) };
165 Ok((stored.attempt == write.attempt()).then(|| stored.progress()))
166 }
167
168 fn downloaded_account(
169 &self,
170 coverage: AccountCoverage,
171 hashed_address: B256,
172 ) -> Result<DownloadedAccount, SnapSyncError>
173 where
174 Self: DBProvider,
175 {
176 if coverage.next().is_some_and(|next| hashed_address >= next) {
178 return Ok(DownloadedAccount::Unknown)
179 }
180 Ok(self
181 .tx_ref()
182 .get::<tables::HashedAccounts>(hashed_address)?
183 .map_or(DownloadedAccount::Absent, DownloadedAccount::Present))
184 }
185
186 fn block_access_list_update(
187 &self,
188 coverage: AccountCoverage,
189 storage: StorageProgress,
190 bal: &[AccountChanges],
191 ) -> Result<BalStateUpdate, SnapSyncError>
192 where
193 Self: DBProvider,
194 {
195 let mut update = BalStateUpdate::default();
196 for account_changes in bal {
197 let account_info = account_changes.account_info();
198 if !account_info.changes_state_root(account_changes) {
200 continue
201 }
202
203 let hashed_address = keccak256(account_changes.address());
204 let mut account = match self.downloaded_account(coverage, hashed_address)? {
205 DownloadedAccount::Unknown => {
208 if storage.has_slots(hashed_address) && account_changes.has_storage_changes() {
209 update.insert_storage(hashed_address, account_changes);
210 }
211 update.unresolved.push(hashed_address);
212 continue
213 }
214 DownloadedAccount::Absent => Account::default(),
215 DownloadedAccount::Present(account) => account,
216 };
217
218 account.apply_bal_info(account_info);
219 account.bytecode_hash = account.bytecode_hash.filter(|hash| *hash != KECCAK256_EMPTY);
221 update.state.accounts.insert(hashed_address, (!account.is_empty()).then_some(account));
223 if account_changes.has_storage_changes() {
224 update.insert_storage(hashed_address, account_changes);
225 }
226 if let Some((code_hash, code)) = account_info
227 .code_hash
228 .zip(account_changes.code_post_state().filter(|code| !code.is_empty()))
229 {
230 update.bytecodes.insert(code_hash, code.clone());
231 }
232 }
233 Ok(update)
234 }
235
236 fn commit_block_access_list(
238 &self,
239 write: SnapWrite,
240 block: BlockNumHash,
241 parent: B256,
242 bal: &[AccountChanges],
243 ) -> Result<CatchUpProgress, SnapSyncError>
244 where
245 Self: BlockHashReader + MetadataWriter + StateWriter + DBProvider<Tx: DbTxMut>,
246 {
247 let attempt = self.authorize_canonical_snap_write(write)?;
249 if self.block_hash(block.number)? != Some(block.hash) {
251 return Err(SnapSyncError::NonCanonicalBlock { block: block.number, hash: block.hash })
252 }
253 let advanced = self
254 .catch_up_progress(write)?
255 .ok_or(SnapSyncError::NoCatchUpProgress)?
256 .advance(block, parent)?;
257 if block.number > attempt.pivot().number {
259 return Err(SnapSyncError::BlockPastPivot {
260 pivot: attempt.pivot().number,
261 block: block.number,
262 })
263 }
264 let coverage = self.account_coverage(write)?.ok_or(SnapSyncError::NoCoverage)?;
265 let storage = match coverage.next() {
266 Some(origin) => self.storage_progress(write, origin)?,
267 None => StorageProgress::START,
268 };
269 let update = self.block_access_list_update(coverage, storage, bal)?;
270
271 let (state, bytecodes, _unresolved) = update.into_parts();
274 for (hashed_address, account) in &state.accounts {
275 if account.is_none() {
278 self.remove::<tables::HashedStorages>(*hashed_address..=*hashed_address)?;
279 }
280 }
281 let mut repairs = self.snap_repairs(write)?;
284 let mut resolved = false;
285 for changes in bal {
286 if repairs.is_empty() {
287 break
288 }
289 let hashed_address = keccak256(changes.address());
290 if state.accounts.contains_key(&hashed_address) {
291 resolved |= repairs.resolve_changes(hashed_address, changes);
292 }
293 }
294 self.commit_bytecodes(write, bytecodes.into_iter().collect())?;
295 self.write_hashed_state(&state.into_sorted())?;
296 if resolved {
297 StoredRepairs::store(self, write.attempt(), repairs)?;
298 }
299 advanced.write(self, write.attempt())?;
300 Ok(advanced)
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use crate::{
308 test_utils::{
309 account, hashed_factory, key, state_root, storage_root_of, BalChain, SnapStateSnapshot,
310 },
311 SnapAccountStore, SnapGeneration, StateRepairs, StorageChunk,
312 };
313 use alloy_eip7928::{
314 BalanceChange, BlockAccessIndex, CodeChange, NonceChange, SlotChanges, StorageChange,
315 };
316 use alloy_primitives::{bytes, keccak256, map::B256Map, Address, Bytes, U256};
317 use reth_primitives_traits::Account;
318 use reth_provider::{
319 test_utils::{insert_headers, MockNodeTypesWithDB},
320 DatabaseProviderFactory, ProviderFactory,
321 };
322 use reth_trie_common::{HashedStorage, TrieAccount};
323
324 type Factory = ProviderFactory<MockNodeTypesWithDB>;
325
326 const CHANGED: Address = Address::repeat_byte(0xaa);
328 const SLOT: U256 = U256::from_limbs([1, 0, 0, 0]);
329
330 fn hashed_slot() -> B256 {
331 keccak256(B256::from(SLOT))
332 }
333
334 fn code() -> Bytes {
335 bytes!("6001")
336 }
337
338 fn accounts() -> Vec<(B256, TrieAccount)> {
341 let mut changed = account(0);
342 changed.storage_root = storage_root_of(&[(hashed_slot(), U256::from(7))]);
343 let mut accounts: Vec<_> = (1..=3).map(|nonce| (key(nonce), account(nonce))).collect();
344 accounts.push((keccak256(CHANGED), changed));
345 accounts.sort_by_key(|(hashed_address, _)| *hashed_address);
346 accounts
347 }
348
349 fn changed_index(accounts: &[(B256, TrieAccount)]) -> usize {
351 accounts
352 .iter()
353 .position(|(hashed_address, _)| *hashed_address == keccak256(CHANGED))
354 .unwrap()
355 }
356
357 fn started(accounts: &[(B256, TrieAccount)], served: usize) -> (Factory, SnapWrite) {
360 let factory = hashed_factory();
361 insert_headers(&factory, &chain().headers);
362 let provider = factory.database_provider_rw().unwrap();
363 let write = provider.start_snap_attempt(generation(1, state_root(accounts))).unwrap();
364 provider.start_account_coverage(write).unwrap();
365 let targets = (served < accounts.len()).then(|| accounts[served - 1].0);
367 let range =
368 crate::test_utils::verified_range(accounts, 0..served, B256::ZERO, targets.as_slice());
369 let storages = B256Map::from_iter([(
370 keccak256(CHANGED),
371 HashedStorage::from_iter([(hashed_slot(), U256::from(7))]),
372 )]);
373 provider
374 .commit_account_range(
375 write,
376 &range,
377 if served == accounts.len() { storages } else { B256Map::default() },
378 Vec::new(),
379 )
380 .unwrap();
381 let write =
382 provider.advance_snap_pivot(write, generation(2, state_root(accounts))).unwrap();
383 provider.commit().unwrap();
384 (factory, write)
385 }
386
387 fn chain() -> BalChain {
390 BalChain::new(1, [Vec::new(), Vec::new()])
391 }
392
393 fn generation(number: u64, state_root: B256) -> SnapGeneration {
395 SnapGeneration::new(chain().block(number as usize - 1), state_root)
396 }
397
398 fn block(number: u64) -> (BlockNumHash, B256) {
400 let chain = chain();
401 (chain.block(number as usize - 1), chain.block(number as usize - 2).hash)
402 }
403
404 fn replacement() -> BalChain {
406 BalChain::new(1, [Vec::new(), credit(1)])
407 }
408
409 fn orphaned_pivot(accounts: &[(B256, TrieAccount)]) -> (Factory, SnapWrite, BlockNumHash) {
412 let (factory, write) = started(accounts, accounts.len());
413 let provider = factory.database_provider_rw().unwrap();
414 let (applied, parent) = block(2);
415 provider.commit_block_access_list(write, applied, parent, &[]).unwrap();
416 let write =
417 provider.advance_snap_pivot(write, generation(3, state_root(accounts))).unwrap();
418 provider.commit().unwrap();
419 let replacement = replacement();
420 assert_eq!(replacement.block(1), applied);
421 replacement.replace_tip(&factory);
422 (factory, write, block(3).0)
423 }
424
425 fn index(value: u64) -> BlockAccessIndex {
426 BlockAccessIndex::new(value)
427 }
428
429 fn credit(balance: u64) -> Vec<AccountChanges> {
431 vec![AccountChanges::new(CHANGED)
432 .with_balance_change(BalanceChange::new(index(1), U256::from(balance)))]
433 }
434
435 fn stored(provider: &impl DBProvider, hashed_address: B256) -> Option<Account> {
436 provider.tx_ref().get::<tables::HashedAccounts>(hashed_address).unwrap()
437 }
438
439 #[test]
440 fn progress_starts_at_the_first_pivot_and_resumes_where_it_left_off() {
441 let accounts = accounts();
442 let (factory, write) = started(&accounts, accounts.len());
443 let provider = factory.database_provider_rw().unwrap();
444 let (block, parent) = block(2);
445
446 assert_eq!(
448 provider.catch_up_progress(write).unwrap().unwrap().applied(),
449 generation(1, B256::ZERO).target()
450 );
451
452 let progress =
453 provider.commit_block_access_list(write, block, parent, &credit(10)).unwrap();
454 assert_eq!(progress.applied(), block);
455 assert_eq!(progress.next(), 3);
456 assert_eq!(provider.catch_up_progress(write).unwrap(), Some(progress));
458 }
459
460 #[test]
461 fn a_list_commits_with_the_block_it_carries_the_state_to() {
462 let accounts = accounts();
463 let (factory, write) = started(&accounts, accounts.len());
464 let (block, parent) = block(2);
465 let changes = vec![AccountChanges::new(CHANGED)
466 .with_balance_change(BalanceChange::new(index(1), U256::from(10)))
467 .with_code_change(CodeChange::new(index(1), code()))
468 .with_storage_change(SlotChanges::new(
469 SLOT,
470 vec![StorageChange::new(index(1), U256::from(9))],
471 ))];
472
473 let provider = factory.database_provider_rw().unwrap();
474 provider.commit_block_access_list(write, block, parent, &changes).unwrap();
475 provider.commit().unwrap();
476
477 let provider = factory.database_provider_ro().unwrap();
478 let applied = stored(&provider, keccak256(CHANGED)).unwrap();
479 assert_eq!(applied.balance, U256::from(10));
480 assert_eq!(applied.bytecode_hash, Some(keccak256(code())));
481 assert!(provider.tx_ref().get::<tables::Bytecodes>(keccak256(code())).unwrap().is_some());
483 assert_eq!(
484 crate::test_utils::stored_slots(&provider, keccak256(CHANGED)),
485 [(hashed_slot(), U256::from(9))]
486 );
487 assert_eq!(provider.catch_up_progress(write).unwrap().unwrap().applied(), block);
488 }
489
490 #[test]
491 fn an_interrupted_application_leaves_nothing_behind() {
492 let accounts = accounts();
493 let (factory, write) = started(&accounts, accounts.len());
494 let (block, parent) = block(2);
495
496 let provider = factory.database_provider_rw().unwrap();
497 provider.commit_block_access_list(write, block, parent, &credit(10)).unwrap();
498 drop(provider);
499
500 let provider = factory.database_provider_ro().unwrap();
501 assert_eq!(stored(&provider, keccak256(CHANGED)).unwrap().balance, U256::from(1));
502 assert_eq!(provider.catch_up_progress(write).unwrap().unwrap().applied().number, 1);
503 }
504
505 #[test]
506 fn a_block_already_applied_is_refused() {
507 let accounts = accounts();
508 let (factory, write) = started(&accounts, accounts.len());
509 let provider = factory.database_provider_rw().unwrap();
510 let (block, parent) = block(2);
511 provider.commit_block_access_list(write, block, parent, &credit(10)).unwrap();
512
513 let duplicate = provider.commit_block_access_list(write, block, parent, &credit(20));
514
515 assert!(matches!(duplicate, Err(SnapSyncError::OutOfOrderBlock { expected: 3, got: 2 })));
516 assert_eq!(stored(&provider, keccak256(CHANGED)).unwrap().balance, U256::from(10));
517 }
518
519 #[test]
520 fn a_block_the_applied_state_has_not_reached_is_refused() {
521 let accounts = accounts();
522 let (factory, write) = started(&accounts, accounts.len());
523 let provider = factory.database_provider_rw().unwrap();
524 let (block, parent) = block(3);
525
526 let gap = provider.commit_block_access_list(write, block, parent, &credit(10));
527
528 assert!(matches!(gap, Err(SnapSyncError::OutOfOrderBlock { expected: 2, got: 3 })));
529 assert_eq!(stored(&provider, keccak256(CHANGED)).unwrap().balance, U256::from(1));
530 }
531
532 #[test]
533 fn a_block_the_canonical_chain_no_longer_holds_is_refused() {
534 let accounts = accounts();
535 let (factory, write) = started(&accounts, accounts.len());
536 let provider = factory.database_provider_rw().unwrap();
537 let (_, parent) = block(2);
538 let orphaned = BlockNumHash::new(2, B256::repeat_byte(0xee));
539
540 let refused = provider.commit_block_access_list(write, orphaned, parent, &credit(10));
541
542 assert!(matches!(refused, Err(SnapSyncError::NonCanonicalBlock { block: 2, .. })));
543 assert_eq!(stored(&provider, keccak256(CHANGED)).unwrap().balance, U256::from(1));
544 }
545
546 #[test]
547 fn a_list_proved_against_an_orphaned_pivot_is_refused() {
548 let accounts = accounts();
549 let (factory, write, orphaned) = orphaned_pivot(&accounts);
550 let provider = factory.database_provider_rw().unwrap();
551 let (applied, _) = block(2);
552 let (block, parent) = (replacement().block(2), applied.hash);
554 let before = SnapStateSnapshot::read(&provider);
555
556 let refused = provider.commit_block_access_list(write, block, parent, &credit(10));
557
558 assert!(matches!(
559 refused,
560 Err(SnapSyncError::NonCanonicalBlock { block: 3, hash }) if hash == orphaned.hash
561 ));
562 assert_eq!(provider.catch_up_progress(write).unwrap().unwrap().applied(), applied);
563 assert_eq!(SnapStateSnapshot::read(&provider), before);
564 provider.commit().unwrap();
565 assert_eq!(SnapStateSnapshot::read(&factory.database_provider_ro().unwrap()), before);
566 }
567
568 #[test]
569 fn an_orphaned_pivot_cannot_be_replaced() {
570 let accounts = accounts();
571 let (factory, write, orphaned) = orphaned_pivot(&accounts);
572 let provider = factory.database_provider_rw().unwrap();
573 let before = SnapStateSnapshot::read(&provider);
574 let replacement = SnapGeneration::new(replacement().block(2), state_root(&accounts));
575
576 let refused = provider.advance_snap_pivot(write, replacement);
577
578 assert!(matches!(
580 refused,
581 Err(SnapSyncError::NonCanonicalBlock { block: 3, hash }) if hash == orphaned.hash
582 ));
583 assert_eq!(provider.authorize_snap_write(write).unwrap().pivot(), orphaned);
584 assert_eq!(SnapStateSnapshot::read(&provider), before);
585 provider.commit().unwrap();
586 assert_eq!(SnapStateSnapshot::read(&factory.database_provider_ro().unwrap()), before);
587 }
588
589 #[test]
590 fn a_block_building_on_another_chain_is_refused() {
591 let accounts = accounts();
592 let (factory, write) = started(&accounts, accounts.len());
593 let provider = factory.database_provider_rw().unwrap();
594 let (block, _) = block(2);
595
596 let forked =
597 provider.commit_block_access_list(write, block, B256::repeat_byte(0xee), &credit(10));
598
599 assert!(matches!(forked, Err(SnapSyncError::ForkedBlock { .. })));
600 assert_eq!(provider.catch_up_progress(write).unwrap().unwrap().applied().number, 1);
601 }
602
603 #[test]
604 fn a_block_that_changes_nothing_still_carries_the_state_past_it() {
605 let accounts = accounts();
606 let (factory, write) = started(&accounts, accounts.len());
607 let provider = factory.database_provider_rw().unwrap();
608 let (block, parent) = block(2);
609 let read_only = vec![AccountChanges::new(CHANGED).with_storage_read(SLOT)];
611
612 let progress = provider.commit_block_access_list(write, block, parent, &read_only).unwrap();
613
614 assert_eq!(progress.applied(), block);
615 assert_eq!(stored(&provider, keccak256(CHANGED)).unwrap().balance, U256::from(1));
616 }
617
618 #[test]
619 fn an_account_the_list_empties_loses_its_storage() {
620 let accounts = accounts();
621 let (factory, write) = started(&accounts, accounts.len());
622 let provider = factory.database_provider_rw().unwrap();
623 let (block, parent) = block(2);
624
625 provider.commit_block_access_list(write, block, parent, &credit(0)).unwrap();
626
627 assert_eq!(stored(&provider, keccak256(CHANGED)), None);
628 assert!(crate::test_utils::stored_slots(&provider, keccak256(CHANGED)).is_empty());
629 }
630
631 #[test]
632 fn a_block_past_the_pivot_is_refused() {
633 let accounts = accounts();
634 let (factory, write) = started(&accounts, accounts.len());
635 let provider = factory.database_provider_rw().unwrap();
636 let (block, parent) = block(2);
637 provider.commit_block_access_list(write, block, parent, &credit(10)).unwrap();
638 let (block, parent) = self::block(3);
639
640 let refused = provider.commit_block_access_list(write, block, parent, &credit(20));
641
642 assert!(matches!(refused, Err(SnapSyncError::BlockPastPivot { pivot: 2, block: 3 })));
643 assert_eq!(stored(&provider, keccak256(CHANGED)).unwrap().balance, U256::from(10));
644 }
645
646 #[test]
647 fn an_account_outside_the_coverage_is_left_to_its_range() {
648 let accounts = accounts();
649 let index = changed_index(&accounts);
650 let (factory, write) = started(&accounts, index);
651 let provider = factory.database_provider_rw().unwrap();
652 let (block, parent) = block(2);
653
654 let progress =
655 provider.commit_block_access_list(write, block, parent, &credit(10)).unwrap();
656
657 assert_eq!(stored(&provider, keccak256(CHANGED)), None);
659 assert_eq!(progress.applied(), block);
660 }
661
662 #[test]
663 fn slots_persisted_ahead_of_their_range_follow_the_lists() {
664 let factory = hashed_factory();
665 insert_headers(&factory, &chain().headers);
666 let provider = factory.database_provider_rw().unwrap();
667 let write = provider.start_snap_attempt(generation(1, state_root(&accounts()))).unwrap();
668 provider.start_account_coverage(write).unwrap();
669 let (changed, stored_slot) = (keccak256(CHANGED), (hashed_slot(), U256::from(7)));
670 let chunk = StorageChunk::new(
671 changed,
672 storage_root_of(&[stored_slot]),
673 B256::ZERO,
674 vec![stored_slot],
675 None,
676 );
677 provider.commit_storage_chunk(write, B256::ZERO, chunk).unwrap();
678 let mut moved = accounts();
679 let position = changed_index(&moved);
680 moved[position].1.storage_root = storage_root_of(&[(hashed_slot(), U256::from(9))]);
681 let write = provider.advance_snap_pivot(write, generation(2, state_root(&moved))).unwrap();
682 let (block, parent) = block(2);
683 let changes = vec![AccountChanges::new(CHANGED).with_storage_change(SlotChanges::new(
684 SLOT,
685 vec![StorageChange::new(index(1), U256::from(9))],
686 ))];
687
688 provider.commit_block_access_list(write, block, parent, &changes).unwrap();
689
690 assert_eq!(stored(&provider, changed), None);
692 assert_eq!(
693 crate::test_utils::stored_slots(&provider, changed),
694 [(hashed_slot(), U256::from(9))]
695 );
696 let range = crate::test_utils::verified_range(&moved, 0..moved.len(), B256::ZERO, &[]);
697 let coverage =
698 provider.commit_account_range(write, &range, B256Map::default(), Vec::new()).unwrap();
699 assert!(coverage.is_complete());
700 }
701
702 #[test]
703 #[allow(clippy::clone_on_copy)]
704 fn skipped_storage_from_an_abandoned_attempt_is_not_reused() {
705 let factory = hashed_factory();
706 insert_headers(&factory, &chain().headers);
707 let provider = factory.database_provider_rw().unwrap();
708 let (changed, later) = (keccak256(CHANGED), B256::repeat_byte(0xff));
709 let stale = (B256::ZERO, U256::from(7));
710 let mut contract = account(1);
711 contract.storage_root = storage_root_of(&[stale]);
712 let mut accounts = vec![(changed, contract.clone()), (later, contract.clone())];
713 let write = provider.start_snap_attempt(generation(1, state_root(&accounts))).unwrap();
714 provider.start_account_coverage(write).unwrap();
715 provider
716 .commit_storage_chunk(
717 write,
718 B256::ZERO,
719 StorageChunk::new(changed, contract.storage_root, B256::ZERO, vec![stale], None),
720 )
721 .unwrap();
722
723 accounts[0].1.storage_root = reth_trie_common::EMPTY_ROOT_HASH;
724 let write = provider.start_snap_attempt(generation(1, state_root(&accounts))).unwrap();
725 provider.start_account_coverage(write).unwrap();
726 provider
727 .commit_storage_chunk(
728 write,
729 B256::ZERO,
730 StorageChunk::new(later, contract.storage_root, B256::ZERO, vec![stale], None),
731 )
732 .unwrap();
733
734 let new_slot = (hashed_slot(), U256::from(9));
735 accounts[0].1.storage_root = storage_root_of(&[new_slot]);
736 let write =
737 provider.advance_snap_pivot(write, generation(2, state_root(&accounts))).unwrap();
738 let (block, parent) = block(2);
739 let changes = vec![AccountChanges::new(CHANGED).with_storage_change(SlotChanges::new(
740 SLOT,
741 vec![StorageChange::new(index(1), new_slot.1)],
742 ))];
743 provider.commit_block_access_list(write, block, parent, &changes).unwrap();
744
745 assert_eq!(crate::test_utils::stored_slots(&provider, changed), [new_slot]);
746 let range =
747 crate::test_utils::verified_range(&accounts, 0..accounts.len(), B256::ZERO, &[]);
748 assert!(provider
749 .commit_account_range(write, &range, B256Map::default(), Vec::new())
750 .unwrap()
751 .is_complete());
752 }
753
754 #[test]
755 fn a_list_from_a_replaced_attempt_changes_nothing() {
756 let accounts = accounts();
757 let (factory, write) = started(&accounts, accounts.len());
758 let provider = factory.database_provider_rw().unwrap();
759 provider.advance_snap_pivot(write, generation(3, B256::repeat_byte(0xcc))).unwrap();
760 let (block, parent) = block(2);
761
762 let refused = provider.commit_block_access_list(write, block, parent, &credit(10));
763
764 assert!(matches!(refused, Err(SnapSyncError::StaleWrite { .. })));
765 assert!(matches!(provider.catch_up_progress(write), Err(SnapSyncError::StaleWrite { .. })));
766 }
767
768 #[test]
769 fn a_record_this_build_cannot_read_is_reported() {
770 let accounts = accounts();
771 let (factory, write) = started(&accounts, accounts.len());
772 let provider = factory.database_provider_rw().unwrap();
773 provider.write_metadata(StoredCatchUpProgress::KEY, br#"{"version":0}"#.to_vec()).unwrap();
774
775 assert!(matches!(
776 provider.catch_up_progress(write),
777 Err(SnapSyncError::UnsupportedRecord {
778 key: StoredCatchUpProgress::KEY,
779 version: Some(0)
780 })
781 ));
782 }
783
784 #[test]
785 fn a_list_resolves_the_repairs_it_overwrites() {
786 let accounts = accounts();
787 let (factory, write) = started(&accounts, accounts.len());
788 let provider = factory.database_provider_rw().unwrap();
789 let orphaned = AccountChanges::new(CHANGED)
791 .with_balance_change(BalanceChange::new(index(1), U256::from(1)))
792 .with_nonce_change(NonceChange::new(index(1), 1));
793 let mut repairs = StateRepairs::default();
794 repairs.insert_changes(keccak256(CHANGED), &orphaned);
795 provider.schedule_snap_repairs(write, repairs).unwrap();
796 let (block, parent) = block(2);
797
798 provider.commit_block_access_list(write, block, parent, &credit(10)).unwrap();
800
801 let mut expected = StateRepairs::default();
802 expected.insert_changes(
803 keccak256(CHANGED),
804 &AccountChanges::new(CHANGED).with_nonce_change(NonceChange::new(index(1), 1)),
805 );
806 assert_eq!(provider.snap_repairs(write).unwrap(), expected);
807 }
808
809 #[test]
810 fn repairs_of_an_account_outside_the_coverage_stay_scheduled() {
811 let accounts = accounts();
812 let (factory, write) = started(&accounts, changed_index(&accounts));
813 let provider = factory.database_provider_rw().unwrap();
814 let mut repairs = StateRepairs::default();
815 repairs.insert_account(keccak256(CHANGED));
816 provider.schedule_snap_repairs(write, repairs.clone()).unwrap();
817 let (block, parent) = block(2);
818
819 provider.commit_block_access_list(write, block, parent, &credit(10)).unwrap();
821
822 assert_eq!(provider.snap_repairs(write).unwrap(), repairs);
823 }
824}