1use crate::{common::SnapRecord, SnapAccountStore, SnapAttemptStore, SnapSyncError, SnapWrite};
7use alloy_primitives::{B256, U256};
8use reth_db_api::{
9 cursor::DbDupCursorRO,
10 tables,
11 transaction::{DbTx, DbTxMut},
12};
13use reth_storage_api::{DBProvider, MetadataProvider, MetadataWriter, SnapAttemptId, StateWriter};
14use reth_trie_common::{root::storage_root, HashedPostState, HashedStorage, EMPTY_ROOT_HASH};
15use serde::{Deserialize, Serialize};
16
17pub trait SnapStorageStore {
22 fn storage_progress(
26 &self,
27 write: SnapWrite,
28 origin: B256,
29 ) -> Result<StorageProgress, SnapSyncError>;
30
31 fn commit_storage_chunk(
36 &self,
37 write: SnapWrite,
38 origin: B256,
39 chunk: StorageChunk,
40 ) -> Result<StorageProgress, SnapSyncError>
41 where
42 Self: MetadataWriter + StateWriter + DBProvider<Tx: DbTxMut>;
43}
44
45#[derive(Clone, Debug, PartialEq, Eq)]
47pub struct StorageChunk {
48 account: B256,
50 storage_root: B256,
52 from: B256,
54 slots: Vec<(B256, U256)>,
56 next: Option<B256>,
58}
59
60impl StorageChunk {
61 pub const fn new(
63 account: B256,
64 storage_root: B256,
65 from: B256,
66 slots: Vec<(B256, U256)>,
67 next: Option<B256>,
68 ) -> Self {
69 Self { account, storage_root, from, slots, next }
70 }
71}
72
73#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
78pub struct StorageProgress {
79 complete: Option<B256>,
81 partial: Vec<PartialStorage>,
83}
84
85impl StorageProgress {
86 pub const START: Self = Self { complete: None, partial: Vec::new() };
88
89 pub fn is_complete(&self, account: B256) -> bool {
91 self.complete.is_some_and(|complete| account <= complete)
92 }
93
94 pub fn has_slots(&self, account: B256) -> bool {
96 self.is_complete(account) || self.partial_of(account).is_some()
97 }
98
99 fn partial_of(&self, account: B256) -> Option<&PartialStorage> {
101 self.partial.iter().find(|partial| partial.account == account)
102 }
103
104 pub(crate) fn request_end<T>(
107 &self,
108 contracts: &[(B256, T)],
109 first: usize,
110 max: usize,
111 ) -> usize {
112 let end = contracts.len().min(first.saturating_add(max));
113 contracts[first + 1..end]
114 .iter()
115 .position(|(account, _)| self.partial_of(*account).is_some())
116 .map_or(end, |offset| first + 1 + offset)
117 }
118
119 pub(crate) fn carry_to(
121 mut self,
122 provider: &impl MetadataWriter,
123 write: SnapWrite,
124 next: B256,
125 ) -> Result<(), SnapSyncError> {
126 self.complete = self.complete.filter(|complete| *complete >= next);
127 self.partial.retain(|partial| partial.account >= next);
128 if self != Self::START {
129 StoredProgress::new(write, next, self).write(provider)?;
130 }
131 Ok(())
132 }
133
134 pub fn resume_at(&self, account: B256) -> Option<B256> {
136 if self.is_complete(account) {
137 return None
138 }
139 Some(self.partial_of(account).map_or(B256::ZERO, |partial| partial.next))
140 }
141
142 fn carried(mut self) -> Self {
144 for partial in &mut self.partial {
145 partial.storage_root = None;
146 }
147 self
148 }
149
150 fn advance(mut self, chunk: &StorageChunk) -> Result<Self, SnapSyncError> {
155 let blocked = self
156 .partial
157 .iter()
158 .any(|partial| partial.account < chunk.account && partial.storage_root.is_some());
159 if blocked || self.resume_at(chunk.account) != Some(chunk.from) {
160 return Err(SnapSyncError::OutOfOrderStorage {
161 account: chunk.account,
162 from: chunk.from,
163 })
164 }
165 if let Some(partial) = self.partial_of(chunk.account) &&
166 let Some(expected) = partial.storage_root &&
167 expected != chunk.storage_root
168 {
169 return Err(SnapSyncError::StorageRootMismatch {
170 account: chunk.account,
171 expected,
172 got: chunk.storage_root,
173 })
174 }
175 if chunk.next.is_some_and(|next| next <= chunk.from) {
176 return Err(SnapSyncError::NoProgress { origin: chunk.from })
177 }
178 self.partial.retain(|partial| partial.account > chunk.account);
181 match chunk.next {
182 Some(next) => {
183 let storage_root = Some(chunk.storage_root);
184 self.partial
185 .insert(0, PartialStorage { account: chunk.account, storage_root, next });
186 }
187 None => self.complete = Some(chunk.account),
188 }
189 Ok(self)
190 }
191}
192
193#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
195struct PartialStorage {
196 account: B256,
198 storage_root: Option<B256>,
200 next: B256,
202}
203
204#[derive(Serialize, Deserialize)]
206pub(crate) struct StoredProgress {
207 version: u32,
209 attempt: SnapAttemptId,
211 state_version: u64,
213 origin: B256,
215 progress: StorageProgress,
217}
218
219impl SnapRecord for StoredProgress {
220 const KEY: &'static str = "snap_storage_progress";
221 const VERSION: u32 = 2;
222}
223
224impl StoredProgress {
225 const fn new(write: SnapWrite, origin: B256, progress: StorageProgress) -> Self {
227 Self {
228 version: Self::VERSION,
229 attempt: write.attempt(),
230 state_version: write.state_version(),
231 origin,
232 progress,
233 }
234 }
235
236 fn into_progress(self, write: SnapWrite, origin: B256) -> Option<StorageProgress> {
238 if self.attempt != write.attempt() || self.origin != origin {
239 return None
240 }
241 Some(if self.state_version == write.state_version() {
242 self.progress
243 } else {
244 self.progress.carried()
245 })
246 }
247}
248
249impl<T: MetadataProvider> SnapStorageStore for T {
250 fn storage_progress(
252 &self,
253 write: SnapWrite,
254 origin: B256,
255 ) -> Result<StorageProgress, SnapSyncError> {
256 self.authorize_snap_write(write)?;
257 let Some(stored) = StoredProgress::read(self)? else { return Ok(StorageProgress::START) };
258 Ok(stored.into_progress(write, origin).unwrap_or(StorageProgress::START))
259 }
260
261 fn commit_storage_chunk(
263 &self,
264 write: SnapWrite,
265 origin: B256,
266 chunk: StorageChunk,
267 ) -> Result<StorageProgress, SnapSyncError>
268 where
269 Self: MetadataWriter + StateWriter + DBProvider<Tx: DbTxMut>,
270 {
271 let coverage = self.account_coverage(write)?.ok_or(SnapSyncError::NoCoverage)?;
272 if coverage.next() != Some(origin) {
273 return Err(SnapSyncError::OutOfOrderRange { expected: coverage.next(), got: origin })
274 }
275 if chunk.account < origin || chunk.storage_root == EMPTY_ROOT_HASH {
276 return Err(SnapSyncError::UnexpectedStorage { account: chunk.account })
277 }
278 let progress = self.storage_progress(write, origin)?;
279 let complete = progress.complete;
280 let progress = progress.advance(&chunk)?;
281
282 if chunk.next.is_none() {
283 let start = complete.map_or(Some(origin), |account| {
286 U256::from_be_bytes(account.0).checked_add(U256::ONE).map(B256::from)
287 });
288 if let Some(start) = start {
289 self.remove::<tables::HashedStorages>(start..chunk.account)?;
290 }
291 }
292 if chunk.from.is_zero() {
293 self.remove::<tables::HashedStorages>(chunk.account..=chunk.account)?;
294 }
295 let state = HashedPostState::default()
296 .with_storages([(chunk.account, HashedStorage::from_iter(chunk.slots))])
297 .into_sorted();
298 self.write_hashed_state(&state)?;
299 let stored = StoredProgress::new(write, origin, progress);
300 stored.write(self)?;
301 Ok(stored.progress)
302 }
303}
304
305pub(crate) fn persisted_storage_root(tx: &impl DbTx, account: B256) -> Result<B256, SnapSyncError> {
308 let mut cursor = tx.cursor_dup_read::<tables::HashedStorages>()?;
309 let mut failed = None;
310 let slots = cursor.walk_dup(Some(account), None)?.map_while(|entry| {
311 entry.map(|(_, slot)| (slot.key, slot.value)).map_err(|error| failed = Some(error)).ok()
312 });
313 let root = storage_root(slots);
314 failed.map_or(Ok(root), |error| Err(error.into()))
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320 use crate::test_utils::{
321 account, generation, hashed_factory_with_roots, state_root, storage_root_of, stored_slots,
322 };
323 use reth_provider::{
324 test_utils::MockNodeTypesWithDB, DatabaseProviderFactory, ProviderFactory,
325 };
326 use std::ops::Range;
327
328 const CONTRACT: B256 = B256::repeat_byte(0x22);
329
330 fn slot(value: u8) -> B256 {
331 B256::with_last_byte(value)
332 }
333
334 fn slots() -> Vec<(B256, U256)> {
335 vec![(slot(1), U256::from(11)), (slot(2), U256::from(12)), (slot(3), U256::from(13))]
336 }
337
338 fn root() -> B256 {
339 storage_root_of(&slots())
340 }
341
342 fn chunk(served: Range<usize>, from: B256, next: Option<B256>) -> StorageChunk {
344 StorageChunk::new(CONTRACT, root(), from, slots()[served].to_vec(), next)
345 }
346
347 fn started() -> (ProviderFactory<MockNodeTypesWithDB>, SnapWrite) {
349 let mut contract = account(1);
350 contract.storage_root = root();
351 let state_root = state_root(&[(CONTRACT, contract)]);
352 let factory = hashed_factory_with_roots(&[state_root]);
353 let provider = factory.database_provider_rw().unwrap();
354 let generation = generation(1, state_root);
355 let write = provider.start_snap_attempt(generation).unwrap();
356 provider.start_account_coverage(write).unwrap();
357 provider.commit().unwrap();
358 (factory, write)
359 }
360
361 #[test]
362 fn an_interrupted_chunk_commit_keeps_the_last_committed_progress() {
363 let (factory, write) = started();
364 let provider = factory.database_provider_rw().unwrap();
365 let first = chunk(0..1, B256::ZERO, Some(slot(2)));
366 let progress = provider.commit_storage_chunk(write, B256::ZERO, first).unwrap();
367 provider.commit().unwrap();
368 assert_eq!(progress.resume_at(CONTRACT), Some(slot(2)));
369
370 let provider = factory.database_provider_rw().unwrap();
371 let rest = chunk(1..3, slot(2), None);
372 assert!(provider
373 .commit_storage_chunk(write, B256::ZERO, rest)
374 .unwrap()
375 .is_complete(CONTRACT));
376 drop(provider);
377
378 let provider = factory.database_provider_rw().unwrap();
379 assert_eq!(provider.storage_progress(write, B256::ZERO).unwrap(), progress);
380 assert_eq!(stored_slots(&provider, CONTRACT), slots()[..1]);
381 }
382
383 #[test]
384 fn a_chunk_must_continue_its_contract_before_another_starts() {
385 let (factory, write) = started();
386 let provider = factory.database_provider_rw().unwrap();
387 let first = chunk(0..1, B256::ZERO, Some(slot(2)));
388 let progress = provider.commit_storage_chunk(write, B256::ZERO, first).unwrap();
389
390 let refused = [
391 chunk(0..1, B256::ZERO, Some(slot(2))),
393 chunk(2..3, slot(3), None),
394 StorageChunk::new(B256::repeat_byte(0x44), root(), B256::ZERO, slots(), None),
395 ];
396 for chunk in refused {
397 assert!(matches!(
398 provider.commit_storage_chunk(write, B256::ZERO, chunk),
399 Err(SnapSyncError::OutOfOrderStorage { .. })
400 ));
401 }
402 let stalled = chunk(1..1, slot(2), Some(slot(2)));
403 assert!(matches!(
404 provider.commit_storage_chunk(write, B256::ZERO, stalled),
405 Err(SnapSyncError::NoProgress { .. })
406 ));
407 let other_root = StorageChunk::new(
408 CONTRACT,
409 B256::repeat_byte(0x33),
410 slot(2),
411 slots()[1..].to_vec(),
412 None,
413 );
414 assert!(matches!(
415 provider.commit_storage_chunk(write, B256::ZERO, other_root),
416 Err(SnapSyncError::StorageRootMismatch { .. })
417 ));
418
419 assert_eq!(provider.storage_progress(write, B256::ZERO).unwrap(), progress);
420 assert_eq!(stored_slots(&provider, CONTRACT), slots()[..1]);
421 }
422
423 #[test]
424 fn starting_a_contract_replaces_what_an_earlier_attempt_left() {
425 let (factory, write) = started();
426 let provider = factory.database_provider_rw().unwrap();
427 let leftover = HashedStorage::from_iter([(slot(7), U256::from(77))]);
428 let leftover = HashedPostState::default().with_storages([(CONTRACT, leftover)]);
429 provider.write_hashed_state(&leftover.into_sorted()).unwrap();
430
431 provider.commit_storage_chunk(write, B256::ZERO, chunk(0..3, B256::ZERO, None)).unwrap();
432
433 assert_eq!(stored_slots(&provider, CONTRACT), slots());
434 assert_eq!(persisted_storage_root(provider.tx_ref(), CONTRACT).unwrap(), root());
435 }
436
437 fn carried() -> (ProviderFactory<MockNodeTypesWithDB>, SnapWrite, SnapWrite) {
439 let (factory, write) = started();
440 let provider = factory.database_provider_rw().unwrap();
441 let first = chunk(0..1, B256::ZERO, Some(slot(2)));
442 provider.commit_storage_chunk(write, B256::ZERO, first).unwrap();
443 let advanced =
444 provider.advance_snap_pivot(write, generation(2, B256::repeat_byte(2))).unwrap();
445 provider.commit().unwrap();
446 (factory, write, advanced)
447 }
448
449 fn whole(account: B256) -> StorageChunk {
451 StorageChunk::new(account, root(), B256::ZERO, slots(), None)
452 }
453
454 #[test]
455 fn progress_survives_the_pivot_moving_with_its_root_unproved() {
456 let (factory, write, advanced) = carried();
457 let provider = factory.database_provider_rw().unwrap();
458
459 assert_eq!(provider.storage_progress(advanced, slot(9)).unwrap(), StorageProgress::START);
460 let progress = provider.storage_progress(advanced, B256::ZERO).unwrap();
461 assert_eq!(progress.resume_at(CONTRACT), Some(slot(2)));
462 assert!(matches!(
463 provider.commit_storage_chunk(write, B256::ZERO, chunk(1..3, slot(2), None)),
464 Err(SnapSyncError::StaleWrite { .. })
465 ));
466
467 let rest = StorageChunk::new(
469 CONTRACT,
470 B256::repeat_byte(0x33),
471 slot(2),
472 slots()[1..].to_vec(),
473 None,
474 );
475 let progress = provider.commit_storage_chunk(advanced, B256::ZERO, rest).unwrap();
476 assert!(progress.is_complete(CONTRACT));
477 assert_eq!(stored_slots(&provider, CONTRACT), slots());
478 }
479
480 #[test]
481 fn a_carried_contract_does_not_block_the_contracts_the_new_root_holds() {
482 let (factory, _, advanced) = carried();
483 let provider = factory.database_provider_rw().unwrap();
484
485 let before = B256::repeat_byte(0x11);
487 let progress = provider.commit_storage_chunk(advanced, B256::ZERO, whole(before)).unwrap();
488 assert!(progress.is_complete(before));
489 assert_eq!(progress.resume_at(CONTRACT), Some(slot(2)));
490
491 let after = B256::repeat_byte(0x44);
493 let progress = provider.commit_storage_chunk(advanced, B256::ZERO, whole(after)).unwrap();
494 assert!(progress.is_complete(after));
495 assert!(progress.partial.is_empty());
496 }
497
498 #[test]
499 fn a_new_contract_part_way_keeps_the_carried_one_resumable() {
500 let (factory, _, advanced) = carried();
501 let provider = factory.database_provider_rw().unwrap();
502 let before = B256::repeat_byte(0x11);
503 let first =
504 StorageChunk::new(before, root(), B256::ZERO, slots()[..1].to_vec(), Some(slot(2)));
505 provider.commit_storage_chunk(advanced, B256::ZERO, first).unwrap();
506
507 let advanced =
509 provider.advance_snap_pivot(advanced, generation(3, B256::repeat_byte(3))).unwrap();
510 let progress = provider.storage_progress(advanced, B256::ZERO).unwrap();
511 assert_eq!(progress.resume_at(before), Some(slot(2)));
512 assert_eq!(progress.resume_at(CONTRACT), Some(slot(2)));
513
514 let rest = StorageChunk::new(before, root(), slot(2), slots()[1..].to_vec(), None);
515 let progress = provider.commit_storage_chunk(advanced, B256::ZERO, rest).unwrap();
516 assert!(progress.is_complete(before));
517 assert_eq!(progress.resume_at(CONTRACT), Some(slot(2)));
518 }
519
520 #[test]
521 fn a_contract_resuming_part_way_leads_its_request() {
522 let progress = StorageProgress {
523 complete: None,
524 partial: vec![PartialStorage { account: CONTRACT, storage_root: None, next: slot(2) }],
525 };
526 let contracts = [(slot(1), ()), (CONTRACT, ()), (B256::repeat_byte(0x33), ())];
527
528 assert_eq!(progress.request_end(&contracts, 0, 10), 1);
529 assert_eq!(progress.request_end(&contracts, 1, 10), 3);
530 assert_eq!(progress.request_end(&contracts, 1, 1), 2);
531 assert_eq!(StorageProgress::START.request_end(&contracts, 0, 10), 3);
532 }
533
534 #[test]
535 fn storage_outside_the_range_being_downloaded_is_refused() {
536 let (factory, write) = started();
537 let provider = factory.database_provider_rw().unwrap();
538
539 let ahead = provider.commit_storage_chunk(write, slot(9), chunk(0..3, B256::ZERO, None));
540 let empty = StorageChunk::new(CONTRACT, EMPTY_ROOT_HASH, B256::ZERO, Vec::new(), None);
541 let empty = provider.commit_storage_chunk(write, B256::ZERO, empty);
542
543 assert!(matches!(ahead, Err(SnapSyncError::OutOfOrderRange { .. })));
544 assert!(matches!(empty, Err(SnapSyncError::UnexpectedStorage { .. })));
545 assert!(stored_slots(&provider, CONTRACT).is_empty());
546 }
547
548 #[test]
549 fn a_record_this_build_cannot_read_is_reported() {
550 let (factory, write) = started();
551
552 for record in [br#"{"version":999}"#.to_vec(), b"{}".to_vec()] {
553 let provider = factory.database_provider_rw().unwrap();
554 provider.write_metadata(StoredProgress::KEY, record).unwrap();
555
556 assert!(matches!(
557 provider.storage_progress(write, B256::ZERO),
558 Err(SnapSyncError::UnsupportedRecord { .. })
559 ));
560 }
561
562 for record in [b"not json".to_vec(), br#"{"version":2}"#.to_vec()] {
563 let provider = factory.database_provider_rw().unwrap();
564 provider.write_metadata(StoredProgress::KEY, record).unwrap();
565
566 assert!(matches!(
567 provider.storage_progress(write, B256::ZERO),
568 Err(SnapSyncError::UnreadableRecord { .. })
569 ));
570 }
571 }
572}