1use super::{
7 request::{SnapVerifier, VerifyingRequest},
8 VerifiedAccountBatch,
9};
10use alloy_primitives::{B256, U256};
11use futures::Future;
12use reth_eth_wire_types::snap::{
13 GetStorageRangesMessage, RangeBound, StorageData, StorageRangesMessage,
14};
15use reth_network_p2p::{
16 error::RequestError,
17 snap::client::{SnapClient, SnapResponse},
18};
19use reth_network_peers::PeerId;
20use reth_tasks::Runtime;
21use reth_trie_common::{range_proof::verify_range_proof, EMPTY_ROOT_HASH};
22use std::{
23 pin::Pin,
24 task::{Context, Poll},
25};
26use tracing::debug;
27
28const MAX_HASH: B256 = B256::new([0xff; B256::len_bytes()]);
30
31#[derive(Debug)]
33pub struct StorageRangeDownloader<C: SnapClient>(VerifyingRequest<C, StorageRangeVerifier>);
34
35impl<C: SnapClient> StorageRangeDownloader<C> {
36 pub fn new(
46 client: C,
47 request: GetStorageRangesMessage,
48 batch: &VerifiedAccountBatch<'_>,
49 runtime: Runtime,
50 ) -> Result<Self, InvalidStorageRangeRequest> {
51 let origin = request.starting_hash.unwrap_or(B256::ZERO);
52 let limit = request.limit_hash.unwrap_or(MAX_HASH);
53 if origin > limit {
54 return Err(InvalidStorageRangeRequest::ReversedBounds { origin, limit })
55 }
56 if request.account_hashes.is_empty() {
57 return Err(InvalidStorageRangeRequest::NoAccounts)
58 }
59 if request.account_hashes.len() > 1 && origin == B256::ZERO && limit != MAX_HASH {
62 return Err(InvalidStorageRangeRequest::LimitedMultipleAccounts {
63 accounts: request.account_hashes.len(),
64 })
65 }
66 batch.verify_batch(&request)?;
67 let mut storage_roots = Vec::with_capacity(batch.accounts().len());
68 for (index, (account_hash, account)) in batch.accounts().iter().enumerate() {
69 if account.storage_root == EMPTY_ROOT_HASH {
70 return Err(InvalidStorageRangeRequest::EmptyStorageRoot {
71 index,
72 account_hash: *account_hash,
73 })
74 }
75 storage_roots.push(account.storage_root);
76 }
77
78 let verifier = StorageRangeVerifier { request: request.clone(), storage_roots };
79 Ok(Self(VerifyingRequest::new(client, request, verifier, runtime)))
80 }
81}
82
83impl<C> Future for StorageRangeDownloader<C>
84where
85 C: SnapClient + Unpin,
86{
87 type Output = Result<StorageRangeOutcome, RequestError>;
88
89 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
90 self.get_mut().0.poll_verified(cx)
91 }
92}
93
94#[derive(Clone, Debug, PartialEq, Eq)]
96pub enum StorageRangeOutcome {
97 Unavailable {
99 peer_id: PeerId,
101 },
102 Verified(VerifiedStorageRanges),
104}
105
106#[derive(Clone, Debug, PartialEq, Eq)]
110pub struct VerifiedStorageRanges {
111 ranges: Vec<VerifiedStorageRange>,
113 continuation: Option<StorageRangeContinuation>,
115 request: Box<GetStorageRangesMessage>,
117}
118
119impl VerifiedStorageRanges {
120 pub fn into_ranges(self) -> Vec<VerifiedStorageRange> {
122 self.ranges
123 }
124
125 pub fn follow_up<'a>(
131 &self,
132 request_id: u64,
133 batch: VerifiedAccountBatch<'a>,
134 ) -> Result<
135 Option<(GetStorageRangesMessage, VerifiedAccountBatch<'a>)>,
136 InvalidStorageRangeRequest,
137 > {
138 batch.verify_batch(&self.request)?;
139 let Some(continuation) = self.continuation else { return Ok(None) };
140 let (index, starting_hash, limit_hash) = match continuation {
141 StorageRangeContinuation::Partial { account_index: 0, starting_hash, .. } => {
142 (0, starting_hash.into(), self.request.limit_hash)
143 }
144 StorageRangeContinuation::Partial { account_index, starting_hash, .. } => {
145 (account_index, starting_hash.into(), RangeBound::default())
146 }
147 StorageRangeContinuation::NextAccount { account_index, .. } => {
148 (account_index, RangeBound::default(), RangeBound::default())
149 }
150 };
151
152 let request = GetStorageRangesMessage {
153 request_id,
154 account_hashes: self.request.account_hashes[index..].to_vec(),
155 starting_hash,
156 limit_hash,
157 ..(*self.request).clone()
158 };
159 let narrowed = batch.slice(index).expect("checked batch covers the continuation");
160 Ok(Some((request, narrowed)))
161 }
162}
163
164#[derive(Clone, Debug, PartialEq, Eq)]
166pub struct VerifiedStorageRange {
167 pub account_hash: B256,
169 pub slots: Vec<(B256, U256)>,
171}
172
173#[derive(Clone, Copy, Debug, PartialEq, Eq)]
175enum StorageRangeContinuation {
176 Partial {
178 account_index: usize,
180 account_hash: B256,
182 starting_hash: B256,
184 },
185 NextAccount {
187 account_index: usize,
189 account_hash: B256,
191 },
192}
193
194#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
196pub enum InvalidStorageRangeRequest {
197 #[error(
199 "storage range requests state root {requested}, but accounts are authenticated by {authenticated}"
200 )]
201 StateRootMismatch {
202 requested: B256,
204 authenticated: B256,
206 },
207 #[error("storage range request contains no accounts")]
209 NoAccounts,
210 #[error("limited storage range request contains {accounts} accounts")]
212 LimitedMultipleAccounts {
213 accounts: usize,
215 },
216 #[error("storage range origin {origin} exceeds limit {limit}")]
218 ReversedBounds {
219 origin: B256,
221 limit: B256,
223 },
224 #[error("storage range request has {requested} accounts but {supplied} were supplied")]
226 AccountCount {
227 requested: usize,
229 supplied: usize,
231 },
232 #[error(
234 "storage range account {index} requests {requested}, but authenticated account is {supplied}"
235 )]
236 AccountMismatch {
237 index: usize,
239 requested: B256,
241 supplied: B256,
243 },
244 #[error("storage range account {index} ({account_hash}) has an empty storage root")]
246 EmptyStorageRoot {
247 index: usize,
249 account_hash: B256,
251 },
252}
253
254#[derive(Clone, Debug)]
256struct StorageRangeVerifier {
257 request: GetStorageRangesMessage,
259 storage_roots: Vec<B256>,
261}
262
263impl SnapVerifier for StorageRangeVerifier {
264 type Request = GetStorageRangesMessage;
265 type Output = StorageRangeOutcome;
266
267 fn verify(self, peer_id: PeerId, response: SnapResponse) -> Result<Self::Output, RequestError> {
268 self.verify_response(peer_id, response)
269 }
270}
271
272impl StorageRangeVerifier {
273 fn verify_response(
275 &self,
276 peer_id: PeerId,
277 response: SnapResponse,
278 ) -> Result<StorageRangeOutcome, RequestError> {
279 let Some(response) = self.accepted_response(response)? else {
280 return Ok(StorageRangeOutcome::Unavailable { peer_id })
281 };
282
283 let proof_index =
285 response.slots.len().checked_sub(1).filter(|_| !response.proof.is_empty());
286 let mut ranges = Vec::with_capacity(response.slots.len());
287 let mut bounded_next = None;
288
289 for (index, slots) in response.slots.iter().enumerate() {
290 let proof = if proof_index == Some(index) { response.proof.as_slice() } else { &[] };
291 let verified = self.verify_range(index, slots, proof)?;
292 ranges.push(verified.range);
293 bounded_next = verified.within_limit;
294 }
295
296 let continuation = self.continuation(&ranges, bounded_next);
297 Ok(StorageRangeOutcome::Verified(VerifiedStorageRanges {
298 ranges,
299 continuation,
300 request: Box::new(self.request.clone()),
301 }))
302 }
303
304 fn accepted_response(
307 &self,
308 response: SnapResponse,
309 ) -> Result<Option<StorageRangesMessage>, RequestError> {
310 let SnapResponse::StorageRanges(mut response) = response else {
311 debug!(target: "downloaders::snap", "Expected storage ranges response");
312 return Err(RequestError::BadResponse)
313 };
314 if response.request_id != self.request.request_id {
315 debug!(
316 target: "downloaders::snap",
317 expected = self.request.request_id,
318 got = response.request_id,
319 "Storage ranges response id mismatch"
320 );
321 return Err(RequestError::BadResponse)
322 }
323 if response.slots.len() > self.request.account_hashes.len() {
324 debug!(target: "downloaders::snap", "Storage response contains extra ranges");
325 return Err(RequestError::BadResponse)
326 }
327 if response.slots.is_empty() {
328 if response.proof.is_empty() {
329 return Ok(None)
330 }
331 response.slots.push(Vec::new());
333 }
334 Ok(Some(response))
335 }
336
337 fn verify_range(
339 &self,
340 index: usize,
341 slots: &[StorageData],
342 proof: &[alloy_primitives::Bytes],
343 ) -> Result<VerifiedRange, RequestError> {
344 let account_hash = self.request.account_hashes[index];
345 let (origin, limit) = if index == 0 {
347 (
348 self.request.starting_hash.unwrap_or(B256::ZERO),
349 self.request.limit_hash.unwrap_or(MAX_HASH),
350 )
351 } else {
352 (B256::ZERO, MAX_HASH)
353 };
354
355 if slots.iter().filter(|slot| slot.hash > limit).nth(1).is_some() {
357 debug!(target: "downloaders::snap", %account_hash, "Storage range exceeds limit");
358 return Err(RequestError::BadResponse)
359 }
360
361 let mut decoded = Self::decode_slots(account_hash, origin, slots)?;
362 let leaves = decoded.iter().map(|(hash, value)| (*hash, alloy_rlp::encode(value)));
363 let next = verify_range_proof(self.storage_roots[index], origin, limit, leaves, proof)
364 .map_err(|error| {
365 debug!(
366 target: "downloaders::snap",
367 %account_hash,
368 %error,
369 "Invalid storage range proof"
370 );
371 RequestError::BadResponse
372 })?;
373
374 if next.is_some_and(|next| next <= origin) {
377 debug!(target: "downloaders::snap", %account_hash, "Storage range does not advance");
378 return Err(RequestError::BadResponse)
379 }
380
381 decoded.truncate(decoded.partition_point(|(hash, _)| *hash <= limit));
382 Ok(VerifiedRange {
383 range: VerifiedStorageRange { account_hash, slots: decoded },
384 within_limit: next.filter(|next| *next <= limit),
385 })
386 }
387
388 fn continuation(
391 &self,
392 ranges: &[VerifiedStorageRange],
393 final_next: Option<B256>,
394 ) -> Option<StorageRangeContinuation> {
395 if let Some(starting_hash) = final_next {
396 return Some(StorageRangeContinuation::Partial {
397 account_index: ranges.len() - 1,
398 account_hash: ranges.last().expect("a response range exists").account_hash,
399 starting_hash,
400 })
401 }
402 let account_index = ranges.len();
403 self.request.account_hashes.get(account_index).copied().map(|account_hash| {
404 StorageRangeContinuation::NextAccount { account_index, account_hash }
405 })
406 }
407
408 fn decode_slots(
410 account_hash: B256,
411 origin: B256,
412 slots: &[StorageData],
413 ) -> Result<Vec<(B256, U256)>, RequestError> {
414 let mut decoded = Vec::with_capacity(slots.len());
415 let mut previous = None;
416
417 for slot in slots {
418 if slot.hash < origin || previous.is_some_and(|previous| slot.hash <= previous) {
419 debug!(
420 target: "downloaders::snap",
421 %account_hash,
422 "Storage slots precede origin or are not strictly ordered"
423 );
424 return Err(RequestError::BadResponse)
425 }
426 let value = slot.value().map_err(|error| {
427 debug!(target: "downloaders::snap", %account_hash, %error, "Invalid storage value");
428 RequestError::BadResponse
429 })?;
430 if value.is_zero() {
431 debug!(target: "downloaders::snap", %account_hash, "Storage trie contains zero leaf");
432 return Err(RequestError::BadResponse)
433 }
434 previous = Some(slot.hash);
435 decoded.push((slot.hash, value));
436 }
437 Ok(decoded)
438 }
439}
440
441struct VerifiedRange {
443 range: VerifiedStorageRange,
445 within_limit: Option<B256>,
447}
448
449#[cfg(test)]
450mod tests {
451 use super::{
452 super::{request::MAX_RETRIES, test_utils::TestSnapClient, VerifiedAccountRange},
453 *,
454 };
455 use alloy_primitives::{Bytes, KECCAK256_EMPTY};
456 use reth_network_p2p::{error::PeerRequestResult, priority::Priority};
457 use reth_network_peers::WithPeerId;
458 use reth_trie_common::{
459 proof::ProofRetainer, HashBuilder, Nibbles, TrieAccount, EMPTY_ROOT_HASH,
460 };
461 use std::sync::Arc;
462
463 fn key(value: u64) -> B256 {
464 B256::left_padding_from(&value.to_be_bytes())
465 }
466
467 fn slots(values: &[(B256, u64)]) -> Vec<(B256, U256)> {
469 values.iter().map(|(hash, value)| (*hash, U256::from(*value))).collect()
470 }
471
472 fn storage_root(slots: &[(B256, U256)], targets: &[B256]) -> (B256, Vec<Bytes>) {
473 let targets = targets.iter().copied().map(Nibbles::unpack).collect();
474 let mut builder = HashBuilder::default().with_proof_retainer(ProofRetainer::new(targets));
475 for (hash, value) in slots {
476 builder.add_leaf(Nibbles::unpack(*hash), &alloy_rlp::encode(value));
477 }
478 let root = builder.root();
479 let proof = builder
480 .take_proof_nodes()
481 .into_nodes_sorted()
482 .into_iter()
483 .map(|(_, node)| node)
484 .collect();
485 (root, proof)
486 }
487
488 const STATE_ROOT: B256 = B256::repeat_byte(0xaa);
490
491 fn verified_range(accounts: &[(B256, TrieAccount)]) -> VerifiedAccountRange {
493 VerifiedAccountRange {
494 state_root: STATE_ROOT,
495 accounts: accounts.to_vec(),
496 has_more: false,
497 next: None,
498 }
499 }
500
501 fn account(storage_root: B256) -> TrieAccount {
502 TrieAccount { nonce: 1, balance: U256::from(2), storage_root, code_hash: KECCAK256_EMPTY }
503 }
504
505 fn wire_slots(slots: &[(B256, U256)]) -> Vec<StorageData> {
506 slots.iter().map(|(hash, value)| StorageData::from_value(*hash, *value)).collect()
507 }
508
509 fn account_refs(accounts: &[(B256, TrieAccount)]) -> Vec<(B256, &TrieAccount)> {
510 accounts.iter().map(|(hash, account)| (*hash, account)).collect()
511 }
512
513 fn request(accounts: &[(B256, TrieAccount)]) -> GetStorageRangesMessage {
514 GetStorageRangesMessage {
515 request_id: 1,
516 root_hash: STATE_ROOT,
517 account_hashes: accounts.iter().map(|(hash, _)| *hash).collect(),
518 starting_hash: B256::ZERO.into(),
519 limit_hash: MAX_HASH.into(),
520 response_bytes: 512 * 1024,
521 }
522 }
523
524 fn response(
525 peer: PeerId,
526 request_id: u64,
527 slots: Vec<Vec<StorageData>>,
528 proof: Vec<Bytes>,
529 ) -> PeerRequestResult<SnapResponse> {
530 Ok(WithPeerId::new(
531 peer,
532 SnapResponse::StorageRanges(StorageRangesMessage { request_id, slots, proof }),
533 ))
534 }
535
536 fn rejecting_client(
537 response: impl Fn(PeerId) -> PeerRequestResult<SnapResponse>,
538 ) -> Arc<TestSnapClient> {
539 let peer = PeerId::random();
540 Arc::new(TestSnapClient::new((0..=MAX_RETRIES).map(|_| response(peer))))
541 }
542
543 fn downloader<C: SnapClient>(
544 client: C,
545 request: GetStorageRangesMessage,
546 accounts: &[(B256, TrieAccount)],
547 ) -> Result<StorageRangeDownloader<C>, InvalidStorageRangeRequest> {
548 let range = verified_range(accounts);
549 StorageRangeDownloader::new(client, request, &range.batch(), Runtime::test())
550 }
551
552 #[tokio::test]
553 async fn complete_ranges_for_multiple_accounts_are_verified() {
554 let first = slots(&[(key(1), 11), (key(2), 12)]);
555 let second = slots(&[(key(3), 13)]);
556 let (first_root, _) = storage_root(&first, &[]);
557 let (second_root, proof) = storage_root(&second, &[key(3)]);
558 let accounts = vec![(key(100), account(first_root)), (key(200), account(second_root))];
559 let client = Arc::new(TestSnapClient::new([response(
560 PeerId::random(),
561 1,
562 vec![wire_slots(&first), wire_slots(&second)],
563 proof,
564 )]));
565
566 let outcome =
567 downloader(Arc::clone(&client), request(&accounts), &accounts).unwrap().await.unwrap();
568
569 let StorageRangeOutcome::Verified(verified) = outcome else { panic!("verified ranges") };
570 assert_eq!(verified.ranges.len(), 2);
571 assert_eq!(verified.ranges[0].slots, first);
572 assert_eq!(verified.ranges[1].slots, second);
573 assert_eq!(verified.continuation, None);
574 assert!(client.reported().is_empty());
575 }
576
577 #[tokio::test]
578 async fn partial_final_range_reports_its_slot_continuation() {
579 let all = slots(&[(key(1), 11), (key(2), 12), (key(3), 13)]);
580 let (root, proof) = storage_root(&all, &[B256::ZERO, key(1)]);
581 let accounts = vec![(key(100), account(root))];
582 let client = Arc::new(TestSnapClient::new([response(
583 PeerId::random(),
584 1,
585 vec![wire_slots(&all[..1])],
586 proof,
587 )]));
588
589 let outcome =
590 downloader(Arc::clone(&client), request(&accounts), &accounts).unwrap().await.unwrap();
591
592 let StorageRangeOutcome::Verified(verified) = outcome else { panic!("verified ranges") };
593 assert_eq!(verified.ranges[0].slots, all[..1]);
594 assert_eq!(
595 verified.continuation,
596 Some(StorageRangeContinuation::Partial {
597 account_index: 0,
598 account_hash: key(100),
599 starting_hash: key(2),
600 })
601 );
602 assert!(client.reported().is_empty());
603 }
604
605 #[tokio::test]
606 async fn an_omitted_account_resumes_at_the_next_position() {
607 let first = slots(&[(key(1), 11)]);
608 let (first_root, _) = storage_root(&first, &[]);
609 let accounts =
610 vec![(key(100), account(first_root)), (key(200), account(B256::repeat_byte(0xbb)))];
611 let client = Arc::new(TestSnapClient::new([response(
612 PeerId::random(),
613 1,
614 vec![wire_slots(&first)],
615 Vec::new(),
616 )]));
617
618 let outcome =
619 downloader(Arc::clone(&client), request(&accounts), &accounts).unwrap().await.unwrap();
620
621 let StorageRangeOutcome::Verified(verified) = outcome else { panic!("verified ranges") };
622 assert_eq!(verified.ranges.len(), 1);
623 assert_eq!(
624 verified.continuation,
625 Some(StorageRangeContinuation::NextAccount {
626 account_index: 1,
627 account_hash: key(200),
628 })
629 );
630 }
631
632 #[test]
633 fn empty_storage_roots_must_be_filtered_before_submission() {
634 let accounts = vec![
635 (key(100), account(B256::repeat_byte(0xaa))),
636 (key(200), account(EMPTY_ROOT_HASH)),
637 (key(300), account(B256::repeat_byte(0xbb))),
638 ];
639 let client = TestSnapClient::new([response(PeerId::random(), 1, Vec::new(), Vec::new())]);
640
641 assert_eq!(
642 downloader(&client, request(&accounts), &accounts).unwrap_err(),
643 InvalidStorageRangeRequest::EmptyStorageRoot { index: 1, account_hash: key(200) }
644 );
645 assert!(client.priorities().is_empty());
646
647 let range = verified_range(&accounts);
648 let batch = range.storage_batch();
649 let mut storage_request = request(&accounts);
650 storage_request.account_hashes = batch.accounts().iter().map(|(hash, _)| *hash).collect();
651 assert_eq!(storage_request.account_hashes, vec![key(100), key(300)]);
652 assert!(
653 StorageRangeDownloader::new(&client, storage_request, &batch, Runtime::test()).is_ok()
654 );
655 assert_eq!(*client.priorities(), [Priority::Normal]);
656 }
657
658 #[tokio::test]
659 async fn a_response_without_slots_or_proof_reports_the_state_unavailable() {
660 let batches = [
661 vec![(key(100), account(B256::repeat_byte(0xbb)))],
662 vec![
663 (key(100), account(B256::repeat_byte(0xaa))),
664 (key(200), account(B256::repeat_byte(0xbb))),
665 ],
666 ];
667 for accounts in batches {
668 let peer = PeerId::random();
669 let client = Arc::new(TestSnapClient::new([response(peer, 1, Vec::new(), Vec::new())]));
670
671 let outcome = downloader(Arc::clone(&client), request(&accounts), &accounts)
672 .unwrap()
673 .await
674 .unwrap();
675
676 assert_eq!(outcome, StorageRangeOutcome::Unavailable { peer_id: peer });
677 assert!(client.reported().is_empty());
679 }
680 }
681
682 #[tokio::test]
683 async fn a_mismatched_request_id_exhausts_the_retry_budget() {
684 let all = slots(&[(key(1), 11)]);
685 let (root, _) = storage_root(&all, &[]);
686 let accounts = vec![(key(100), account(root))];
687
688 let client = rejecting_client(|peer| response(peer, 9, vec![wire_slots(&all)], Vec::new()));
689 let error = downloader(Arc::clone(&client), request(&accounts), &accounts)
690 .unwrap()
691 .await
692 .unwrap_err();
693
694 assert_eq!(error, RequestError::BadResponse);
695 assert_eq!(client.reported().len(), usize::from(MAX_RETRIES) + 1);
697 }
698
699 #[tokio::test]
700 async fn a_range_proved_against_another_root_is_rejected() {
701 let all = slots(&[(key(1), 11)]);
702 let accounts = vec![(key(100), account(B256::repeat_byte(0xcc)))];
703
704 let client = rejecting_client(|peer| response(peer, 1, vec![wire_slots(&all)], Vec::new()));
705 let error = downloader(Arc::clone(&client), request(&accounts), &accounts)
706 .unwrap()
707 .await
708 .unwrap_err();
709
710 assert_eq!(error, RequestError::BadResponse);
711 assert_eq!(client.reported().len(), usize::from(MAX_RETRIES) + 1);
712 }
713
714 #[tokio::test]
715 async fn slots_that_are_not_strictly_ordered_penalize_the_peer_before_a_retry_succeeds() {
716 let all = slots(&[(key(1), 11), (key(2), 12)]);
717 let (root, _) = storage_root(&all, &[]);
718 let accounts = vec![(key(100), account(root))];
719 let bad_peer = PeerId::random();
720 let good_peer = PeerId::random();
721 let mut reversed = wire_slots(&all);
722 reversed.reverse();
723 let client = Arc::new(TestSnapClient::new([
724 response(bad_peer, 1, vec![reversed], Vec::new()),
725 response(good_peer, 1, vec![wire_slots(&all)], Vec::new()),
726 ]));
727
728 let outcome =
729 downloader(Arc::clone(&client), request(&accounts), &accounts).unwrap().await.unwrap();
730
731 let StorageRangeOutcome::Verified(verified) = outcome else { panic!("verified ranges") };
732 assert_eq!(verified.ranges[0].slots, all);
733 assert_eq!(*client.reported(), [bad_peer]);
734 assert_eq!(*client.priorities(), [Priority::Normal, Priority::High]);
735 }
736
737 #[test]
738 fn a_request_that_does_not_match_its_accounts_is_refused() {
739 let accounts = vec![(key(100), account(EMPTY_ROOT_HASH))];
740 let client = TestSnapClient::new([]);
741
742 let mut mismatched = request(&accounts);
743 mismatched.account_hashes = vec![key(999)];
744 let error = downloader(&client, mismatched, &accounts).unwrap_err();
745 assert_eq!(
746 error,
747 InvalidStorageRangeRequest::AccountMismatch {
748 index: 0,
749 requested: key(999),
750 supplied: key(100),
751 }
752 );
753
754 let mut empty = request(&accounts);
755 empty.account_hashes.clear();
756 assert_eq!(
757 downloader(&client, empty, &accounts).unwrap_err(),
758 InvalidStorageRangeRequest::NoAccounts
759 );
760
761 let mut reversed = request(&accounts);
762 reversed.starting_hash = key(9).into();
763 reversed.limit_hash = key(1).into();
764 assert_eq!(
765 downloader(&client, reversed, &accounts).unwrap_err(),
766 InvalidStorageRangeRequest::ReversedBounds { origin: key(9), limit: key(1) }
767 );
768 }
769
770 #[test]
771 fn a_request_for_another_state_root_is_refused() {
772 let accounts = vec![(key(100), account(EMPTY_ROOT_HASH))];
773 let client = TestSnapClient::new([]);
774
775 let mut other_root = request(&accounts);
776 other_root.root_hash = B256::repeat_byte(0xcc);
777 assert_eq!(
778 downloader(&client, other_root, &accounts).unwrap_err(),
779 InvalidStorageRangeRequest::StateRootMismatch {
780 requested: B256::repeat_byte(0xcc),
781 authenticated: STATE_ROOT,
782 }
783 );
784 }
785
786 #[tokio::test]
787 async fn a_trie_continuing_past_the_limit_needs_no_follow_up() {
788 let all = slots(&[(key(1), 11), (key(2), 12), (key(3), 13)]);
789 let (root, proof) = storage_root(&all, &[B256::ZERO, key(2)]);
790 let accounts = vec![(key(100), account(root))];
791 let client = Arc::new(TestSnapClient::new([response(
792 PeerId::random(),
793 1,
794 vec![wire_slots(&all[..2])],
795 proof,
796 )]));
797 let mut bounded = request(&accounts);
798 bounded.limit_hash = key(2).into();
799
800 let outcome = downloader(Arc::clone(&client), bounded, &accounts).unwrap().await.unwrap();
801
802 let StorageRangeOutcome::Verified(verified) = outcome else { panic!("verified ranges") };
803 let range = verified_range(&accounts);
804 assert_eq!(verified.follow_up(2, range.batch()).unwrap(), None);
805 assert_eq!(verified.into_ranges()[0].slots, all[..2]);
806 }
807
808 #[tokio::test]
809 async fn a_follow_up_keeps_the_limit_only_at_the_first_account() {
810 let all = slots(&[(key(1), 11), (key(2), 12), (key(3), 13)]);
811 let (root, proof) = storage_root(&all, &[B256::ZERO, key(1)]);
812 let accounts = vec![(key(100), account(root))];
813 let client = Arc::new(TestSnapClient::new([response(
814 PeerId::random(),
815 1,
816 vec![wire_slots(&all[..1])],
817 proof,
818 )]));
819 let mut bounded = request(&accounts);
820 bounded.limit_hash = key(5).into();
821
822 let outcome =
823 downloader(Arc::clone(&client), bounded.clone(), &accounts).unwrap().await.unwrap();
824
825 let StorageRangeOutcome::Verified(verified) = outcome else { panic!("verified ranges") };
826 let range = verified_range(&accounts);
827 let (follow_up, narrowed) = verified.follow_up(2, range.batch()).unwrap().unwrap();
828 assert_eq!(narrowed.accounts(), account_refs(&accounts));
829 assert_eq!(follow_up.request_id, 2);
830 assert_eq!(follow_up.root_hash, bounded.root_hash);
831 assert_eq!(follow_up.account_hashes, bounded.account_hashes);
832 assert_eq!(follow_up.starting_hash, key(2).into());
833 assert_eq!(follow_up.limit_hash, key(5).into());
834 }
835
836 #[tokio::test]
839 async fn a_follow_up_past_the_first_account_is_unbounded() {
840 let first = slots(&[(key(1), 11)]);
841 let second = slots(&[(key(1), 21), (key(2), 22)]);
842 let (first_root, _) = storage_root(&first, &[]);
843 let (second_root, proof) = storage_root(&second, &[B256::ZERO, key(1)]);
844 let accounts = vec![(key(100), account(first_root)), (key(200), account(second_root))];
845 let client = Arc::new(TestSnapClient::new([response(
846 PeerId::random(),
847 1,
848 vec![wire_slots(&first), wire_slots(&second[..1])],
849 proof,
850 )]));
851
852 let outcome =
853 downloader(Arc::clone(&client), request(&accounts), &accounts).unwrap().await.unwrap();
854
855 let StorageRangeOutcome::Verified(verified) = outcome else { panic!("verified ranges") };
856 assert_eq!(
857 verified.continuation,
858 Some(StorageRangeContinuation::Partial {
859 account_index: 1,
860 account_hash: key(200),
861 starting_hash: key(2),
862 })
863 );
864
865 let range = verified_range(&accounts);
866 let (follow_up, narrowed) = verified.follow_up(2, range.batch()).unwrap().unwrap();
867 assert_eq!(narrowed.accounts(), account_refs(&accounts[1..]));
868 assert_eq!(follow_up.account_hashes, vec![key(200)]);
869 assert_eq!(follow_up.starting_hash, key(2).into());
870 assert_eq!(follow_up.limit_hash, RangeBound::default());
871 }
872
873 #[tokio::test]
875 async fn resuming_a_partial_range_advances_until_the_trie_is_exhausted() {
876 let all = slots(&[(key(1), 11), (key(2), 12), (key(3), 13)]);
877 let (root, _) = storage_root(&all, &[]);
878 let accounts = vec![(key(100), account(root))];
879 let range = verified_range(&accounts);
880 let mut request = request(&accounts);
881 let mut origins = Vec::new();
882 let mut collected = Vec::new();
883
884 for served in 0..all.len() {
885 let origin = request.starting_hash.unwrap_or(B256::ZERO);
886 origins.push(origin);
887 let (_, proof) = storage_root(&all, &[origin, all[served].0]);
888 let client = Arc::new(TestSnapClient::new([response(
889 PeerId::random(),
890 request.request_id,
891 vec![wire_slots(&all[served..=served])],
892 proof,
893 )]));
894
895 let outcome =
896 downloader(Arc::clone(&client), request.clone(), &accounts).unwrap().await.unwrap();
897 let StorageRangeOutcome::Verified(verified) = outcome else {
898 panic!("verified ranges")
899 };
900 collected.extend(verified.ranges[0].slots.clone());
901
902 let Some((follow_up, _)) =
903 verified.follow_up(request.request_id + 1, range.batch()).unwrap()
904 else {
905 assert_eq!(served, all.len() - 1);
906 break
907 };
908 request = follow_up;
909 }
910
911 assert_eq!(collected, all);
912 assert_eq!(origins, vec![B256::ZERO, key(2), key(3)]);
913 }
914
915 #[test]
918 fn a_zero_origin_limited_request_must_not_carry_several_accounts() {
919 let accounts = vec![
920 (key(100), account(B256::repeat_byte(0xaa))),
921 (key(200), account(B256::repeat_byte(0xbb))),
922 ];
923 let client =
925 TestSnapClient::new([response(PeerId::random(), 1, vec![Vec::new()], Vec::new())]);
926
927 let mut bounded = request(&accounts);
928 bounded.limit_hash = key(5).into();
929 assert_eq!(
930 downloader(&client, bounded.clone(), &accounts).unwrap_err(),
931 InvalidStorageRangeRequest::LimitedMultipleAccounts { accounts: 2 }
932 );
933
934 bounded.starting_hash = key(1).into();
935 assert!(downloader(&client, bounded, &accounts).is_ok());
936 }
937
938 #[tokio::test]
941 async fn consecutive_account_transitions_carry_their_batch_forward() {
942 let served = slots(&[(key(1), 11)]);
943 let (root, _) = storage_root(&served, &[]);
944 let accounts =
945 vec![(key(100), account(root)), (key(200), account(root)), (key(300), account(root))];
946 let range = verified_range(&accounts);
947 let mut request = request(&accounts);
948 let mut batch = range.batch();
949
950 for remaining in (1..accounts.len()).rev() {
951 let client = Arc::new(TestSnapClient::new([response(
952 PeerId::random(),
953 request.request_id,
954 vec![wire_slots(&served)],
955 Vec::new(),
956 )]));
957 let outcome = StorageRangeDownloader::new(
958 Arc::clone(&client),
959 request.clone(),
960 &batch,
961 Runtime::test(),
962 )
963 .unwrap()
964 .await
965 .unwrap();
966
967 let StorageRangeOutcome::Verified(verified) = outcome else {
968 panic!("verified ranges")
969 };
970 let (follow_up, narrowed) =
971 verified.follow_up(request.request_id + 1, batch).unwrap().unwrap();
972 assert_eq!(follow_up.account_hashes.len(), remaining);
973 assert_eq!(narrowed.accounts(), account_refs(&accounts[accounts.len() - remaining..]));
974 request = follow_up;
975 batch = narrowed;
976 }
977
978 assert_eq!(request.account_hashes, vec![key(300)]);
979 }
980
981 #[tokio::test]
982 async fn a_follow_up_refuses_a_batch_from_another_request() {
983 let served = slots(&[(key(1), 11)]);
984 let (root, _) = storage_root(&served, &[]);
985 let accounts = vec![(key(100), account(root)), (key(200), account(root))];
986 let client = Arc::new(TestSnapClient::new([response(
987 PeerId::random(),
988 1,
989 vec![wire_slots(&served)],
990 Vec::new(),
991 )]));
992
993 let outcome =
994 downloader(Arc::clone(&client), request(&accounts), &accounts).unwrap().await.unwrap();
995 let StorageRangeOutcome::Verified(verified) = outcome else { panic!("verified ranges") };
996
997 let others = vec![(key(900), account(root)), (key(901), account(root))];
998 let other_range = verified_range(&others);
999 assert_eq!(
1000 verified.follow_up(2, other_range.batch()).unwrap_err(),
1001 InvalidStorageRangeRequest::AccountMismatch {
1002 index: 0,
1003 requested: key(100),
1004 supplied: key(900),
1005 }
1006 );
1007 }
1008}