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 let root = state_root(accounts);
362 insert_headers(&factory, &chain().headers_with_roots(&[(1, root), (2, root), (3, root)]));
363 let provider = factory.database_provider_rw().unwrap();
364 let write = provider.start_snap_attempt(generation(1, root)).unwrap();
365 provider.start_account_coverage(write).unwrap();
366 let targets = (served < accounts.len()).then(|| accounts[served - 1].0);
368 let range =
369 crate::test_utils::verified_range(accounts, 0..served, B256::ZERO, targets.as_slice());
370 let storages = B256Map::from_iter([(
371 keccak256(CHANGED),
372 HashedStorage::from_iter([(hashed_slot(), U256::from(7))]),
373 )]);
374 provider
375 .commit_account_range(
376 write,
377 &range,
378 if served == accounts.len() { storages } else { B256Map::default() },
379 Vec::new(),
380 )
381 .unwrap();
382 let write =
383 provider.advance_snap_pivot(write, generation(2, state_root(accounts))).unwrap();
384 provider.commit().unwrap();
385 (factory, write)
386 }
387
388 fn chain() -> BalChain {
391 BalChain::new(1, [Vec::new(), Vec::new()])
392 }
393
394 fn generation(number: u64, state_root: B256) -> SnapGeneration {
396 SnapGeneration::new(chain().block(number as usize - 1), state_root)
397 }
398
399 fn block(number: u64) -> (BlockNumHash, B256) {
401 let chain = chain();
402 (chain.block(number as usize - 1), chain.block(number as usize - 2).hash)
403 }
404
405 fn replacement() -> BalChain {
407 BalChain::new(1, [Vec::new(), credit(1)])
408 }
409
410 fn orphaned_pivot(accounts: &[(B256, TrieAccount)]) -> (Factory, SnapWrite, BlockNumHash) {
413 let (factory, write) = started(accounts, accounts.len());
414 let provider = factory.database_provider_rw().unwrap();
415 let (applied, parent) = block(2);
416 provider.commit_block_access_list(write, applied, parent, &[]).unwrap();
417 let write =
418 provider.advance_snap_pivot(write, generation(3, state_root(accounts))).unwrap();
419 provider.commit().unwrap();
420 let replacement = replacement();
421 assert_eq!(replacement.block(1), applied);
422 replacement.replace_tip(&factory);
423 (factory, write, block(3).0)
424 }
425
426 fn index(value: u64) -> BlockAccessIndex {
427 BlockAccessIndex::new(value)
428 }
429
430 fn credit(balance: u64) -> Vec<AccountChanges> {
432 vec![AccountChanges::new(CHANGED)
433 .with_balance_change(BalanceChange::new(index(1), U256::from(balance)))]
434 }
435
436 fn stored(provider: &impl DBProvider, hashed_address: B256) -> Option<Account> {
437 provider.tx_ref().get::<tables::HashedAccounts>(hashed_address).unwrap()
438 }
439
440 #[test]
441 fn progress_starts_at_the_first_pivot_and_resumes_where_it_left_off() {
442 let accounts = accounts();
443 let (factory, write) = started(&accounts, accounts.len());
444 let provider = factory.database_provider_rw().unwrap();
445 let (block, parent) = block(2);
446
447 assert_eq!(
449 provider.catch_up_progress(write).unwrap().unwrap().applied(),
450 generation(1, B256::ZERO).target()
451 );
452
453 let progress =
454 provider.commit_block_access_list(write, block, parent, &credit(10)).unwrap();
455 assert_eq!(progress.applied(), block);
456 assert_eq!(progress.next(), 3);
457 assert_eq!(provider.catch_up_progress(write).unwrap(), Some(progress));
459 }
460
461 #[test]
462 fn a_list_commits_with_the_block_it_carries_the_state_to() {
463 let accounts = accounts();
464 let (factory, write) = started(&accounts, accounts.len());
465 let (block, parent) = block(2);
466 let changes = vec![AccountChanges::new(CHANGED)
467 .with_balance_change(BalanceChange::new(index(1), U256::from(10)))
468 .with_code_change(CodeChange::new(index(1), code()))
469 .with_storage_change(SlotChanges::new(
470 SLOT,
471 vec![StorageChange::new(index(1), U256::from(9))],
472 ))];
473
474 let provider = factory.database_provider_rw().unwrap();
475 provider.commit_block_access_list(write, block, parent, &changes).unwrap();
476 provider.commit().unwrap();
477
478 let provider = factory.database_provider_ro().unwrap();
479 let applied = stored(&provider, keccak256(CHANGED)).unwrap();
480 assert_eq!(applied.balance, U256::from(10));
481 assert_eq!(applied.bytecode_hash, Some(keccak256(code())));
482 assert!(provider.tx_ref().get::<tables::Bytecodes>(keccak256(code())).unwrap().is_some());
484 assert_eq!(
485 crate::test_utils::stored_slots(&provider, keccak256(CHANGED)),
486 [(hashed_slot(), U256::from(9))]
487 );
488 assert_eq!(provider.catch_up_progress(write).unwrap().unwrap().applied(), block);
489 }
490
491 #[test]
492 fn a_block_already_applied_is_refused() {
493 let accounts = accounts();
494 let (factory, write) = started(&accounts, accounts.len());
495 let provider = factory.database_provider_rw().unwrap();
496 let (block, parent) = block(2);
497 provider.commit_block_access_list(write, block, parent, &credit(10)).unwrap();
498
499 let duplicate = provider.commit_block_access_list(write, block, parent, &credit(20));
500
501 assert!(matches!(duplicate, Err(SnapSyncError::OutOfOrderBlock { expected: 3, got: 2 })));
502 assert_eq!(stored(&provider, keccak256(CHANGED)).unwrap().balance, U256::from(10));
503 }
504
505 #[test]
506 fn a_block_the_applied_state_has_not_reached_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(3);
511
512 let gap = provider.commit_block_access_list(write, block, parent, &credit(10));
513
514 assert!(matches!(gap, Err(SnapSyncError::OutOfOrderBlock { expected: 2, got: 3 })));
515 assert_eq!(stored(&provider, keccak256(CHANGED)).unwrap().balance, U256::ONE);
516 }
517
518 #[test]
519 fn a_block_the_canonical_chain_no_longer_holds_is_refused() {
520 let accounts = accounts();
521 let (factory, write) = started(&accounts, accounts.len());
522 let provider = factory.database_provider_rw().unwrap();
523 let (_, parent) = block(2);
524 let orphaned = BlockNumHash::new(2, B256::repeat_byte(0xee));
525
526 let refused = provider.commit_block_access_list(write, orphaned, parent, &credit(10));
527
528 assert!(matches!(refused, Err(SnapSyncError::NonCanonicalBlock { block: 2, .. })));
529 assert_eq!(stored(&provider, keccak256(CHANGED)).unwrap().balance, U256::ONE);
530 }
531
532 #[test]
533 fn a_list_proved_against_an_orphaned_pivot_is_refused() {
534 let accounts = accounts();
535 let (factory, write, orphaned) = orphaned_pivot(&accounts);
536 let provider = factory.database_provider_rw().unwrap();
537 let (applied, _) = block(2);
538 let (block, parent) = (replacement().block(2), applied.hash);
540 let before = SnapStateSnapshot::read(&provider);
541
542 let refused = provider.commit_block_access_list(write, block, parent, &credit(10));
543
544 assert!(matches!(
545 refused,
546 Err(SnapSyncError::NonCanonicalBlock { block: 3, hash }) if hash == orphaned.hash
547 ));
548 assert_eq!(provider.catch_up_progress(write).unwrap().unwrap().applied(), applied);
549 assert_eq!(SnapStateSnapshot::read(&provider), before);
550 provider.commit().unwrap();
551 assert_eq!(SnapStateSnapshot::read(&factory.database_provider_ro().unwrap()), before);
552 }
553
554 #[test]
555 fn an_orphaned_pivot_cannot_be_replaced() {
556 let accounts = accounts();
557 let (factory, write, orphaned) = orphaned_pivot(&accounts);
558 let provider = factory.database_provider_rw().unwrap();
559 let before = SnapStateSnapshot::read(&provider);
560 let replacement = SnapGeneration::new(replacement().block(2), state_root(&accounts));
561
562 let refused = provider.advance_snap_pivot(write, replacement);
563
564 assert!(matches!(
566 refused,
567 Err(SnapSyncError::NonCanonicalBlock { block: 3, hash }) if hash == orphaned.hash
568 ));
569 assert_eq!(provider.authorize_snap_write(write).unwrap().pivot(), orphaned);
570 assert_eq!(SnapStateSnapshot::read(&provider), before);
571 provider.commit().unwrap();
572 assert_eq!(SnapStateSnapshot::read(&factory.database_provider_ro().unwrap()), before);
573 }
574
575 #[test]
576 fn a_block_building_on_another_chain_is_refused() {
577 let accounts = accounts();
578 let (factory, write) = started(&accounts, accounts.len());
579 let provider = factory.database_provider_rw().unwrap();
580 let (block, _) = block(2);
581
582 let forked =
583 provider.commit_block_access_list(write, block, B256::repeat_byte(0xee), &credit(10));
584
585 assert!(matches!(forked, Err(SnapSyncError::ForkedBlock { .. })));
586 assert_eq!(provider.catch_up_progress(write).unwrap().unwrap().applied().number, 1);
587 }
588
589 #[test]
590 fn an_account_the_list_empties_loses_its_storage() {
591 let accounts = accounts();
592 let (factory, write) = started(&accounts, accounts.len());
593 let provider = factory.database_provider_rw().unwrap();
594 let (block, parent) = block(2);
595
596 provider.commit_block_access_list(write, block, parent, &credit(0)).unwrap();
597
598 assert_eq!(stored(&provider, keccak256(CHANGED)), None);
599 assert!(crate::test_utils::stored_slots(&provider, keccak256(CHANGED)).is_empty());
600 }
601
602 #[test]
603 fn a_block_past_the_pivot_is_refused() {
604 let accounts = accounts();
605 let (factory, write) = started(&accounts, accounts.len());
606 let provider = factory.database_provider_rw().unwrap();
607 let (block, parent) = block(2);
608 provider.commit_block_access_list(write, block, parent, &credit(10)).unwrap();
609 let (block, parent) = self::block(3);
610
611 let refused = provider.commit_block_access_list(write, block, parent, &credit(20));
612
613 assert!(matches!(refused, Err(SnapSyncError::BlockPastPivot { pivot: 2, block: 3 })));
614 assert_eq!(stored(&provider, keccak256(CHANGED)).unwrap().balance, U256::from(10));
615 }
616
617 #[test]
618 fn an_account_outside_the_coverage_is_left_to_its_range() {
619 let accounts = accounts();
620 let index = changed_index(&accounts);
621 let (factory, write) = started(&accounts, index);
622 let provider = factory.database_provider_rw().unwrap();
623 let (block, parent) = block(2);
624
625 let progress =
626 provider.commit_block_access_list(write, block, parent, &credit(10)).unwrap();
627
628 assert_eq!(stored(&provider, keccak256(CHANGED)), None);
630 assert_eq!(progress.applied(), block);
631 }
632
633 #[test]
634 fn slots_persisted_ahead_of_their_range_follow_the_lists() {
635 let factory = hashed_factory();
636 let mut moved = accounts();
637 let position = changed_index(&moved);
638 moved[position].1.storage_root = storage_root_of(&[(hashed_slot(), U256::from(9))]);
639 insert_headers(
640 &factory,
641 &chain().headers_with_roots(&[(1, state_root(&accounts())), (2, state_root(&moved))]),
642 );
643 let provider = factory.database_provider_rw().unwrap();
644 let write = provider.start_snap_attempt(generation(1, state_root(&accounts()))).unwrap();
645 provider.start_account_coverage(write).unwrap();
646 let (changed, stored_slot) = (keccak256(CHANGED), (hashed_slot(), U256::from(7)));
647 let chunk = StorageChunk::new(
648 changed,
649 storage_root_of(&[stored_slot]),
650 B256::ZERO,
651 vec![stored_slot],
652 None,
653 );
654 provider.commit_storage_chunk(write, B256::ZERO, chunk).unwrap();
655 let write = provider.advance_snap_pivot(write, generation(2, state_root(&moved))).unwrap();
656 let (block, parent) = block(2);
657 let changes = vec![AccountChanges::new(CHANGED).with_storage_change(SlotChanges::new(
658 SLOT,
659 vec![StorageChange::new(index(1), U256::from(9))],
660 ))];
661
662 provider.commit_block_access_list(write, block, parent, &changes).unwrap();
663
664 assert_eq!(stored(&provider, changed), None);
666 assert_eq!(
667 crate::test_utils::stored_slots(&provider, changed),
668 [(hashed_slot(), U256::from(9))]
669 );
670 let range = crate::test_utils::verified_range(&moved, 0..moved.len(), B256::ZERO, &[]);
671 let coverage =
672 provider.commit_account_range(write, &range, B256Map::default(), Vec::new()).unwrap();
673 assert!(coverage.is_complete());
674 }
675
676 #[test]
677 #[allow(clippy::clone_on_copy)]
678 fn skipped_storage_from_an_abandoned_attempt_is_not_reused() {
679 let factory = hashed_factory();
680 let (changed, later) = (keccak256(CHANGED), B256::repeat_byte(0xff));
681 let stale = (B256::ZERO, U256::from(7));
682 let mut contract = account(1);
683 contract.storage_root = storage_root_of(&[stale]);
684 let mut accounts = vec![(changed, contract.clone()), (later, contract.clone())];
685 let first = state_root(&accounts);
686 accounts[0].1.storage_root = reth_trie_common::EMPTY_ROOT_HASH;
687 let second = state_root(&accounts);
688 let new_slot = (hashed_slot(), U256::from(9));
689 accounts[0].1.storage_root = storage_root_of(&[new_slot]);
690 let third = state_root(&accounts);
691 accounts[0].1.storage_root = contract.storage_root;
692 insert_headers(
693 &factory,
694 &chain().headers_with_roots(&[(1, first), (2, second), (3, third)]),
695 );
696 let provider = factory.database_provider_rw().unwrap();
697 let write = provider.start_snap_attempt(generation(1, state_root(&accounts))).unwrap();
698 provider.start_account_coverage(write).unwrap();
699 provider
700 .commit_storage_chunk(
701 write,
702 B256::ZERO,
703 StorageChunk::new(changed, contract.storage_root, B256::ZERO, vec![stale], None),
704 )
705 .unwrap();
706
707 accounts[0].1.storage_root = reth_trie_common::EMPTY_ROOT_HASH;
708 let write = provider.start_snap_attempt(generation(2, state_root(&accounts))).unwrap();
709 provider.start_account_coverage(write).unwrap();
710 provider
711 .commit_storage_chunk(
712 write,
713 B256::ZERO,
714 StorageChunk::new(later, contract.storage_root, B256::ZERO, vec![stale], None),
715 )
716 .unwrap();
717
718 accounts[0].1.storage_root = storage_root_of(&[new_slot]);
719 let write =
720 provider.advance_snap_pivot(write, generation(3, state_root(&accounts))).unwrap();
721 let (block, parent) = block(3);
722 let changes = vec![AccountChanges::new(CHANGED).with_storage_change(SlotChanges::new(
723 SLOT,
724 vec![StorageChange::new(index(1), new_slot.1)],
725 ))];
726 provider.commit_block_access_list(write, block, parent, &changes).unwrap();
727
728 assert_eq!(crate::test_utils::stored_slots(&provider, changed), [new_slot]);
729 let range =
730 crate::test_utils::verified_range(&accounts, 0..accounts.len(), B256::ZERO, &[]);
731 assert!(provider
732 .commit_account_range(write, &range, B256Map::default(), Vec::new())
733 .unwrap()
734 .is_complete());
735 }
736
737 #[test]
738 fn a_list_from_a_replaced_attempt_changes_nothing() {
739 let accounts = accounts();
740 let (factory, write) = started(&accounts, accounts.len());
741 let provider = factory.database_provider_rw().unwrap();
742 provider.advance_snap_pivot(write, generation(3, state_root(&accounts))).unwrap();
743 let (block, parent) = block(2);
744
745 let refused = provider.commit_block_access_list(write, block, parent, &credit(10));
746
747 assert!(matches!(refused, Err(SnapSyncError::StaleWrite { .. })));
748 assert!(matches!(provider.catch_up_progress(write), Err(SnapSyncError::StaleWrite { .. })));
749 }
750
751 #[test]
752 fn a_record_this_build_cannot_read_is_reported() {
753 let accounts = accounts();
754 let (factory, write) = started(&accounts, accounts.len());
755 let provider = factory.database_provider_rw().unwrap();
756 provider.write_metadata(StoredCatchUpProgress::KEY, br#"{"version":0}"#.to_vec()).unwrap();
757
758 assert!(matches!(
759 provider.catch_up_progress(write),
760 Err(SnapSyncError::UnsupportedRecord {
761 key: StoredCatchUpProgress::KEY,
762 version: Some(0)
763 })
764 ));
765 }
766
767 #[test]
768 fn a_list_resolves_the_repairs_it_overwrites() {
769 let accounts = accounts();
770 let (factory, write) = started(&accounts, accounts.len());
771 let provider = factory.database_provider_rw().unwrap();
772 let orphaned = AccountChanges::new(CHANGED)
774 .with_balance_change(BalanceChange::new(index(1), U256::ONE))
775 .with_nonce_change(NonceChange::new(index(1), 1));
776 let mut repairs = StateRepairs::default();
777 repairs.insert_changes(keccak256(CHANGED), &orphaned);
778 provider.schedule_snap_repairs(write, repairs).unwrap();
779 let (block, parent) = block(2);
780
781 provider.commit_block_access_list(write, block, parent, &credit(10)).unwrap();
783
784 let mut expected = StateRepairs::default();
785 expected.insert_changes(
786 keccak256(CHANGED),
787 &AccountChanges::new(CHANGED).with_nonce_change(NonceChange::new(index(1), 1)),
788 );
789 assert_eq!(provider.snap_repairs(write).unwrap(), expected);
790 }
791
792 #[test]
793 fn repairs_of_an_account_outside_the_coverage_stay_scheduled() {
794 let accounts = accounts();
795 let (factory, write) = started(&accounts, changed_index(&accounts));
796 let provider = factory.database_provider_rw().unwrap();
797 let mut repairs = StateRepairs::default();
798 repairs.insert_account(keccak256(CHANGED));
799 provider.schedule_snap_repairs(write, repairs.clone()).unwrap();
800 let (block, parent) = block(2);
801
802 provider.commit_block_access_list(write, block, parent, &credit(10)).unwrap();
804
805 assert_eq!(provider.snap_repairs(write).unwrap(), repairs);
806 }
807}