1use crate::{
7 common::SnapRecord, repair::StoredRepairs, storage::persisted_storage_root, SnapAttemptStore,
8 SnapCatchUpStore, SnapStorageStore, SnapSyncError, SnapWrite, StateRepairs, StorageProgress,
9};
10use alloy_primitives::{
11 map::{B256Map, B256Set},
12 B256, KECCAK256_EMPTY, U256,
13};
14use reth_db_api::{
15 tables,
16 transaction::{DbTx, DbTxMut},
17 RawKey, RawTable,
18};
19use reth_downloaders::snap::VerifiedAccountRange;
20use reth_primitives_traits::Account;
21use reth_storage_api::{
22 BlockHashReader, DBProvider, MetadataProvider, MetadataWriter, SnapAttemptId, StateWriter,
23};
24use reth_trie_common::{
25 root::storage_root, HashedPostState, HashedPostStateSorted, HashedStorage, TrieAccount,
26 EMPTY_ROOT_HASH,
27};
28use revm::{bytecode::Bytecode, database::states::StateChangeset};
29use serde::{Deserialize, Serialize};
30use std::ops::Bound;
31
32pub trait SnapAccountStore {
37 fn start_account_coverage(&self, write: SnapWrite) -> Result<AccountCoverage, SnapSyncError>
40 where
41 Self: MetadataWriter;
42
43 fn account_coverage(&self, write: SnapWrite) -> Result<Option<AccountCoverage>, SnapSyncError>;
45
46 fn commit_account_range(
52 &self,
53 write: SnapWrite,
54 range: &VerifiedAccountRange,
55 storages: B256Map<HashedStorage>,
56 bytecodes: Vec<(B256, Bytecode)>,
57 ) -> Result<AccountCoverage, SnapSyncError>
58 where
59 Self: MetadataWriter + StateWriter + DBProvider<Tx: DbTxMut>;
60
61 fn remove_storages_except(
64 &self,
65 interval: (Bound<B256>, Bound<B256>),
66 kept: &[B256],
67 ) -> Result<(), SnapSyncError>
68 where
69 Self: DBProvider<Tx: DbTxMut>;
70
71 fn snap_repairs(&self, write: SnapWrite) -> Result<StateRepairs, SnapSyncError>;
73
74 fn schedule_snap_repairs(
76 &self,
77 write: SnapWrite,
78 repairs: StateRepairs,
79 ) -> Result<(), SnapSyncError>
80 where
81 Self: MetadataWriter;
82
83 fn commit_account_repair(
86 &self,
87 write: SnapWrite,
88 range: &VerifiedAccountRange,
89 slots: Vec<(B256, U256)>,
90 ) -> Result<usize, SnapSyncError>
91 where
92 Self: BlockHashReader + MetadataWriter + StateWriter + DBProvider<Tx: DbTxMut>;
93}
94
95#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
97pub struct AccountCoverage {
98 next: Option<B256>,
100}
101
102impl AccountCoverage {
103 pub const START: Self = Self { next: Some(B256::ZERO) };
105
106 pub const COMPLETE: Self = Self { next: None };
108
109 pub const fn next(&self) -> Option<B256> {
111 self.next
112 }
113
114 pub const fn is_complete(&self) -> bool {
116 self.next.is_none()
117 }
118
119 fn advance(&self, range: &VerifiedAccountRange) -> Result<Self, SnapSyncError> {
121 let origin = range.origin();
122 if self.next != Some(origin) {
123 return Err(SnapSyncError::OutOfOrderRange { expected: self.next, got: origin })
124 }
125 if range.next().is_some_and(|next| next <= origin) {
126 return Err(SnapSyncError::NoProgress { origin })
127 }
128 Ok(Self { next: range.next() })
129 }
130
131 fn interval_to(&self, to: &Self) -> (Bound<B256>, Bound<B256>) {
133 (
134 self.next.map_or(Bound::Unbounded, Bound::Included),
135 to.next.map_or(Bound::Unbounded, Bound::Excluded),
136 )
137 }
138}
139
140#[derive(Serialize, Deserialize)]
142pub(crate) struct StoredCoverage {
143 version: u32,
145 attempt: SnapAttemptId,
147 coverage: AccountCoverage,
149}
150
151impl SnapRecord for StoredCoverage {
152 const KEY: &'static str = "snap_account_coverage";
153 const VERSION: u32 = 1;
154}
155
156impl StoredCoverage {
157 const fn new(attempt: SnapAttemptId, coverage: AccountCoverage) -> Self {
159 Self { version: Self::VERSION, attempt, coverage }
160 }
161}
162
163impl<T: MetadataProvider> SnapAccountStore for T {
164 fn start_account_coverage(&self, write: SnapWrite) -> Result<AccountCoverage, SnapSyncError>
166 where
167 Self: MetadataWriter,
168 {
169 if let Some(coverage) = self.account_coverage(write)? {
170 return Ok(coverage)
171 }
172 let start = AccountCoverage::START;
173 StoredCoverage::new(write.attempt(), start).write(self)?;
174 Ok(start)
175 }
176
177 fn account_coverage(&self, write: SnapWrite) -> Result<Option<AccountCoverage>, SnapSyncError> {
179 self.authorize_snap_write(write)?;
180 let Some(stored) = StoredCoverage::read(self)? else { return Ok(None) };
181 Ok((stored.attempt == write.attempt()).then_some(stored.coverage))
182 }
183
184 fn commit_account_range(
186 &self,
187 write: SnapWrite,
188 range: &VerifiedAccountRange,
189 storages: B256Map<HashedStorage>,
190 bytecodes: Vec<(B256, Bytecode)>,
191 ) -> Result<AccountCoverage, SnapSyncError>
192 where
193 Self: MetadataWriter + StateWriter + DBProvider<Tx: DbTxMut>,
194 {
195 let attempt = self.authorize_snap_write(write)?;
196 if range.state_root() != attempt.state_root() {
197 return Err(SnapSyncError::RootMismatch {
198 expected: attempt.state_root(),
199 got: range.state_root(),
200 })
201 }
202 let coverage = self.account_coverage(write)?.ok_or(SnapSyncError::NoCoverage)?;
203 let advanced = coverage.advance(range)?;
204 let progress = self.storage_progress(write, range.origin())?;
205 if progress != StorageProgress::START {
207 let applied = self
208 .catch_up_progress(write)?
209 .ok_or(SnapSyncError::NoCatchUpProgress)?
210 .applied()
211 .number;
212 if applied < attempt.pivot().number {
213 return Err(SnapSyncError::CatchUpBehindPivot {
214 applied,
215 pivot: attempt.pivot().number,
216 })
217 }
218 }
219 let dependencies = RangeDependencies::new(range.accounts(), storages, bytecodes);
220 let persisted = dependencies.verify(range.accounts(), &progress, self.tx_ref())?;
221
222 let interval = coverage.interval_to(&advanced);
223 self.remove::<tables::HashedAccounts>(interval)?;
224 self.remove_storages_except(interval, &persisted)?;
225 dependencies.write(self)?;
226 StoredCoverage::new(write.attempt(), advanced).write(self)?;
227 if let Some(next) = advanced.next() {
229 progress.carry_to(self, write, next)?;
230 }
231 Ok(advanced)
232 }
233
234 fn remove_storages_except(
235 &self,
236 (mut start, end): (Bound<B256>, Bound<B256>),
237 kept: &[B256],
238 ) -> Result<(), SnapSyncError>
239 where
240 Self: DBProvider<Tx: DbTxMut>,
241 {
242 for account in kept {
243 self.remove::<tables::HashedStorages>((start, Bound::Excluded(*account)))?;
244 let Some(after) = U256::from_be_bytes(account.0).checked_add(U256::ONE) else {
246 return Ok(())
247 };
248 start = Bound::Included(after.into());
249 }
250 self.remove::<tables::HashedStorages>((start, end))?;
251 Ok(())
252 }
253
254 fn snap_repairs(&self, write: SnapWrite) -> Result<StateRepairs, SnapSyncError> {
256 self.authorize_snap_write(write)?;
257 let Some(stored) = StoredRepairs::read(self)? else { return Ok(StateRepairs::default()) };
258 Ok(if stored.attempt == write.attempt() { stored.repairs } else { StateRepairs::default() })
259 }
260
261 fn schedule_snap_repairs(
262 &self,
263 write: SnapWrite,
264 repairs: StateRepairs,
265 ) -> Result<(), SnapSyncError>
266 where
267 Self: MetadataWriter,
268 {
269 let mut scheduled = self.snap_repairs(write)?;
270 scheduled.extend(repairs);
271 StoredRepairs::store(self, write.attempt(), scheduled)
272 }
273
274 #[allow(clippy::clone_on_copy)]
276 fn commit_account_repair(
277 &self,
278 write: SnapWrite,
279 range: &VerifiedAccountRange,
280 slots: Vec<(B256, U256)>,
281 ) -> Result<usize, SnapSyncError>
282 where
283 Self: BlockHashReader + MetadataWriter + StateWriter + DBProvider<Tx: DbTxMut>,
284 {
285 let attempt = self.authorize_canonical_snap_write(write)?;
287 if range.state_root() != attempt.state_root() {
288 return Err(SnapSyncError::RootMismatch {
289 expected: attempt.state_root(),
290 got: range.state_root(),
291 })
292 }
293 let applied =
294 self.catch_up_progress(write)?.ok_or(SnapSyncError::NoCatchUpProgress)?.applied();
295 if applied != attempt.pivot() {
296 return Err(SnapSyncError::CatchUpBehindPivot {
297 applied: applied.number,
298 pivot: attempt.pivot().number,
299 })
300 }
301 let hashed_address = range.origin();
302 let account = range
304 .accounts()
305 .first()
306 .filter(|(hash, _)| *hash == hashed_address)
307 .map(|(_, account)| account);
308 if let Some(hash) = account.map(|account| account.code_hash) &&
309 hash != KECCAK256_EMPTY &&
310 self.tx_ref().get::<RawTable<tables::Bytecodes>>(RawKey::new(hash))?.is_none()
311 {
312 return Err(SnapSyncError::MissingCode { hash })
313 }
314
315 let has_storage = account.is_some_and(|account| account.storage_root != EMPTY_ROOT_HASH);
316 let mut state = HashedPostState::default().with_accounts([(
317 hashed_address,
318 account.map(|account| Account::from(account.clone())),
319 )]);
320 if has_storage {
321 state = state
323 .with_storages([(hashed_address, HashedStorage::from_iter(slots.iter().copied()))]);
324 } else {
325 self.remove::<tables::HashedStorages>(hashed_address..=hashed_address)?;
326 }
327 self.write_hashed_state(&state.into_sorted())?;
328
329 let mut remaining = self.snap_repairs(write)?;
330 remaining.resolve(hashed_address, has_storage.then_some(slots.as_slice()));
331 let accounts = remaining.len();
332 StoredRepairs::store(self, write.attempt(), remaining)?;
333 Ok(accounts)
334 }
335}
336
337struct RangeDependencies {
339 state: HashedPostStateSorted,
341 bytecodes: Vec<(B256, Bytecode)>,
343}
344
345impl RangeDependencies {
346 #[allow(clippy::clone_on_copy)]
347 fn new(
348 accounts: &[(B256, TrieAccount)],
349 storages: B256Map<HashedStorage>,
350 bytecodes: Vec<(B256, Bytecode)>,
351 ) -> Self {
352 let state = HashedPostState::default()
353 .with_accounts(
354 accounts
355 .iter()
356 .map(|(hash, account)| (*hash, Some(Account::from(account.clone())))),
357 )
358 .with_storages(storages)
359 .into_sorted();
360 Self { state, bytecodes }
361 }
362
363 fn verify(
366 &self,
367 accounts: &[(B256, TrieAccount)],
368 progress: &StorageProgress,
369 tx: &impl DbTx,
370 ) -> Result<Vec<B256>, SnapSyncError> {
371 let mut available = self.supplied_code()?;
372 let mut contracts = B256Set::default();
373 let mut persisted = Vec::new();
374 for (hash, account) in accounts {
375 if account.storage_root != EMPTY_ROOT_HASH {
376 contracts.insert(*hash);
377 if self.verify_storage(*hash, account, progress, tx)? {
378 persisted.push(*hash);
379 }
380 }
381 if account.code_hash != KECCAK256_EMPTY &&
383 available.insert(account.code_hash) &&
384 tx.get::<RawTable<tables::Bytecodes>>(RawKey::new(account.code_hash))?.is_none()
385 {
386 return Err(SnapSyncError::MissingCode { hash: account.code_hash })
387 }
388 }
389 if let Some(account) =
390 self.state.account_storages().keys().find(|hash| !contracts.contains(*hash))
391 {
392 return Err(SnapSyncError::UnexpectedStorage { account: *account })
393 }
394 Ok(persisted)
395 }
396
397 fn supplied_code(&self) -> Result<B256Set, SnapSyncError> {
399 let mut hashes = B256Set::default();
400 for (hash, code) in &self.bytecodes {
401 let got = code.hash_slow();
402 if got != *hash {
403 return Err(SnapSyncError::CodeMismatch { expected: *hash, got })
404 }
405 hashes.insert(*hash);
406 }
407 Ok(hashes)
408 }
409
410 fn verify_storage(
413 &self,
414 hash: B256,
415 account: &TrieAccount,
416 progress: &StorageProgress,
417 tx: &impl DbTx,
418 ) -> Result<bool, SnapSyncError> {
419 let (got, persisted) = match self.state.account_storages().get(&hash) {
420 Some(storage) => (
422 storage_root(
423 storage
424 .storage_slots_ref()
425 .iter()
426 .filter(|(_, value)| !value.is_zero())
427 .copied(),
428 ),
429 false,
430 ),
431 None if progress.is_complete(hash) => (persisted_storage_root(tx, hash)?, true),
432 None => return Err(SnapSyncError::MissingStorage { account: hash }),
433 };
434 if got != account.storage_root {
435 return Err(SnapSyncError::StorageRootMismatch {
436 account: hash,
437 expected: account.storage_root,
438 got,
439 })
440 }
441 Ok(persisted)
442 }
443
444 fn write(self, writer: &impl StateWriter) -> Result<(), SnapSyncError> {
446 writer.write_hashed_state(&self.state)?;
447 writer.write_state_changes(StateChangeset {
448 contracts: self.bytecodes,
449 ..Default::default()
450 })?;
451 Ok(())
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use crate::{
459 test_utils::{
460 account, generation, hashed_factory_with_roots, key, state_root, stored_slots,
461 verified_range, verified_repair,
462 },
463 SnapGeneration, StorageChunk,
464 };
465 use alloy_primitives::{Bytes, U256};
466 use reth_db_api::cursor::DbCursorRO;
467 use reth_provider::{
468 test_utils::MockNodeTypesWithDB, DatabaseProviderFactory, ProviderFactory,
469 };
470 use reth_trie_common::root::storage_root_unsorted;
471
472 const FAR: B256 = B256::repeat_byte(0xaa);
473 const SLOT: B256 = B256::repeat_byte(0x55);
474 const OTHER: B256 = B256::repeat_byte(0x66);
476 const ABSENT: B256 = B256::repeat_byte(0x33);
478
479 fn code() -> Bytecode {
480 Bytecode::new_raw(Bytes::from_static(&[0x60, 0x00]))
481 }
482
483 fn storage() -> (B256, HashedStorage) {
485 let storage = HashedStorage::from_iter([(SLOT, U256::from(7))]);
486 (
487 storage_root_unsorted(storage.storage.iter().map(|(slot, value)| (*slot, *value))),
488 storage,
489 )
490 }
491
492 fn accounts() -> Vec<(B256, TrieAccount)> {
494 let mut contract = account(2);
495 contract.storage_root = storage().0;
496 contract.code_hash = code().hash_slow();
497 vec![(key(1), account(1)), (key(2), contract), (FAR, account(3))]
498 }
499
500 fn dependencies() -> (B256Map<HashedStorage>, Vec<(B256, Bytecode)>) {
502 (B256Map::from_iter([(key(2), storage().1)]), vec![(code().hash_slow(), code())])
503 }
504
505 fn started(
507 accounts: &[(B256, TrieAccount)],
508 ) -> (ProviderFactory<MockNodeTypesWithDB>, SnapWrite, SnapGeneration) {
509 started_moving_to(accounts, accounts)
510 }
511
512 fn started_moving_to(
514 accounts: &[(B256, TrieAccount)],
515 moved: &[(B256, TrieAccount)],
516 ) -> (ProviderFactory<MockNodeTypesWithDB>, SnapWrite, SnapGeneration) {
517 let factory = hashed_factory_with_roots(&[
518 state_root(accounts),
519 state_root(moved),
520 state_root(moved),
521 ]);
522 let generation = generation(1, state_root(accounts));
523 let provider = factory.database_provider_rw().unwrap();
524 let write = provider.start_snap_attempt(generation).unwrap();
525 provider.start_account_coverage(write).unwrap();
526 provider.commit().unwrap();
527 (factory, write, generation)
528 }
529
530 fn stored_accounts(provider: &impl DBProvider) -> Vec<B256> {
531 let mut cursor = provider.tx_ref().cursor_read::<tables::HashedAccounts>().unwrap();
532 cursor.walk(None).unwrap().map(|entry| entry.unwrap().0).collect()
533 }
534
535 fn stored(provider: &impl DBProvider) -> (Vec<B256>, bool, bool) {
536 let tx = provider.tx_ref();
537 (
538 stored_accounts(provider),
539 tx.get::<tables::HashedStorages>(key(2)).unwrap().is_some(),
540 tx.get::<tables::Bytecodes>(code().hash_slow()).unwrap().is_some(),
541 )
542 }
543
544 #[test]
545 fn accounts_dependencies_and_coverage_commit_together() {
546 let accounts = accounts();
547 let (factory, write, _) = started(&accounts);
548 let range = verified_range(&accounts, 0..3, B256::ZERO, &[]);
549 let (storages, bytecodes) = dependencies();
550
551 let provider = factory.database_provider_rw().unwrap();
552 let coverage = provider.commit_account_range(write, &range, storages, bytecodes).unwrap();
553 provider.commit().unwrap();
554
555 assert!(coverage.is_complete());
556 let provider = factory.database_provider_rw().unwrap();
557 assert_eq!(stored(&provider), (vec![key(1), key(2), FAR], true, true));
558 assert_eq!(provider.account_coverage(write).unwrap(), Some(coverage));
559 }
560
561 #[test]
562 fn a_partial_range_moves_the_coverage_to_its_next_key() {
563 let accounts = accounts();
564 let (factory, write, _) = started(&accounts);
565 let range = verified_range(&accounts, 0..1, B256::ZERO, &[key(1)]);
567
568 let provider = factory.database_provider_rw().unwrap();
569 let coverage =
570 provider.commit_account_range(write, &range, Default::default(), Vec::new()).unwrap();
571
572 assert_eq!(coverage.next(), Some(key(2)));
573 assert_eq!(stored_accounts(&provider), [key(1)]);
574 }
575
576 #[test]
577 fn a_proven_empty_tail_completes_the_coverage() {
578 let accounts = vec![(key(1), account(1)), (key(2), account(2))];
579 let (factory, write, _) = started(&accounts);
580 let tail = verified_range(&accounts, 0..0, key(3), &[key(3)]);
581 assert!(tail.accounts().is_empty());
582 let provider = factory.database_provider_rw().unwrap();
583 let coverage = AccountCoverage { next: Some(key(3)) };
584 StoredCoverage::new(write.attempt(), coverage).write(&provider).unwrap();
585
586 let coverage =
587 provider.commit_account_range(write, &tail, Default::default(), Vec::new()).unwrap();
588
589 assert!(coverage.is_complete());
590 assert!(stored_accounts(&provider).is_empty());
591 }
592
593 #[test]
594 fn a_range_from_another_origin_changes_nothing() {
595 let accounts = accounts();
596 let (factory, write, _) = started(&accounts);
597 let provider = factory.database_provider_rw().unwrap();
598 let first = verified_range(&accounts, 0..1, B256::ZERO, &[key(1)]);
599 provider.commit_account_range(write, &first, Default::default(), Vec::new()).unwrap();
600
601 let duplicate =
603 provider.commit_account_range(write, &first, Default::default(), Vec::new());
604 let skipped = verified_range(&accounts, 2..3, FAR, &[FAR]);
605 let ahead = provider.commit_account_range(write, &skipped, Default::default(), Vec::new());
606
607 assert!(matches!(duplicate, Err(SnapSyncError::OutOfOrderRange { .. })));
608 assert!(matches!(ahead, Err(SnapSyncError::OutOfOrderRange { .. })));
609 assert_eq!(provider.account_coverage(write).unwrap().unwrap().next(), Some(key(2)));
610 assert_eq!(stored_accounts(&provider), [key(1)]);
611 }
612
613 #[test]
614 fn a_contract_without_its_storage_or_code_is_refused() {
615 let accounts = accounts();
616 let (factory, write, _) = started(&accounts);
617 let range = verified_range(&accounts, 0..3, B256::ZERO, &[]);
618 let (storages, bytecodes) = dependencies();
619 let provider = factory.database_provider_rw().unwrap();
620
621 let no_storage =
622 provider.commit_account_range(write, &range, Default::default(), bytecodes.clone());
623 assert!(
624 matches!(no_storage, Err(SnapSyncError::MissingStorage { account }) if account == key(2))
625 );
626
627 let no_code = provider.commit_account_range(write, &range, storages, Vec::new());
628 assert!(matches!(no_code, Err(SnapSyncError::MissingCode { .. })));
629
630 let stray = B256Map::from_iter([(key(1), storage().1), (key(2), storage().1)]);
631 let unexpected = provider.commit_account_range(write, &range, stray, bytecodes);
632 assert!(
633 matches!(unexpected, Err(SnapSyncError::UnexpectedStorage { account }) if account == key(1))
634 );
635
636 assert_eq!(stored(&provider), (Vec::new(), false, false));
637 assert_eq!(provider.account_coverage(write).unwrap(), Some(AccountCoverage::START));
638 }
639
640 #[test]
641 fn missing_code_is_named_after_contracts_whose_code_is_stored() {
642 let stored_code = code();
643 let missing_code = Bytecode::new_raw(Bytes::from_static(&[0x60, 0x01]));
644 let mut first = account(1);
645 first.code_hash = stored_code.hash_slow();
646 let mut second = account(2);
647 second.code_hash = missing_code.hash_slow();
648 let accounts = vec![(key(1), first), (key(2), second), (key(3), account(3))];
649 let (factory, write, _) = started(&accounts);
650 let range = verified_range(&accounts, 0..3, B256::ZERO, &[]);
651 let provider = factory.database_provider_rw().unwrap();
652 provider
653 .write_state_changes(StateChangeset {
654 contracts: vec![(stored_code.hash_slow(), stored_code)],
655 ..Default::default()
656 })
657 .unwrap();
658
659 let refused = provider.commit_account_range(write, &range, Default::default(), Vec::new());
660
661 assert!(matches!(
662 refused,
663 Err(SnapSyncError::MissingCode { hash }) if hash == missing_code.hash_slow()
664 ));
665 assert_eq!(stored_accounts(&provider), Vec::<B256>::new());
666 assert_eq!(provider.account_coverage(write).unwrap(), Some(AccountCoverage::START));
667 }
668
669 #[test]
670 fn storage_and_code_are_checked_against_what_the_account_commits_to() {
671 let accounts = accounts();
672 let (factory, write, _) = started(&accounts);
673 let range = verified_range(&accounts, 0..3, B256::ZERO, &[]);
674 let (storages, bytecodes) = dependencies();
675 let provider = factory.database_provider_rw().unwrap();
676
677 let partial = B256Map::from_iter([(key(2), HashedStorage::default())]);
679 let short = provider.commit_account_range(write, &range, partial, bytecodes);
680 assert!(matches!(
681 short,
682 Err(SnapSyncError::StorageRootMismatch { account, expected, .. })
683 if account == key(2) && expected == storage().0
684 ));
685
686 let other = Bytecode::new_raw(Bytes::from_static(&[0x60, 0x01]));
688 let relabelled = vec![(code().hash_slow(), other)];
689 let wrong = provider.commit_account_range(write, &range, storages, relabelled);
690 assert!(matches!(
691 wrong,
692 Err(SnapSyncError::CodeMismatch { expected, .. }) if expected == code().hash_slow()
693 ));
694
695 assert_eq!(stored(&provider), (Vec::new(), false, false));
696 assert_eq!(provider.account_coverage(write).unwrap(), Some(AccountCoverage::START));
697 }
698
699 #[test]
700 fn code_already_stored_need_not_be_supplied() {
701 let accounts = accounts();
702 let (factory, write, _) = started(&accounts);
703 let range = verified_range(&accounts, 0..3, B256::ZERO, &[]);
704 let (storages, _) = dependencies();
705 let provider = factory.database_provider_rw().unwrap();
706 provider
707 .write_state_changes(StateChangeset {
708 contracts: vec![(code().hash_slow(), code())],
709 ..Default::default()
710 })
711 .unwrap();
712
713 let coverage = provider.commit_account_range(write, &range, storages, Vec::new()).unwrap();
714
715 assert!(coverage.is_complete());
716 }
717
718 #[test]
719 fn starting_coverage_again_resumes_where_the_attempt_left_off() {
720 let accounts = accounts();
721 let (factory, write, _) = started(&accounts);
722 let range = verified_range(&accounts, 0..1, B256::ZERO, &[key(1)]);
723 let provider = factory.database_provider_rw().unwrap();
724 let committed =
725 provider.commit_account_range(write, &range, Default::default(), Vec::new()).unwrap();
726
727 assert_eq!(provider.start_account_coverage(write).unwrap(), committed);
728 assert_eq!(provider.account_coverage(write).unwrap(), Some(committed));
729 }
730
731 #[test]
732 fn a_write_from_a_replaced_attempt_changes_nothing() {
733 let accounts = accounts();
734 let (factory, replaced, generation) = started(&accounts);
735 let range = verified_range(&accounts, 0..3, B256::ZERO, &[]);
736 let (storages, bytecodes) = dependencies();
737 let provider = factory.database_provider_rw().unwrap();
738 let current = provider.start_snap_attempt(generation).unwrap();
739
740 let refused = provider.commit_account_range(replaced, &range, storages, bytecodes);
741
742 assert!(matches!(refused, Err(SnapSyncError::StaleWrite { .. })));
743 assert_eq!(stored(&provider), (Vec::new(), false, false));
744 assert_eq!(provider.account_coverage(current).unwrap(), None);
746 assert!(matches!(
747 provider.account_coverage(replaced),
748 Err(SnapSyncError::StaleWrite { .. })
749 ));
750 }
751
752 #[test]
753 fn a_new_attempt_replaces_what_an_earlier_one_left_in_the_interval() {
754 let earlier = accounts();
755 let current = vec![(key(1), account(9)), (FAR, account(3))];
756 let (factory, write, _) = started_moving_to(&earlier, ¤t);
757 let (storages, bytecodes) = dependencies();
758 let provider = factory.database_provider_rw().unwrap();
759 let range = verified_range(&earlier, 0..3, B256::ZERO, &[]);
760 provider.commit_account_range(write, &range, storages, bytecodes).unwrap();
761 provider.commit().unwrap();
762
763 let provider = factory.database_provider_rw().unwrap();
765 let write = provider.start_snap_attempt(generation(2, state_root(¤t))).unwrap();
766 provider.start_account_coverage(write).unwrap();
767 let range = verified_range(¤t, 0..2, B256::ZERO, &[]);
768 let coverage =
769 provider.commit_account_range(write, &range, Default::default(), Vec::new()).unwrap();
770
771 assert!(coverage.is_complete());
772 assert_eq!(stored(&provider), (vec![key(1), FAR], false, true));
773 let first = provider.tx_ref().get::<tables::HashedAccounts>(key(1)).unwrap().unwrap();
774 assert_eq!(first.nonce, 9);
775 }
776
777 #[test]
778 fn a_range_proved_against_another_root_is_refused() {
779 let accounts = accounts();
780 let (factory, write, _) = started(&accounts);
781 let provider = factory.database_provider_rw().unwrap();
782 let other = vec![(key(7), account(7))];
783 let range = verified_range(&other, 0..1, B256::ZERO, &[]);
784
785 let refused = provider.commit_account_range(write, &range, Default::default(), Vec::new());
786
787 assert!(matches!(refused, Err(SnapSyncError::RootMismatch { .. })));
788 assert!(stored_accounts(&provider).is_empty());
789 }
790
791 #[test]
792 fn a_range_without_recorded_coverage_is_refused() {
793 let accounts = accounts();
794 let factory = hashed_factory_with_roots(&[state_root(&accounts)]);
795 let provider = factory.database_provider_rw().unwrap();
796 let write = provider.start_snap_attempt(generation(1, state_root(&accounts))).unwrap();
797 let range = verified_range(&accounts, 0..3, B256::ZERO, &[]);
798
799 assert!(matches!(
800 provider.commit_account_range(write, &range, Default::default(), Vec::new()),
801 Err(SnapSyncError::NoCoverage)
802 ));
803 }
804
805 #[test]
806 fn storage_persisted_ahead_of_the_range_commits_with_it() {
807 let accounts = accounts();
808 let (factory, write, _) = started(&accounts);
809 let range = verified_range(&accounts, 0..3, B256::ZERO, &[]);
810 let (_, bytecodes) = dependencies();
811 let provider = factory.database_provider_rw().unwrap();
812 let leftover = HashedPostState::default().with_storages([(key(1), storage().1)]);
814 provider.write_hashed_state(&leftover.into_sorted()).unwrap();
815 let slots = vec![(SLOT, U256::from(7))];
816 let chunk = StorageChunk::new(key(2), storage().0, B256::ZERO, slots, None);
817 provider.commit_storage_chunk(write, B256::ZERO, chunk).unwrap();
818
819 let coverage =
820 provider.commit_account_range(write, &range, Default::default(), bytecodes).unwrap();
821
822 assert!(coverage.is_complete());
823 assert_eq!(stored(&provider), (vec![key(1), key(2), FAR], true, true));
824 assert!(provider.tx_ref().get::<tables::HashedStorages>(key(1)).unwrap().is_none());
825 }
826
827 #[test]
828 fn persisted_storage_must_be_complete_and_match_the_account_root() {
829 let accounts = accounts();
830 let (factory, write, _) = started(&accounts);
831 let range = verified_range(&accounts, 0..3, B256::ZERO, &[]);
832 let (_, bytecodes) = dependencies();
833
834 let provider = factory.database_provider_rw().unwrap();
836 let slots = vec![(SLOT, U256::from(7))];
837 let next = Some(B256::repeat_byte(0x66));
838 let partial = StorageChunk::new(key(2), storage().0, B256::ZERO, slots, next);
839 provider.commit_storage_chunk(write, B256::ZERO, partial).unwrap();
840 let incomplete =
841 provider.commit_account_range(write, &range, Default::default(), bytecodes.clone());
842 assert!(
843 matches!(incomplete, Err(SnapSyncError::MissingStorage { account }) if account == key(2))
844 );
845 drop(provider);
846
847 let provider = factory.database_provider_rw().unwrap();
849 let slots = vec![(SLOT, U256::from(8))];
850 let wrong = StorageChunk::new(key(2), storage().0, B256::ZERO, slots, None);
851 provider.commit_storage_chunk(write, B256::ZERO, wrong).unwrap();
852 let mismatched =
853 provider.commit_account_range(write, &range, Default::default(), bytecodes);
854 assert!(matches!(
855 mismatched,
856 Err(SnapSyncError::StorageRootMismatch { account, .. }) if account == key(2)
857 ));
858 assert_eq!(provider.account_coverage(write).unwrap(), Some(AccountCoverage::START));
859 }
860
861 #[test]
862 fn a_record_this_build_cannot_read_is_reported() {
863 let accounts = accounts();
864 let (factory, write, _) = started(&accounts);
865
866 for record in [br#"{"version":999}"#.to_vec(), b"{}".to_vec()] {
867 let provider = factory.database_provider_rw().unwrap();
868 provider.write_metadata(StoredCoverage::KEY, record).unwrap();
869
870 assert!(matches!(
871 provider.account_coverage(write),
872 Err(SnapSyncError::UnsupportedRecord { .. })
873 ));
874 }
875 }
876
877 fn repairing(
880 accounts: &[(B256, TrieAccount)],
881 ) -> (ProviderFactory<MockNodeTypesWithDB>, SnapWrite) {
882 let (factory, write, _) = started(accounts);
883 let provider = factory.database_provider_rw().unwrap();
884 let stale = HashedPostState::default()
885 .with_accounts([
886 (key(2), Some(Account::from(account(9)))),
887 (ABSENT, Some(Account::from(account(9)))),
888 ])
889 .with_storages([
890 (key(2), HashedStorage::from_iter([(SLOT, U256::ONE), (OTHER, U256::from(2))])),
891 (ABSENT, HashedStorage::from_iter([(SLOT, U256::ONE)])),
892 ]);
893 provider.write_hashed_state(&stale.into_sorted()).unwrap();
894 let mut repairs = StateRepairs::default();
895 repairs.insert_slot(key(2), SLOT);
896 repairs.insert_slot(key(2), OTHER);
897 repairs.insert_account(ABSENT);
898 provider.schedule_snap_repairs(write, repairs).unwrap();
899 provider.commit().unwrap();
900 (factory, write)
901 }
902
903 fn repair(
904 factory: &ProviderFactory<MockNodeTypesWithDB>,
905 write: SnapWrite,
906 range: &VerifiedAccountRange,
907 slots: Vec<(B256, U256)>,
908 ) -> Result<usize, SnapSyncError> {
909 let provider = factory.database_provider_rw().unwrap();
910 let remaining = provider.commit_account_repair(write, range, slots)?;
911 provider.commit().unwrap();
912 Ok(remaining)
913 }
914
915 #[test]
916 #[allow(clippy::clone_on_copy)]
917 fn a_repair_takes_the_pivot_account_and_scheduled_slots() {
918 let accounts = accounts();
919 let (factory, write) = repairing(&accounts);
920 let range = verified_repair(&accounts, 1..2, key(2), &[key(2)]);
921 let slots = vec![(SLOT, U256::from(7)), (OTHER, U256::ZERO)];
923
924 let refused = repair(&factory, write, &range, slots.clone());
925 assert!(matches!(refused, Err(SnapSyncError::MissingCode { .. })));
926 let provider = factory.database_provider_rw().unwrap();
927 provider
928 .write_state_changes(StateChangeset {
929 contracts: vec![(code().hash_slow(), code())],
930 ..Default::default()
931 })
932 .unwrap();
933 provider.commit().unwrap();
934 let remaining = repair(&factory, write, &range, slots).unwrap();
935
936 assert_eq!(remaining, 1);
937 let provider = factory.database_provider_ro().unwrap();
938 let mut expected = StateRepairs::default();
939 expected.insert_account(ABSENT);
940 assert_eq!(provider.snap_repairs(write).unwrap(), expected);
941 let stored = provider.tx_ref().get::<tables::HashedAccounts>(key(2)).unwrap();
942 assert_eq!(stored, Some(Account::from(accounts[1].1.clone())));
943 assert_eq!(stored_slots(&provider, key(2)), [(SLOT, U256::from(7))]);
944 }
945
946 #[test]
947 fn an_account_the_pivot_lacks_is_removed_with_its_storage() {
948 let accounts = accounts();
949 let (factory, write) = repairing(&accounts);
950 let range = verified_repair(&accounts, 2..3, ABSENT, &[ABSENT, FAR]);
952
953 repair(&factory, write, &range, Vec::new()).unwrap();
954
955 let provider = factory.database_provider_ro().unwrap();
956 assert_eq!(provider.tx_ref().get::<tables::HashedAccounts>(ABSENT).unwrap(), None);
957 assert!(stored_slots(&provider, ABSENT).is_empty());
958 assert_eq!(provider.snap_repairs(write).unwrap().len(), 1);
959 }
960
961 #[test]
962 fn a_repair_waits_for_catch_up_to_reach_the_pivot() {
963 let accounts = accounts();
964 let (factory, write) = repairing(&accounts);
965 let provider = factory.database_provider_rw().unwrap();
966 let write =
967 provider.advance_snap_pivot(write, generation(2, state_root(&accounts))).unwrap();
968 provider.commit().unwrap();
969 let range = verified_repair(&accounts, 2..3, ABSENT, &[ABSENT, FAR]);
970
971 let refused = repair(&factory, write, &range, Vec::new());
972
973 assert!(matches!(refused, Err(SnapSyncError::CatchUpBehindPivot { applied: 1, pivot: 2 })));
974 }
975
976 #[test]
977 fn a_new_attempt_starts_without_repairs() {
978 let accounts = accounts();
979 let (factory, _) = repairing(&accounts);
980 let provider = factory.database_provider_rw().unwrap();
981
982 let restarted = provider.start_snap_attempt(generation(1, state_root(&accounts))).unwrap();
983
984 assert!(provider.snap_repairs(restarted).unwrap().is_empty());
985 assert!(StoredRepairs::read(&provider).unwrap().is_none());
986 }
987}