1use crate::{
4 common::DownloadContext, SnapAccountStore, SnapStorageStore, SnapSyncError, StorageChunk,
5 StorageProgress, VerifiedRange, MAX_HASH,
6};
7use alloy_primitives::{B256, U256};
8use futures::future::join_all;
9use reth_db_api::transaction::DbTxMut;
10use reth_downloaders::snap::{
11 StorageRangeDownloader, StorageRangeOutcome, VerifiedAccountBatch, VerifiedStorageRanges,
12};
13use reth_eth_wire_types::snap::GetStorageRangesMessage;
14use reth_network_p2p::snap::client::SnapClient;
15use reth_network_peers::PeerId;
16use reth_storage_api::{
17 DBProvider, DatabaseProviderFactory, MetadataProvider, MetadataWriter, StateWriter,
18};
19use reth_tasks::Runtime;
20use std::fmt;
21
22pub const DEFAULT_STORAGE_ACCOUNTS: usize = 128;
24
25pub const DEFAULT_REPAIR_SLOTS: usize = 128;
27
28pub struct StorageRangeDownload<C, F> {
33 context: DownloadContext<C, F>,
34 max_accounts: usize,
36 max_repair_slots: usize,
38}
39
40impl<C, F> StorageRangeDownload<C, F> {
41 pub const fn new(client: C, factory: F, runtime: Runtime) -> Self {
43 Self {
44 context: DownloadContext::new(client, factory, runtime),
45 max_accounts: DEFAULT_STORAGE_ACCOUNTS,
46 max_repair_slots: DEFAULT_REPAIR_SLOTS,
47 }
48 }
49
50 pub const fn with_response_bytes(mut self, response_bytes: u64) -> Self {
52 self.context.set_response_bytes(response_bytes);
53 self
54 }
55
56 pub const fn with_max_accounts(mut self, max_accounts: usize) -> Self {
58 self.max_accounts = if max_accounts == 0 { 1 } else { max_accounts };
59 self
60 }
61
62 pub const fn with_max_repair_slots(mut self, max_repair_slots: usize) -> Self {
65 self.max_repair_slots = if max_repair_slots == 0 { 1 } else { max_repair_slots };
66 self
67 }
68}
69
70impl<C, F> StorageRangeDownload<C, F>
71where
72 C: SnapClient + Clone + Unpin,
73 F: DatabaseProviderFactory + Clone + 'static,
74 F::Provider: MetadataProvider,
75 F::ProviderRW: MetadataProvider + MetadataWriter + StateWriter + DBProvider<Tx: DbTxMut>,
76{
77 pub async fn next(&mut self, range: &VerifiedRange) -> Result<StorageRangeStep, SnapSyncError> {
82 let (write, origin) = (range.write(), range.origin());
83 let progress =
84 self.context.factory().database_provider_ro()?.storage_progress(write, origin)?;
85 let contracts = range.range().storage_batch();
86 let Some(first) =
87 contracts.accounts().iter().position(|(account, _)| !progress.is_complete(*account))
88 else {
89 return Ok(StorageRangeStep::Complete)
90 };
91 let end = progress.request_end(contracts.accounts(), first, self.max_accounts);
92 let batch = contracts.range(first..end).expect("positions are inside the batch");
93 let from = progress.resume_at(batch.accounts()[0].0).expect("first contract is incomplete");
94
95 let request = GetStorageRangesMessage {
96 request_id: self.context.next_request_id(),
97 root_hash: batch.state_root(),
98 account_hashes: batch.accounts().iter().map(|(account, _)| *account).collect(),
99 starting_hash: from.into(),
100 limit_hash: MAX_HASH.into(),
101 response_bytes: self.context.response_bytes(),
102 };
103 let downloader = StorageRangeDownloader::new(
104 self.context.client().clone(),
105 request,
106 &batch,
107 self.context.runtime().clone(),
108 )?;
109 let ranges = match downloader.await? {
110 StorageRangeOutcome::Verified(ranges) => ranges,
111 StorageRangeOutcome::Unavailable { peer_id } => {
112 return Ok(StorageRangeStep::Unavailable { peer_id })
113 }
114 };
115 let chunks = chunks(ranges, batch, from)?;
116 let committed = self
118 .context
119 .commit(move |provider| {
120 let mut progress = StorageProgress::START;
121 for chunk in chunks {
122 progress = provider.commit_storage_chunk(write, origin, chunk)?;
123 }
124 Ok(progress)
125 })
126 .await?;
127 Ok(StorageRangeStep::Committed(committed))
128 }
129
130 pub async fn repair_slots(
134 &mut self,
135 range: &VerifiedRange,
136 ) -> Result<Option<Vec<(B256, U256)>>, SnapSyncError> {
137 let (write, account) = (range.write(), range.origin());
138 let repairs = self.context.factory().database_provider_ro()?.snap_repairs(write)?;
139 let contracts = range.range().storage_batch();
141 let Some(batch) = contracts.range(0..1).filter(|batch| batch.accounts()[0].0 == account)
142 else {
143 return Ok(Some(Vec::new()))
144 };
145
146 let mut requests = Vec::new();
147 for slot in repairs.slots(account).take(self.max_repair_slots) {
148 let request = GetStorageRangesMessage {
149 request_id: self.context.next_request_id(),
150 root_hash: batch.state_root(),
151 account_hashes: vec![account],
152 starting_hash: slot.into(),
153 limit_hash: slot.into(),
154 response_bytes: self.context.response_bytes(),
155 };
156 let downloader = StorageRangeDownloader::new(
157 self.context.client().clone(),
158 request,
159 &batch,
160 self.context.runtime().clone(),
161 )?;
162 requests.push(async move { downloader.await.map(|outcome| (slot, outcome)) });
163 }
164
165 let requested = requests.len();
166 let mut values = Vec::new();
167 for response in join_all(requests).await {
168 let (slot, StorageRangeOutcome::Verified(ranges)) = response? else { continue };
169 let value = ranges
171 .into_ranges()
172 .into_iter()
173 .next()
174 .and_then(|range| range.slots.first().copied())
175 .filter(|(key, _)| *key == slot)
176 .map_or(U256::ZERO, |(_, value)| value);
177 values.push((slot, value));
178 }
179 Ok((!values.is_empty() || requested == 0).then_some(values))
181 }
182}
183
184impl<C, F> fmt::Debug for StorageRangeDownload<C, F> {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 f.debug_struct("StorageRangeDownload")
187 .field("context", &self.context)
188 .field("max_accounts", &self.max_accounts)
189 .field("max_repair_slots", &self.max_repair_slots)
190 .finish()
191 }
192}
193
194#[derive(Debug)]
196pub enum StorageRangeStep {
197 Committed(StorageProgress),
199 Unavailable {
201 peer_id: PeerId,
203 },
204 Complete,
206}
207
208fn chunks(
211 ranges: VerifiedStorageRanges,
212 batch: VerifiedAccountBatch<'_>,
213 from: B256,
214) -> Result<Vec<StorageChunk>, SnapSyncError> {
215 let roots: Vec<B256> =
216 batch.accounts().iter().map(|(_, account)| account.storage_root).collect();
217 let resume = ranges.follow_up(0, batch)?.map(|(request, _)| {
218 (request.account_hashes[0], request.starting_hash.unwrap_or(B256::ZERO))
219 });
220 Ok(ranges
221 .into_ranges()
222 .into_iter()
223 .zip(roots)
224 .enumerate()
225 .map(|(index, (range, storage_root))| {
226 let from = if index == 0 { from } else { B256::ZERO };
228 let next =
229 resume.filter(|(account, _)| *account == range.account_hash).map(|(_, slot)| slot);
230 StorageChunk::new(range.account_hash, storage_root, from, range.slots, next)
231 })
232 .collect())
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use crate::{
239 test_utils::{
240 account, generation, hashed_factory_with_roots, key, state_root, storage_ranges,
241 storage_root_of, stored_slots, verified_range, verified_repair, ScriptedSnapClient,
242 },
243 SnapAccountStore, SnapAttemptStore, SnapCatchUpStore, StateRepairs,
244 };
245 use alloy_eips::BlockNumHash;
246 use alloy_primitives::U256;
247 use reth_eth_wire_types::snap::StorageRangesMessage;
248 use reth_network_p2p::{error::PeerRequestResult, snap::client::SnapResponse};
249 use reth_network_peers::WithPeerId;
250 use reth_provider::{test_utils::MockNodeTypesWithDB, ProviderFactory};
251 use reth_trie_common::TrieAccount;
252 use std::sync::Arc;
253
254 const FAR: B256 = B256::repeat_byte(0xaa);
255
256 type Factory = ProviderFactory<MockNodeTypesWithDB>;
257 type Download = StorageRangeDownload<Arc<ScriptedSnapClient>, Factory>;
258
259 fn large() -> Vec<(B256, U256)> {
261 vec![(key(1), U256::from(11)), (key(2), U256::from(12)), (key(3), U256::from(13))]
262 }
263
264 fn small() -> Vec<(B256, U256)> {
265 vec![(key(9), U256::from(19))]
266 }
267
268 fn contract(nonce: u64, slots: &[(B256, U256)]) -> TrieAccount {
269 let mut contract = account(nonce);
270 contract.storage_root = storage_root_of(slots);
271 contract
272 }
273
274 fn accounts() -> Vec<(B256, TrieAccount)> {
276 vec![
277 (key(1), account(1)),
278 (key(2), contract(2, &large())),
279 (key(3), contract(3, &small())),
280 (FAR, account(4)),
281 ]
282 }
283
284 fn started(accounts: &[(B256, TrieAccount)]) -> (Factory, VerifiedRange) {
286 started_moving_to(accounts, accounts)
287 }
288
289 fn started_moving_to(
291 accounts: &[(B256, TrieAccount)],
292 moved: &[(B256, TrieAccount)],
293 ) -> (Factory, VerifiedRange) {
294 let factory = hashed_factory_with_roots(&[state_root(accounts), state_root(moved)]);
295 let provider = factory.database_provider_rw().unwrap();
296 let write = provider.start_snap_attempt(generation(1, state_root(accounts))).unwrap();
297 provider.start_account_coverage(write).unwrap();
298 provider.commit().unwrap();
299 let range = verified_range(accounts, 0..accounts.len(), B256::ZERO, &[]);
300 (factory, VerifiedRange::new(write, range))
301 }
302
303 fn download(
304 responses: impl IntoIterator<Item = PeerRequestResult<SnapResponse>>,
305 factory: Factory,
306 ) -> (Arc<ScriptedSnapClient>, Download) {
307 let client = Arc::new(ScriptedSnapClient::new(responses));
308 (Arc::clone(&client), StorageRangeDownload::new(client, factory, Runtime::test()))
309 }
310
311 async fn committed(download: &mut Download, range: &VerifiedRange) -> StorageProgress {
312 match download.next(range).await.unwrap() {
313 StorageRangeStep::Committed(progress) => progress,
314 step => panic!("expected a commit, got {step:?}"),
315 }
316 }
317
318 fn slots_of(factory: &Factory, account: B256) -> Vec<(B256, U256)> {
319 stored_slots(&factory.database_provider_ro().unwrap(), account)
320 }
321
322 #[tokio::test]
323 async fn a_contract_spanning_responses_is_committed_response_by_response() {
324 let accounts = accounts();
325 let (factory, range) = started(&accounts);
326 let (large, small) = (large(), small());
327 let responses = [
328 storage_ranges(1, &[&large[..1]], &large, &[B256::ZERO, key(1)]),
330 storage_ranges(2, &[&large[1..]], &large, &[key(2), key(3)]),
332 storage_ranges(3, &[&small[..]], &small, &[]),
333 ];
334 let (client, mut download) = download(responses, factory.clone());
335
336 let progress = committed(&mut download, &range).await;
337 assert_eq!(progress.resume_at(key(2)), Some(key(2)));
338 assert_eq!(slots_of(&factory, key(2)), large[..1]);
339
340 assert!(committed(&mut download, &range).await.is_complete(key(2)));
341 assert!(committed(&mut download, &range).await.is_complete(key(3)));
342 assert!(matches!(download.next(&range).await.unwrap(), StorageRangeStep::Complete));
343 assert_eq!(
344 *client.storage_requests(),
345 [
346 (vec![key(2), key(3)], B256::ZERO),
347 (vec![key(2), key(3)], key(2)),
348 (vec![key(3)], B256::ZERO),
349 ]
350 );
351
352 let provider = factory.database_provider_rw().unwrap();
354 let coverage = provider
355 .commit_account_range(range.write(), range.range(), Default::default(), Vec::new())
356 .unwrap();
357 provider.commit().unwrap();
358 assert!(coverage.is_complete());
359 assert_eq!(slots_of(&factory, key(2)), large);
360 assert_eq!(slots_of(&factory, key(3)), small);
361 }
362
363 #[tokio::test]
364 async fn an_unavailable_response_leaves_the_progress_in_place() {
365 let accounts = accounts();
366 let (factory, range) = started(&accounts);
367 let large = large();
368 let peer = PeerId::random();
369 let empty = StorageRangesMessage { request_id: 2, slots: Vec::new(), proof: Vec::new() };
370 let responses = [
371 storage_ranges(1, &[&large[..1]], &large, &[B256::ZERO, key(1)]),
372 Ok(WithPeerId::new(peer, SnapResponse::StorageRanges(empty))),
373 storage_ranges(3, &[&large[1..]], &large, &[key(2), key(3)]),
374 ];
375 let (client, mut download) = download(responses, factory.clone());
376 let progress = committed(&mut download, &range).await;
377
378 let step = download.next(&range).await.unwrap();
379
380 assert!(matches!(step, StorageRangeStep::Unavailable { peer_id } if peer_id == peer));
381 let provider = factory.database_provider_ro().unwrap();
382 assert_eq!(provider.storage_progress(range.write(), range.origin()).unwrap(), progress);
383 drop(provider);
384 assert!(committed(&mut download, &range).await.is_complete(key(2)));
385 let origins: Vec<_> = client.storage_requests().iter().map(|(_, from)| *from).collect();
386 assert_eq!(origins, [B256::ZERO, key(2), key(2)]);
387 }
388
389 #[tokio::test]
390 async fn requests_ask_for_at_most_the_configured_contracts() {
391 let accounts = accounts();
392 let (factory, range) = started(&accounts);
393 let (large, small) = (large(), small());
394 let responses = [
395 storage_ranges(1, &[&large[..]], &large, &[]),
396 storage_ranges(2, &[&small[..]], &small, &[]),
397 ];
398 let (client, download) = download(responses, factory);
399 let mut download = download.with_max_accounts(1);
400
401 committed(&mut download, &range).await;
402 committed(&mut download, &range).await;
403
404 assert!(matches!(download.next(&range).await.unwrap(), StorageRangeStep::Complete));
405 assert_eq!(
406 *client.storage_requests(),
407 [(vec![key(2)], B256::ZERO), (vec![key(3)], B256::ZERO)]
408 );
409 }
410
411 #[tokio::test]
412 async fn a_range_without_contracts_requests_no_storage() {
413 let accounts = vec![(key(1), account(1)), (FAR, account(2))];
414 let (factory, range) = started(&accounts);
415 let (client, mut download) = download([], factory);
416
417 assert!(matches!(download.next(&range).await.unwrap(), StorageRangeStep::Complete));
418 assert!(client.storage_requests().is_empty());
419 }
420
421 #[tokio::test]
422 async fn storage_fetched_before_the_pivot_moved_is_not_resumed() {
423 let accounts = accounts();
424 let (factory, range) = started(&accounts);
425 let large = large();
426 let first = storage_ranges(1, &[&large[..1]], &large, &[B256::ZERO, key(1)]);
427 let (client, mut download) = download([first], factory.clone());
428 committed(&mut download, &range).await;
429 let provider = factory.database_provider_rw().unwrap();
430 provider.advance_snap_pivot(range.write(), generation(2, state_root(&accounts))).unwrap();
431 provider.commit().unwrap();
432
433 assert!(matches!(download.next(&range).await, Err(SnapSyncError::StaleWrite { .. })));
434 assert_eq!(client.storage_requests().len(), 1);
435 }
436
437 #[tokio::test]
438 async fn storage_part_way_through_resumes_at_the_new_root() {
439 let accounts = accounts();
440 let (large, small) = (large(), small());
441 let mut moved = accounts.clone();
443 moved[0].1 = contract(1, &small);
444 let (factory, range) = started_moving_to(&accounts, &moved);
445 let responses = [
446 storage_ranges(1, &[&large[..1]], &large, &[B256::ZERO, key(1)]),
447 storage_ranges(2, &[&small[..]], &small, &[]),
448 storage_ranges(3, &[&large[1..]], &large, &[key(2), key(3)]),
449 storage_ranges(4, &[&small[..]], &small, &[]),
450 ];
451 let (client, mut download) = download(responses, factory.clone());
452 committed(&mut download, &range).await;
453
454 let provider = factory.database_provider_rw().unwrap();
455 let write =
456 provider.advance_snap_pivot(range.write(), generation(2, state_root(&moved))).unwrap();
457 provider.commit().unwrap();
458 let range =
459 VerifiedRange::new(write, verified_range(&moved, 0..moved.len(), B256::ZERO, &[]));
460 for _ in 0..3 {
461 committed(&mut download, &range).await;
462 }
463 assert!(matches!(download.next(&range).await.unwrap(), StorageRangeStep::Complete));
464 assert_eq!(
465 *client.storage_requests(),
466 [
467 (vec![key(2), key(3)], B256::ZERO),
468 (vec![key(1)], B256::ZERO),
469 (vec![key(2), key(3)], key(2)),
470 (vec![key(3)], B256::ZERO),
471 ]
472 );
473
474 let provider = factory.database_provider_rw().unwrap();
476 assert!(matches!(
477 provider.commit_account_range(write, range.range(), Default::default(), Vec::new()),
478 Err(SnapSyncError::CatchUpBehindPivot { applied: 1, pivot: 2 })
479 ));
480 let block = BlockNumHash::new(2, B256::repeat_byte(2));
481 provider.commit_block_access_list(write, block, B256::repeat_byte(1), &[]).unwrap();
482 let coverage = provider
483 .commit_account_range(write, range.range(), Default::default(), Vec::new())
484 .unwrap();
485 provider.commit().unwrap();
486 assert!(coverage.is_complete());
487 assert_eq!(slots_of(&factory, key(1)), small);
488 assert_eq!(slots_of(&factory, key(2)), large);
489 }
490
491 #[tokio::test]
492 async fn a_page_ending_before_a_carried_contract_hands_it_to_the_next_range() {
493 let accounts = accounts();
494 let (factory, range) = started(&accounts);
495 let large = large();
496 let first = storage_ranges(1, &[&large[..1]], &large, &[B256::ZERO, key(1)]);
497 let (_, mut download) = download([first], factory.clone());
498 committed(&mut download, &range).await;
499 let provider = factory.database_provider_rw().unwrap();
500 let write = provider
501 .advance_snap_pivot(range.write(), generation(2, state_root(&accounts)))
502 .unwrap();
503 let block = BlockNumHash::new(2, B256::repeat_byte(2));
504 provider.commit_block_access_list(write, block, B256::repeat_byte(1), &[]).unwrap();
505
506 let page = verified_range(&accounts, 0..1, B256::ZERO, &[B256::ZERO, key(1)]);
508 let next = provider
509 .commit_account_range(write, &page, Default::default(), Vec::new())
510 .unwrap()
511 .next()
512 .unwrap();
513
514 assert!(next <= key(2));
515 assert_eq!(provider.storage_progress(write, next).unwrap().resume_at(key(2)), Some(key(2)));
516 }
517
518 fn repairing(accounts: &[(B256, TrieAccount)], slots: &[B256]) -> (Factory, VerifiedRange) {
520 let (factory, _) = started(accounts);
521 let provider = factory.database_provider_rw().unwrap();
522 let write = provider.active_snap_write().unwrap().unwrap();
523 let mut repairs = StateRepairs::default();
524 for slot in slots {
525 repairs.insert_slot(key(2), *slot);
526 }
527 provider.schedule_snap_repairs(write, repairs).unwrap();
528 provider.commit().unwrap();
529 let range = verified_repair(accounts, 1..2, key(2), &[key(2)]);
530 (factory, VerifiedRange::new(write, range))
531 }
532
533 #[tokio::test]
534 async fn repair_slots_read_zero_where_the_pivot_holds_none() {
535 let accounts = accounts();
536 let (factory, range) = repairing(&accounts, &[key(1), key(5)]);
537 let large = large();
538 let responses = [
539 storage_ranges(1, &[&large[..1]], &large, &[key(1)]),
540 storage_ranges(2, &[&[]], &large, &[key(5)]),
542 ];
543 let (client, mut download) = download(responses, factory);
544
545 let values = download.repair_slots(&range).await.unwrap();
546
547 assert_eq!(values, Some(vec![(key(1), U256::from(11)), (key(5), U256::ZERO)]));
548 assert_eq!(*client.storage_requests(), [(vec![key(2)], key(1)), (vec![key(2)], key(5))]);
549 }
550
551 #[tokio::test]
552 async fn a_contract_scheduled_without_slots_fetches_none() {
553 let accounts = accounts();
554 let (factory, range) = repairing(&accounts, &[]);
555 let provider = factory.database_provider_rw().unwrap();
556 let mut repairs = StateRepairs::default();
557 repairs.insert_account(key(2));
558 provider.schedule_snap_repairs(range.write(), repairs).unwrap();
559 provider.commit().unwrap();
560 let (client, mut download) = download([], factory);
561
562 assert_eq!(download.repair_slots(&range).await.unwrap(), Some(Vec::new()));
563 assert!(client.storage_requests().is_empty());
564 }
565
566 #[tokio::test]
567 async fn an_unserved_repair_slot_fetches_nothing() {
568 let accounts = accounts();
569 let (factory, range) = repairing(&accounts, &[key(1)]);
570 let (_, mut download) = download([storage_ranges(1, &[], &[], &[])], factory);
571
572 assert_eq!(download.repair_slots(&range).await.unwrap(), None);
573 }
574
575 #[tokio::test]
576 async fn served_repair_slots_survive_an_unserved_one() {
577 let accounts = accounts();
578 let (factory, range) = repairing(&accounts, &[key(1), key(5)]);
579 let large = large();
580 let responses = [
581 storage_ranges(1, &[&large[..1]], &large, &[key(1)]),
582 storage_ranges(2, &[], &[], &[]),
583 ];
584 let (_, mut download) = download(responses, factory);
585
586 let values = download.repair_slots(&range).await.unwrap();
587
588 assert_eq!(values, Some(vec![(key(1), U256::from(11))]));
590 }
591
592 #[tokio::test]
593 async fn repair_slots_are_fetched_in_bounded_batches() {
594 let accounts = accounts();
595 let (factory, range) = repairing(&accounts, &[key(1), key(5)]);
596 let large = large();
597 let responses = [storage_ranges(1, &[&large[..1]], &large, &[key(1)])];
598 let (client, download) = download(responses, factory);
599 let mut download = download.with_max_repair_slots(1);
600
601 let values = download.repair_slots(&range).await.unwrap();
602
603 assert_eq!(values, Some(vec![(key(1), U256::from(11))]));
605 assert_eq!(*client.storage_requests(), [(vec![key(2)], key(1))]);
606 }
607}