1use crate::{
8 account::StoredCoverage, common::SnapRecord, reorg::StoredAncestry, repair::StoredRepairs,
9 storage::StoredProgress, verify::StoredRebuild, CatchUpProgress, DownloadedAccount,
10 SnapAccountStore, SnapCatchUpStore, SnapGeneration, SnapReorg, SnapSyncError, StateRepairs,
11};
12use alloy_eips::{eip7928::bal::DecodedBal, BlockNumHash};
13use alloy_primitives::keccak256;
14use reth_storage_api::{
15 BlockHashReader, DBProvider, HeaderProvider, MetadataProvider, MetadataWriter, SnapAttempt,
16 SnapAttemptId, StorageSettings,
17};
18use serde::{Deserialize, Serialize};
19
20pub trait SnapAttemptStore {
25 fn start_snap_attempt(&self, generation: SnapGeneration) -> Result<SnapWrite, SnapSyncError>
28 where
29 Self: MetadataWriter + HeaderProvider;
30
31 fn active_snap_write(&self) -> Result<Option<SnapWrite>, SnapSyncError>;
33
34 fn authorize_snap_write(&self, write: SnapWrite) -> Result<SnapAttempt, SnapSyncError>;
36
37 fn authorize_canonical_snap_write(
42 &self,
43 write: SnapWrite,
44 ) -> Result<SnapAttempt, SnapSyncError>
45 where
46 Self: BlockHashReader;
47
48 fn advance_snap_pivot(
53 &self,
54 write: SnapWrite,
55 generation: SnapGeneration,
56 ) -> Result<SnapWrite, SnapSyncError>
57 where
58 Self: MetadataWriter + HeaderProvider + BlockHashReader;
59
60 fn verify_snap_attempt(&self, write: SnapWrite) -> Result<(), SnapSyncError>
62 where
63 Self: MetadataWriter;
64
65 fn abandon_snap_attempt(&self) -> Result<(), SnapSyncError>
67 where
68 Self: MetadataWriter;
69
70 fn snap_reorg(
75 &self,
76 write: SnapWrite,
77 ) -> Result<Option<SnapReorg<<Self as HeaderProvider>::Header>>, SnapSyncError>
78 where
79 Self: HeaderProvider + BlockHashReader;
80
81 fn commit_reorg_recovery(
86 &self,
87 write: SnapWrite,
88 ancestor: BlockNumHash,
89 lists: &[DecodedBal],
90 generation: SnapGeneration,
91 ) -> Result<SnapWrite, SnapSyncError>
92 where
93 Self: MetadataWriter + HeaderProvider + BlockHashReader + DBProvider;
94}
95
96#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
98pub struct SnapWrite {
99 attempt: SnapAttemptId,
101 state_version: u64,
103}
104
105impl SnapWrite {
106 const fn of(attempt: &SnapAttempt) -> Self {
108 Self { attempt: attempt.id(), state_version: attempt.state_version() }
109 }
110
111 pub const fn attempt(&self) -> SnapAttemptId {
113 self.attempt
114 }
115
116 pub const fn state_version(&self) -> u64 {
118 self.state_version
119 }
120}
121
122impl<T: MetadataProvider> SnapAttemptStore for T {
123 fn start_snap_attempt(&self, generation: SnapGeneration) -> Result<SnapWrite, SnapSyncError>
124 where
125 Self: MetadataWriter + HeaderProvider,
126 {
127 if !self.storage_settings()?.unwrap_or_else(StorageSettings::v1).use_hashed_state() {
129 return Err(SnapSyncError::UnsupportedStorage)
130 }
131
132 let attempt =
133 SnapAttempt::start(self.snap_attempt()?, generation.target(), generation.state_root());
134 self.write_snap_attempt(&attempt)?;
135 CatchUpProgress::at_pivot(attempt.pivot()).write(self, attempt.id())?;
137 StoredCoverage::clear(self)?;
140 StoredProgress::clear(self)?;
141 StoredRepairs::clear(self)?;
142 StoredRebuild::clear(self)?;
143 StoredAncestry::record(self, attempt.id(), attempt.pivot())?;
144 Ok(SnapWrite::of(&attempt))
145 }
146
147 fn active_snap_write(&self) -> Result<Option<SnapWrite>, SnapSyncError> {
148 Ok(self.snap_attempt()?.filter(SnapAttempt::is_unfinished).as_ref().map(SnapWrite::of))
149 }
150
151 fn authorize_snap_write(&self, write: SnapWrite) -> Result<SnapAttempt, SnapSyncError> {
152 let attempt = self.snap_attempt()?.ok_or(SnapSyncError::NoAttempt)?;
153 if !attempt.is_unfinished() || SnapWrite::of(&attempt) != write {
154 return Err(SnapSyncError::StaleWrite {
155 attempt: write.attempt,
156 state_version: write.state_version,
157 })
158 }
159 Ok(attempt)
160 }
161
162 fn authorize_canonical_snap_write(&self, write: SnapWrite) -> Result<SnapAttempt, SnapSyncError>
163 where
164 Self: BlockHashReader,
165 {
166 let attempt = self.authorize_snap_write(write)?;
167 let pivot = attempt.pivot();
168 if self.block_hash(pivot.number)? != Some(pivot.hash) {
169 return Err(SnapSyncError::NonCanonicalBlock { block: pivot.number, hash: pivot.hash })
170 }
171 Ok(attempt)
172 }
173
174 fn advance_snap_pivot(
175 &self,
176 write: SnapWrite,
177 generation: SnapGeneration,
178 ) -> Result<SnapWrite, SnapSyncError>
179 where
180 Self: MetadataWriter + HeaderProvider + BlockHashReader,
181 {
182 let mut attempt = self.authorize_canonical_snap_write(write)?;
184 let (pivot, target) = (attempt.pivot(), generation.target());
185 if target.number <= pivot.number {
187 return Err(SnapSyncError::PivotNotAdvanced {
188 pivot: pivot.number,
189 target: target.number,
190 })
191 }
192 if self.block_hash(target.number)? != Some(target.hash) {
193 return Err(SnapSyncError::NonCanonicalBlock { block: target.number, hash: target.hash })
194 }
195 attempt.re_anchor(target, generation.state_root());
196 self.write_snap_attempt(&attempt)?;
197 StoredAncestry::record(self, attempt.id(), target)?;
198 Ok(SnapWrite::of(&attempt))
199 }
200
201 fn verify_snap_attempt(&self, write: SnapWrite) -> Result<(), SnapSyncError>
202 where
203 Self: MetadataWriter,
204 {
205 let mut attempt = self.authorize_snap_write(write)?;
206 attempt.verify();
207 self.write_snap_attempt(&attempt)?;
208 Ok(())
209 }
210
211 fn abandon_snap_attempt(&self) -> Result<(), SnapSyncError>
212 where
213 Self: MetadataWriter,
214 {
215 if let Some(mut attempt) = self.snap_attempt()? &&
216 attempt.is_unfinished()
217 {
218 attempt.abandon();
219 self.write_snap_attempt(&attempt)?;
220 }
221 Ok(())
222 }
223
224 fn snap_reorg(
225 &self,
226 write: SnapWrite,
227 ) -> Result<Option<SnapReorg<<Self as HeaderProvider>::Header>>, SnapSyncError>
228 where
229 Self: HeaderProvider + BlockHashReader,
230 {
231 StoredAncestry::reorg(self, &self.authorize_snap_write(write)?)
232 }
233
234 fn commit_reorg_recovery(
235 &self,
236 write: SnapWrite,
237 ancestor: BlockNumHash,
238 lists: &[DecodedBal],
239 generation: SnapGeneration,
240 ) -> Result<SnapWrite, SnapSyncError>
241 where
242 Self: MetadataWriter + HeaderProvider + BlockHashReader + DBProvider,
243 {
244 let mut attempt = self.authorize_snap_write(write)?;
245 let target = generation.target();
246 for block in [ancestor, target] {
247 if self.block_hash(block.number)? != Some(block.hash) {
248 return Err(SnapSyncError::NonCanonicalBlock {
249 block: block.number,
250 hash: block.hash,
251 })
252 }
253 }
254 if target.number < ancestor.number {
255 return Err(SnapSyncError::PivotBelowAncestor {
256 ancestor: ancestor.number,
257 target: target.number,
258 })
259 }
260
261 let coverage = self.account_coverage(write)?.ok_or(SnapSyncError::NoCoverage)?;
262 let mut repairs = StateRepairs::default();
263 for changes in lists.iter().flat_map(|list| list.as_bal().iter()) {
264 let hashed_address = keccak256(changes.address());
265 if !changes.has_changes() ||
267 self.downloaded_account(coverage, hashed_address)? == DownloadedAccount::Unknown
268 {
269 continue
270 }
271 repairs.insert_changes(hashed_address, changes);
272 }
273 self.schedule_snap_repairs(write, repairs)?;
274
275 let applied = self
276 .catch_up_progress(write)?
277 .ok_or(SnapSyncError::NoCatchUpProgress)?
278 .resume_after(ancestor);
279 attempt.re_anchor(target, generation.state_root());
280 self.write_snap_attempt(&attempt)?;
281 CatchUpProgress::at_pivot(applied).write(self, attempt.id())?;
282 StoredProgress::clear(self)?;
283 StoredAncestry::record(self, attempt.id(), target)?;
284 Ok(SnapWrite::of(&attempt))
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use crate::test_utils::insert_generation_headers;
292 use alloy_eips::BlockNumHash;
293 use alloy_primitives::{Bytes, B256};
294 use reth_db_api::{tables, transaction::DbTx};
295 use reth_primitives_traits::Account;
296 use reth_provider::{
297 test_utils::{create_test_provider_factory, MockNodeTypesWithDB},
298 DBProvider, DatabaseProviderFactory, ProviderFactory,
299 };
300 use reth_storage_api::{metadata::keys, StateWriter};
301 use reth_trie_common::HashedPostState;
302 use revm::{bytecode::Bytecode, database::states::StateChangeset};
303
304 const HASHED_ADDRESS: B256 = B256::repeat_byte(0xbb);
305
306 fn code() -> Bytecode {
307 Bytecode::new_raw(Bytes::from_static(&[0x60, 0x00]))
308 }
309
310 fn generation(block: u64) -> SnapGeneration {
311 SnapGeneration::new(
312 BlockNumHash::new(block, B256::repeat_byte(block as u8)),
313 B256::repeat_byte(0xaa),
314 )
315 }
316
317 fn factory() -> ProviderFactory<MockNodeTypesWithDB> {
320 let factory = create_test_provider_factory();
321 let provider = factory.database_provider_rw().unwrap();
322 provider.write_storage_settings(StorageSettings::v2()).unwrap();
323 provider.commit().unwrap();
324 insert_generation_headers(&factory);
325 factory
326 }
327
328 fn download(provider: &impl StateWriter) {
330 let mut state = HashedPostState::default();
331 state.accounts.insert(HASHED_ADDRESS, Some(Account::default()));
332 provider.write_hashed_state(&state.into_sorted()).unwrap();
333 provider
334 .write_state_changes(StateChangeset {
335 contracts: vec![(code().hash_slow(), code())],
336 ..Default::default()
337 })
338 .unwrap();
339 }
340
341 fn downloaded(provider: &impl DBProvider) -> (bool, bool) {
342 let tx = provider.tx_ref();
343 (
344 tx.get::<tables::HashedAccounts>(HASHED_ADDRESS).unwrap().is_some(),
345 tx.get::<tables::Bytecodes>(code().hash_slow()).unwrap().is_some(),
346 )
347 }
348
349 #[test]
350 fn nothing_owns_the_state_before_an_attempt_starts() {
351 let factory = factory();
352 let provider = factory.database_provider_rw().unwrap();
353
354 assert_eq!(provider.active_snap_write().unwrap(), None);
355 let write = SnapWrite { attempt: SnapAttemptId::FIRST, state_version: 0 };
356 assert!(matches!(provider.authorize_snap_write(write), Err(SnapSyncError::NoAttempt)));
357 }
358
359 #[test]
360 fn address_keyed_state_cannot_host_an_attempt() {
361 let factory = create_test_provider_factory();
362 let provider = factory.database_provider_rw().unwrap();
363
364 assert!(matches!(
366 provider.start_snap_attempt(generation(1)),
367 Err(SnapSyncError::UnsupportedStorage)
368 ));
369 provider.write_storage_settings(StorageSettings::v1()).unwrap();
370 assert!(matches!(
371 provider.start_snap_attempt(generation(1)),
372 Err(SnapSyncError::UnsupportedStorage)
373 ));
374 assert_eq!(provider.snap_attempt().unwrap(), None);
375 }
376
377 #[test]
378 fn a_new_attempt_drops_progress_another_build_wrote() {
379 let factory = factory();
380 let provider = factory.database_provider_rw().unwrap();
381 let unreadable = br#"{"version":999}"#.to_vec();
382 for key in [StoredCoverage::KEY, StoredProgress::KEY, StoredRebuild::KEY] {
383 provider.write_metadata(key, unreadable.clone()).unwrap();
384 }
385
386 let write = provider.start_snap_attempt(generation(1)).unwrap();
387
388 assert_eq!(
389 crate::SnapAccountStore::start_account_coverage(&provider, write).unwrap(),
390 crate::AccountCoverage::START
391 );
392 for key in [StoredProgress::KEY, StoredRebuild::KEY] {
393 assert_eq!(provider.get_metadata(key).unwrap(), None);
394 }
395 }
396
397 #[test]
398 fn restarting_at_the_same_pivot_takes_a_new_identity() {
399 let factory = factory();
400 let provider = factory.database_provider_rw().unwrap();
401
402 let first = provider.start_snap_attempt(generation(1)).unwrap();
403 let second = provider.start_snap_attempt(generation(1)).unwrap();
404
405 assert_ne!(first.attempt(), second.attempt());
406 assert!(matches!(
408 provider.authorize_snap_write(first),
409 Err(SnapSyncError::StaleWrite { .. })
410 ));
411 provider.authorize_snap_write(second).unwrap();
412 }
413
414 #[test]
415 fn abandoning_an_attempt_keeps_its_identity_taken() {
416 let factory = factory();
417 let provider = factory.database_provider_rw().unwrap();
418
419 let abandoned = provider.start_snap_attempt(generation(1)).unwrap();
420 provider.abandon_snap_attempt().unwrap();
421 assert_eq!(provider.active_snap_write().unwrap(), None);
422 let started = provider.start_snap_attempt(generation(2)).unwrap();
423
424 assert_ne!(abandoned.attempt(), started.attempt());
425 assert!(matches!(
427 provider.authorize_snap_write(abandoned),
428 Err(SnapSyncError::StaleWrite { .. })
429 ));
430 }
431
432 #[test]
433 fn the_attempt_survives_reopening_the_database() {
434 let factory = factory();
435 let provider = factory.database_provider_rw().unwrap();
436 let write = provider.start_snap_attempt(generation(7)).unwrap();
437 provider.commit().unwrap();
438
439 let reopened = factory.database_provider_rw().unwrap();
440
441 assert_eq!(reopened.active_snap_write().unwrap(), Some(write));
442 let attempt = reopened.snap_attempt().unwrap().unwrap();
443 assert_eq!(attempt.pivot(), BlockNumHash::new(7, B256::repeat_byte(7)));
444 assert_eq!(attempt.state_root(), B256::repeat_byte(0xaa));
445 assert!(attempt.is_unfinished());
446 }
447
448 #[test]
449 fn committing_keeps_downloaded_state_and_progress_together() {
450 let factory = factory();
451 let provider = factory.database_provider_rw().unwrap();
452 let write = provider.start_snap_attempt(generation(1)).unwrap();
453 download(&provider);
454 provider.commit().unwrap();
455
456 let provider = factory.database_provider_rw().unwrap();
457 assert_eq!(provider.active_snap_write().unwrap(), Some(write));
458 assert_eq!(downloaded(&provider), (true, true));
459 }
460
461 #[test]
462 fn rolling_back_drops_downloaded_state_and_progress_together() {
463 let factory = factory();
464 let provider = factory.database_provider_rw().unwrap();
465 provider.start_snap_attempt(generation(1)).unwrap();
466 download(&provider);
467 drop(provider);
469
470 let provider = factory.database_provider_rw().unwrap();
471 assert_eq!(provider.active_snap_write().unwrap(), None);
472 assert_eq!(downloaded(&provider), (false, false));
473 }
474
475 #[test]
476 fn advancing_the_pivot_rejects_writes_proved_against_the_old_root() {
477 let factory = factory();
478 let provider = factory.database_provider_rw().unwrap();
479
480 let before = provider.start_snap_attempt(generation(1)).unwrap();
481 let after = provider.advance_snap_pivot(before, generation(2)).unwrap();
482
483 assert_eq!(after.attempt(), before.attempt());
484 assert_eq!(after.state_version(), before.state_version() + 1);
485 assert!(matches!(
486 provider.authorize_snap_write(before),
487 Err(SnapSyncError::StaleWrite { .. })
488 ));
489 provider.authorize_snap_write(after).unwrap();
490 }
491
492 #[test]
493 fn the_pivot_advances_repeatedly_within_one_attempt() {
494 let factory = factory();
495 let provider = factory.database_provider_rw().unwrap();
496 let first = provider.start_snap_attempt(generation(1)).unwrap();
497
498 let second = provider.advance_snap_pivot(first, generation(2)).unwrap();
499 let third = provider.advance_snap_pivot(second, generation(3)).unwrap();
500
501 assert_eq!(third.attempt(), first.attempt());
502 assert_eq!(third.state_version(), first.state_version() + 2);
503 assert_eq!(provider.snap_attempt().unwrap().unwrap().pivot().number, 3);
504 for stale in [first, second] {
505 assert!(matches!(
506 provider.authorize_snap_write(stale),
507 Err(SnapSyncError::StaleWrite { .. })
508 ));
509 }
510 }
511
512 #[test]
513 fn the_pivot_only_moves_forward() {
514 let factory = factory();
515 let provider = factory.database_provider_rw().unwrap();
516 let write = provider.start_snap_attempt(generation(2)).unwrap();
517
518 for target in [1, 2] {
520 assert!(matches!(
521 provider.advance_snap_pivot(write, generation(target)),
522 Err(SnapSyncError::PivotNotAdvanced { pivot: 2, target: refused })
523 if refused == target
524 ));
525 }
526 provider.authorize_snap_write(write).unwrap();
527 }
528
529 #[test]
530 fn the_pivot_cannot_move_off_the_canonical_chain() {
531 let factory = factory();
532 let provider = factory.database_provider_rw().unwrap();
533 let write = provider.start_snap_attempt(generation(1)).unwrap();
534 let orphan = SnapGeneration::new(BlockNumHash::new(2, B256::repeat_byte(0xee)), B256::ZERO);
535
536 let refused = provider.advance_snap_pivot(write, orphan);
537
538 assert!(matches!(refused, Err(SnapSyncError::NonCanonicalBlock { block: 2, .. })));
539 provider.authorize_snap_write(write).unwrap();
540 }
541
542 #[test]
543 fn an_interrupted_advance_keeps_the_previous_pivot() {
544 let factory = factory();
545 let provider = factory.database_provider_rw().unwrap();
546 let write = provider.start_snap_attempt(generation(1)).unwrap();
547 provider.commit().unwrap();
548
549 let provider = factory.database_provider_rw().unwrap();
550 provider.advance_snap_pivot(write, generation(2)).unwrap();
551 drop(provider);
552
553 let provider = factory.database_provider_rw().unwrap();
555 assert_eq!(provider.active_snap_write().unwrap(), Some(write));
556 assert_eq!(provider.authorize_snap_write(write).unwrap().pivot().number, 1);
557 }
558
559 #[test]
560 fn a_verified_attempt_accepts_no_further_writes() {
561 let factory = factory();
562 let provider = factory.database_provider_rw().unwrap();
563 let write = provider.start_snap_attempt(generation(1)).unwrap();
564
565 provider.verify_snap_attempt(write).unwrap();
566
567 assert!(!provider.snap_attempt().unwrap().unwrap().is_unfinished());
568 assert_eq!(provider.active_snap_write().unwrap(), None);
569 assert!(matches!(
570 provider.authorize_snap_write(write),
571 Err(SnapSyncError::StaleWrite { .. })
572 ));
573 provider.abandon_snap_attempt().unwrap();
575 assert!(provider.snap_attempt().unwrap().unwrap().is_verified());
576 }
577
578 #[test]
579 fn a_record_this_build_cannot_read_is_reported_rather_than_ignored() {
580 let factory = factory();
581
582 for record in [br#"{"version":999}"#.to_vec(), b"{}".to_vec(), b"not json".to_vec()] {
583 let provider = factory.database_provider_rw().unwrap();
584 provider.write_metadata(keys::SNAP_ATTEMPT, record).unwrap();
585
586 assert!(provider.snap_attempt().is_err());
587 assert!(provider.start_snap_attempt(generation(1)).is_err());
589 }
590 }
591}