1use alloy_primitives::{map::B256Set, B256, KECCAK256_EMPTY};
7use futures::Future;
8use reth_eth_wire_types::snap::{
9 AccountData, AccountRangeMessage, GetAccountRangeMessage, GetStorageRangesMessage,
10};
11use reth_network_p2p::{
12 error::RequestError,
13 snap::client::{SnapClient, SnapResponse},
14};
15use reth_network_peers::PeerId;
16use reth_tasks::Runtime;
17use reth_trie_common::{range_proof::verify_range_proof, TrieAccount, EMPTY_ROOT_HASH};
18use std::{
19 ops::Range,
20 pin::Pin,
21 task::{Context, Poll},
22};
23use tracing::debug;
24
25mod block_access_list;
26mod bytecode;
27mod request;
28mod storage;
29#[cfg(test)]
30mod test_utils;
31
32pub use block_access_list::*;
33pub use bytecode::*;
34use request::{SnapVerifier, VerifyingRequest};
35pub use storage::*;
36
37#[derive(Debug)]
42pub struct AccountRangeDownloader<C: SnapClient>(VerifyingRequest<C, GetAccountRangeMessage>);
43
44impl<C: SnapClient> AccountRangeDownloader<C> {
45 pub fn new(
48 client: C,
49 request: GetAccountRangeMessage,
50 runtime: Runtime,
51 ) -> Result<Self, InvalidAccountRange> {
52 if request.starting_hash > request.limit_hash {
53 return Err(InvalidAccountRange {
54 origin: request.starting_hash,
55 limit: request.limit_hash,
56 })
57 }
58 let verifier = request.clone();
59 Ok(Self(VerifyingRequest::new(client, request, verifier, runtime)))
60 }
61}
62
63impl<C> Future for AccountRangeDownloader<C>
64where
65 C: SnapClient + Unpin,
66{
67 type Output = Result<AccountRangeOutcome, RequestError>;
68
69 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
70 self.get_mut().0.poll_verified(cx)
71 }
72}
73
74#[derive(Clone, Debug, PartialEq, Eq)]
76pub enum AccountRangeOutcome {
77 Unavailable {
79 peer_id: PeerId,
81 },
82 Verified(VerifiedAccountRange),
84}
85
86#[derive(Clone, Debug, PartialEq, Eq)]
88pub struct VerifiedAccountRange {
89 state_root: B256,
92 origin: B256,
94 accounts: Vec<(B256, TrieAccount)>,
96 has_more: bool,
98 next: Option<B256>,
100}
101
102impl VerifiedAccountRange {
103 pub const fn state_root(&self) -> B256 {
105 self.state_root
106 }
107
108 pub const fn origin(&self) -> B256 {
110 self.origin
111 }
112
113 pub fn accounts(&self) -> &[(B256, TrieAccount)] {
115 &self.accounts
116 }
117
118 pub const fn has_more(&self) -> bool {
122 self.has_more
123 }
124
125 pub const fn next(&self) -> Option<B256> {
128 self.next
129 }
130
131 pub fn batch(&self) -> VerifiedAccountBatch<'_> {
133 VerifiedAccountBatch {
134 state_root: self.state_root,
135 accounts: self.accounts.iter().map(|(hash, account)| (*hash, account)).collect(),
136 }
137 }
138
139 pub fn code_hashes(&self) -> Vec<B256> {
143 let mut seen = B256Set::default();
144 self.accounts
145 .iter()
146 .map(|(_, account)| account.code_hash)
147 .filter(|hash| *hash != KECCAK256_EMPTY && seen.insert(*hash))
148 .collect()
149 }
150
151 pub fn storage_batch(&self) -> VerifiedAccountBatch<'_> {
157 VerifiedAccountBatch {
158 state_root: self.state_root,
159 accounts: self
160 .accounts
161 .iter()
162 .filter(|(_, account)| account.storage_root != EMPTY_ROOT_HASH)
163 .map(|(hash, account)| (*hash, account))
164 .collect(),
165 }
166 }
167}
168
169#[derive(Clone, Debug, PartialEq, Eq)]
174pub struct VerifiedAccountBatch<'a> {
175 state_root: B256,
177 accounts: Vec<(B256, &'a TrieAccount)>,
179}
180
181impl<'a> VerifiedAccountBatch<'a> {
182 pub const fn state_root(&self) -> B256 {
184 self.state_root
185 }
186
187 pub fn accounts(&self) -> &[(B256, &'a TrieAccount)] {
189 &self.accounts
190 }
191
192 pub fn range(&self, range: Range<usize>) -> Option<Self> {
197 self.accounts
198 .get(range)
199 .map(|accounts| Self { state_root: self.state_root, accounts: accounts.to_vec() })
200 }
201
202 pub(super) fn verify_batch(
205 &self,
206 request: &GetStorageRangesMessage,
207 ) -> Result<(), InvalidStorageRangeRequest> {
208 if request.root_hash != self.state_root {
209 return Err(InvalidStorageRangeRequest::StateRootMismatch {
210 requested: request.root_hash,
211 authenticated: self.state_root,
212 })
213 }
214 if request.account_hashes.len() != self.accounts.len() {
215 return Err(InvalidStorageRangeRequest::AccountCount {
216 requested: request.account_hashes.len(),
217 supplied: self.accounts.len(),
218 })
219 }
220 for (index, (requested, (supplied, _))) in
221 request.account_hashes.iter().zip(&self.accounts).enumerate()
222 {
223 if requested != supplied {
224 return Err(InvalidStorageRangeRequest::AccountMismatch {
225 index,
226 requested: *requested,
227 supplied: *supplied,
228 })
229 }
230 }
231 Ok(())
232 }
233
234 pub(super) fn slice(mut self, from: usize) -> Option<Self> {
236 (from <= self.accounts.len()).then(|| {
237 self.accounts.drain(..from);
238 self
239 })
240 }
241}
242
243#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
245#[error("account range origin {origin} exceeds limit {limit}")]
246pub struct InvalidAccountRange {
247 pub origin: B256,
249 pub limit: B256,
251}
252
253impl SnapVerifier for GetAccountRangeMessage {
255 type Request = Self;
256 type Output = AccountRangeOutcome;
257
258 fn verify(self, peer_id: PeerId, response: SnapResponse) -> Result<Self::Output, RequestError> {
259 let SnapResponse::AccountRange(response) = response else {
260 debug!(target: "downloaders::snap", "Expected account range response");
261 return Err(RequestError::BadResponse)
262 };
263 if response.request_id != self.request_id {
264 debug!(
265 target: "downloaders::snap",
266 expected = self.request_id,
267 got = response.request_id,
268 "Account range response id mismatch"
269 );
270 return Err(RequestError::BadResponse)
271 }
272 if response.accounts.is_empty() && response.proof.is_empty() {
273 return if self.root_hash == EMPTY_ROOT_HASH {
274 Ok(AccountRangeOutcome::Verified(VerifiedAccountRange {
275 state_root: self.root_hash,
276 origin: self.starting_hash,
277 accounts: Vec::new(),
278 has_more: false,
279 next: None,
280 }))
281 } else {
282 Ok(AccountRangeOutcome::Unavailable { peer_id })
283 }
284 }
285
286 verify_account_range(&self, response).map(AccountRangeOutcome::Verified)
287 }
288}
289
290fn verify_account_range(
292 request: &GetAccountRangeMessage,
293 response: AccountRangeMessage,
294) -> Result<VerifiedAccountRange, RequestError> {
295 if response.accounts.iter().filter(|data| data.hash > request.limit_hash).nth(1).is_some() {
297 debug!(target: "downloaders::snap", "Account range runs past the requested limit");
298 return Err(RequestError::BadResponse)
299 }
300
301 let mut accounts =
302 response.accounts.into_iter().map(AccountData::into_trie_entry).collect::<Vec<_>>();
303 let next = verify_proof(request, &accounts, &response.proof)?;
304
305 let kept = accounts.partition_point(|(hash, _)| *hash <= request.limit_hash);
308 let next = accounts.get(kept).map(|(hash, _)| *hash).or(next);
309 accounts.truncate(kept);
310 let has_more = next.is_some_and(|next| next <= request.limit_hash);
311
312 Ok(VerifiedAccountRange {
313 state_root: request.root_hash,
314 origin: request.starting_hash,
315 accounts,
316 has_more,
317 next,
318 })
319}
320
321fn verify_proof(
323 request: &GetAccountRangeMessage,
324 accounts: &[(B256, TrieAccount)],
325 proof: &[alloy_primitives::Bytes],
326) -> Result<Option<B256>, RequestError> {
327 let leaves = accounts.iter().map(|(hash, account)| (*hash, alloy_rlp::encode(account)));
328 verify_range_proof(request.root_hash, request.starting_hash, request.limit_hash, leaves, proof)
329 .map_err(|error| {
330 debug!(target: "downloaders::snap", %error, "Invalid account range proof");
331 RequestError::BadResponse
332 })
333}
334
335#[cfg(test)]
336#[allow(clippy::clone_on_copy)]
337mod tests {
338 use super::{request::MAX_RETRIES, test_utils::TestSnapClient, *};
339 use alloy_primitives::{Bytes, U256};
340 use reth_eth_wire_types::snap::ByteCodesMessage;
341 use reth_network_p2p::{error::PeerRequestResult, priority::Priority};
342 use reth_network_peers::WithPeerId;
343 use reth_trie_common::{proof::ProofRetainer, root::state_root, HashBuilder, Nibbles};
344 use std::sync::Arc;
345
346 const MAX_HASH: B256 = B256::repeat_byte(0xff);
347
348 fn key(value: u64) -> B256 {
349 B256::left_padding_from(&value.to_be_bytes())
350 }
351
352 fn account(nonce: u64) -> TrieAccount {
353 TrieAccount { nonce, balance: U256::ONE, ..Default::default() }
354 }
355
356 fn root(accounts: &[(B256, TrieAccount)]) -> B256 {
357 state_root(accounts.to_vec())
358 }
359
360 fn root_and_proof(accounts: &[(B256, TrieAccount)], targets: &[B256]) -> (B256, Vec<Bytes>) {
361 let targets = targets.iter().copied().map(Nibbles::unpack).collect();
362 let mut builder = HashBuilder::default().with_proof_retainer(ProofRetainer::new(targets));
363 for (key, account) in accounts {
364 builder.add_leaf(Nibbles::unpack(*key), &alloy_rlp::encode(account));
365 }
366 let root = builder.root();
367 let proof = builder
368 .take_proof_nodes()
369 .into_nodes_sorted()
370 .into_iter()
371 .map(|(_, node)| node)
372 .collect();
373 (root, proof)
374 }
375
376 fn request(root_hash: B256) -> GetAccountRangeMessage {
377 GetAccountRangeMessage {
378 request_id: 1,
379 root_hash,
380 starting_hash: B256::ZERO,
381 limit_hash: MAX_HASH,
382 response_bytes: 512 * 1024,
383 }
384 }
385
386 fn response(peer: PeerId, message: AccountRangeMessage) -> PeerRequestResult<SnapResponse> {
387 Ok(WithPeerId::new(peer, SnapResponse::AccountRange(message)))
388 }
389
390 fn downloader(
391 client: Arc<TestSnapClient>,
392 request: GetAccountRangeMessage,
393 ) -> Result<AccountRangeDownloader<Arc<TestSnapClient>>, InvalidAccountRange> {
394 AccountRangeDownloader::new(client, request, Runtime::test())
395 }
396
397 #[test]
398 fn verifies_and_decodes_without_an_ambient_runtime() {
399 let accounts = vec![(key(1), account(7)), (key(2), account(8))];
400 let root_hash = root(&accounts);
401 let peer = PeerId::random();
402 let message = AccountRangeMessage {
403 request_id: 1,
404 accounts: accounts
405 .iter()
406 .map(|(key, account)| AccountData::from_trie_account(*key, account))
407 .collect(),
408 proof: Vec::new(),
409 };
410 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
411
412 let downloader = downloader(Arc::clone(&client), request(root_hash)).unwrap();
413 let outcome = futures::executor::block_on(downloader).unwrap();
414
415 assert_eq!(
416 outcome,
417 AccountRangeOutcome::Verified(VerifiedAccountRange {
418 state_root: root_hash,
419 origin: B256::ZERO,
420 accounts,
421 has_more: false,
422 next: None,
423 })
424 );
425 assert!(client.reported().is_empty());
426 assert_eq!(*client.priorities(), [Priority::Normal]);
427 }
428
429 #[tokio::test]
430 async fn invalid_peer_is_reported_and_request_is_retried_at_high_priority() {
431 let accounts = vec![(key(1), account(7))];
432 let root_hash = root(&accounts);
433 let bad_peer = PeerId::random();
434 let good_peer = PeerId::random();
435 let bad = Ok(WithPeerId::new(
436 bad_peer,
437 SnapResponse::ByteCodes(ByteCodesMessage { request_id: 1, codes: Vec::new() }),
438 ));
439 let good = response(
440 good_peer,
441 AccountRangeMessage {
442 request_id: 1,
443 accounts: vec![AccountData::from_trie_account(accounts[0].0, &accounts[0].1)],
444 proof: Vec::new(),
445 },
446 );
447 let client = Arc::new(TestSnapClient::new([bad, good]));
448
449 let outcome = downloader(Arc::clone(&client), request(root_hash)).unwrap().await.unwrap();
450
451 assert!(matches!(outcome, AccountRangeOutcome::Verified(_)));
452 assert_eq!(*client.reported(), [bad_peer]);
453 assert_eq!(*client.priorities(), [Priority::Normal, Priority::High]);
454 }
455
456 #[tokio::test]
457 async fn unavailable_state_is_not_a_bad_peer_response() {
458 let peer = PeerId::random();
459 let message =
460 AccountRangeMessage { request_id: 1, accounts: Vec::new(), proof: Vec::new() };
461 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
462
463 let outcome = downloader(Arc::clone(&client), request(B256::repeat_byte(0x11)))
464 .unwrap()
465 .await
466 .unwrap();
467
468 assert_eq!(outcome, AccountRangeOutcome::Unavailable { peer_id: peer });
469 assert!(client.reported().is_empty());
470 }
471
472 #[test]
473 fn code_hashes_are_listed_once_in_the_order_the_accounts_reference_them() {
474 let shared = B256::repeat_byte(0x11);
475 let mut first = account(1);
476 first.code_hash = shared;
477 let mut third = account(3);
478 third.code_hash = shared;
479 let mut fourth = account(4);
480 fourth.code_hash = B256::repeat_byte(0x44);
481 let accounts =
482 vec![(key(1), first), (key(2), account(2)), (key(3), third), (key(4), fourth)];
483 let range = VerifiedAccountRange {
484 state_root: root(&accounts),
485 origin: B256::ZERO,
486 accounts,
487 has_more: false,
488 next: None,
489 };
490
491 assert_eq!(range.code_hashes(), vec![shared, B256::repeat_byte(0x44)]);
492 }
493
494 #[test]
495 fn storage_batch_omits_interleaved_accounts_without_storage() {
496 let mut first = account(1);
497 first.storage_root = B256::repeat_byte(0x11);
498 let empty = account(2);
499 let mut third = account(3);
500 third.storage_root = B256::repeat_byte(0x33);
501 let accounts = vec![(key(1), first), (key(2), empty), (key(3), third)];
502 let root_hash = root(&accounts);
503 let range = VerifiedAccountRange {
504 state_root: root_hash,
505 origin: B256::ZERO,
506 accounts,
507 has_more: false,
508 next: None,
509 };
510
511 let batch = range.storage_batch();
512
513 assert_eq!(batch.state_root(), root_hash);
514 assert_eq!(
515 batch.accounts().iter().map(|(hash, _)| *hash).collect::<Vec<_>>(),
516 vec![key(1), key(3)]
517 );
518
519 let chunk = batch.range(1..2).expect("chunk is inside the batch");
520 assert_eq!(
521 chunk.accounts().iter().map(|(hash, _)| *hash).collect::<Vec<_>>(),
522 vec![key(3)]
523 );
524 assert_eq!(chunk.state_root(), root_hash);
525
526 assert_eq!(batch.range(1..3), None);
528 }
529
530 #[test]
531 fn invalid_request_range_is_rejected_before_submission() {
532 let client = Arc::new(TestSnapClient::new(std::iter::empty()));
533 let mut request = request(B256::repeat_byte(0x11));
534 request.starting_hash = key(2);
535 request.limit_hash = key(1);
536
537 assert!(matches!(
538 downloader(Arc::clone(&client), request),
539 Err(InvalidAccountRange { .. })
540 ));
541 assert!(client.priorities().is_empty());
542 }
543
544 #[tokio::test]
545 async fn authenticates_then_trims_an_account_past_the_limit() {
546 let accounts = vec![(key(1), account(7)), (key(3), account(8)), (key(4), account(9))];
547 let (root_hash, proof) = root_and_proof(&accounts, &[key(1), key(3)]);
548 let peer = PeerId::random();
549 let message = AccountRangeMessage {
550 request_id: 1,
551 accounts: accounts[..2]
552 .iter()
553 .map(|(key, account)| AccountData::from_trie_account(*key, account))
554 .collect(),
555 proof,
556 };
557 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
558 let mut request = request(root_hash);
559 request.limit_hash = key(2);
560
561 let outcome = downloader(Arc::clone(&client), request).unwrap().await.unwrap();
562
563 assert_eq!(
564 outcome,
565 AccountRangeOutcome::Verified(VerifiedAccountRange {
566 state_root: root_hash,
567 origin: B256::ZERO,
568 accounts: vec![accounts[0].clone()],
569 has_more: false,
570 next: Some(key(3)),
571 })
572 );
573 assert!(client.reported().is_empty());
574 }
575
576 #[tokio::test]
577 async fn range_running_past_the_limit_is_rejected() {
578 let accounts = vec![(key(1), account(7)), (key(3), account(8)), (key(4), account(9))];
579 let (root_hash, proof) = root_and_proof(&accounts, &[key(1), key(4)]);
580 let peer = PeerId::random();
581 let message = AccountRangeMessage {
582 request_id: 1,
583 accounts: accounts
584 .iter()
585 .map(|(key, account)| AccountData::from_trie_account(*key, account))
586 .collect(),
587 proof,
588 };
589 let attempts = usize::from(MAX_RETRIES) + 1;
590 let client = Arc::new(TestSnapClient::new(
591 std::iter::repeat_with(|| response(peer, message.clone())).take(attempts),
592 ));
593 let mut request = request(root_hash);
594 request.limit_hash = key(2);
595
596 let error = downloader(Arc::clone(&client), request).unwrap().await.unwrap_err();
597
598 assert_eq!(error, RequestError::BadResponse);
599 assert_eq!(client.reported().len(), attempts);
600 }
601
602 #[tokio::test]
603 async fn account_at_the_limit_completes_the_requested_interval() {
604 let accounts = vec![(key(1), account(7)), (key(2), account(8)), (key(3), account(9))];
605 let (root_hash, proof) = root_and_proof(&accounts, &[key(1), key(2)]);
606 let peer = PeerId::random();
607 let message = AccountRangeMessage {
608 request_id: 1,
609 accounts: accounts[..2]
610 .iter()
611 .map(|(key, account)| AccountData::from_trie_account(*key, account))
612 .collect(),
613 proof,
614 };
615 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
616 let mut request = request(root_hash);
617 request.limit_hash = key(2);
618
619 let outcome = downloader(Arc::clone(&client), request).unwrap().await.unwrap();
620
621 assert_eq!(
622 outcome,
623 AccountRangeOutcome::Verified(VerifiedAccountRange {
624 state_root: root_hash,
625 origin: B256::ZERO,
626 accounts: accounts[..2].to_vec(),
627 has_more: false,
628 next: Some(key(3)),
629 })
630 );
631 assert!(client.reported().is_empty());
632 }
633
634 #[tokio::test]
636 async fn empty_interval_is_proven_by_the_first_account_after_the_limit() {
637 let accounts = vec![(key(1), account(7)), (key(9), account(8))];
638 let (root_hash, proof) = root_and_proof(&accounts, &[key(3), key(9)]);
639 let peer = PeerId::random();
640 let message = AccountRangeMessage {
641 request_id: 1,
642 accounts: vec![AccountData::from_trie_account(accounts[1].0, &accounts[1].1)],
643 proof,
644 };
645 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
646 let mut request = request(root_hash);
647 request.starting_hash = key(3);
648 request.limit_hash = key(5);
649
650 let outcome = downloader(Arc::clone(&client), request).unwrap().await.unwrap();
651
652 assert_eq!(
653 outcome,
654 AccountRangeOutcome::Verified(VerifiedAccountRange {
655 state_root: root_hash,
656 origin: key(3),
657 accounts: Vec::new(),
658 has_more: false,
659 next: Some(key(9)),
660 })
661 );
662 assert!(client.reported().is_empty());
663 }
664
665 #[tokio::test]
666 async fn empty_interval_is_proven_without_a_boundary_account() {
667 let accounts = vec![(key(1), account(7)), (key(9), account(8))];
668 let (root_hash, proof) = root_and_proof(&accounts, &[key(3), key(5)]);
669 let peer = PeerId::random();
670 let message = AccountRangeMessage { request_id: 1, accounts: Vec::new(), proof };
671 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
672 let mut request = request(root_hash);
673 request.starting_hash = key(3);
674 request.limit_hash = key(5);
675
676 let outcome = downloader(Arc::clone(&client), request).unwrap().await.unwrap();
677
678 assert_eq!(
679 outcome,
680 AccountRangeOutcome::Verified(VerifiedAccountRange {
681 state_root: root_hash,
682 origin: key(3),
683 accounts: Vec::new(),
684 has_more: false,
685 next: Some(key(9)),
686 })
687 );
688 assert!(client.reported().is_empty());
689 }
690
691 #[tokio::test]
693 async fn range_ending_before_the_limit_needs_no_further_request() {
694 let accounts = vec![(key(1), account(7)), (key(9), account(8))];
695 let (root_hash, proof) = root_and_proof(&accounts, &[key(1)]);
696 let peer = PeerId::random();
697 let message = AccountRangeMessage {
698 request_id: 1,
699 accounts: vec![AccountData::from_trie_account(accounts[0].0, &accounts[0].1)],
700 proof,
701 };
702 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
703 let mut request = request(root_hash);
704 request.limit_hash = key(5);
705
706 let outcome = downloader(Arc::clone(&client), request).unwrap().await.unwrap();
707
708 assert_eq!(
709 outcome,
710 AccountRangeOutcome::Verified(VerifiedAccountRange {
711 state_root: root_hash,
712 origin: B256::ZERO,
713 accounts: vec![accounts[0].clone()],
714 has_more: false,
715 next: Some(key(9)),
716 })
717 );
718 }
719
720 #[tokio::test]
721 async fn range_ending_before_a_covered_key_reports_more() {
722 let accounts = vec![(key(1), account(7)), (key(3), account(8))];
723 let (root_hash, proof) = root_and_proof(&accounts, &[key(1)]);
724 let peer = PeerId::random();
725 let message = AccountRangeMessage {
726 request_id: 1,
727 accounts: vec![AccountData::from_trie_account(accounts[0].0, &accounts[0].1)],
728 proof,
729 };
730 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
731 let mut request = request(root_hash);
732 request.limit_hash = key(5);
733
734 let outcome = downloader(Arc::clone(&client), request).unwrap().await.unwrap();
735
736 assert_eq!(
737 outcome,
738 AccountRangeOutcome::Verified(VerifiedAccountRange {
739 state_root: root_hash,
740 origin: B256::ZERO,
741 accounts: vec![accounts[0].clone()],
742 has_more: true,
743 next: Some(key(3)),
744 })
745 );
746 }
747
748 #[tokio::test]
749 async fn request_errors_retry_without_duplicate_peer_penalties() {
750 let accounts = vec![(key(1), account(7))];
751 let root_hash = root(&accounts);
752 let peer = PeerId::random();
753 let good = response(
754 peer,
755 AccountRangeMessage {
756 request_id: 1,
757 accounts: vec![AccountData::from_trie_account(accounts[0].0, &accounts[0].1)],
758 proof: Vec::new(),
759 },
760 );
761 let client = Arc::new(TestSnapClient::new([
762 Err(RequestError::Timeout),
763 Err(RequestError::BadResponse),
764 good,
765 ]));
766
767 downloader(Arc::clone(&client), request(root_hash)).unwrap().await.unwrap();
768
769 assert!(client.reported().is_empty());
770 assert_eq!(*client.priorities(), [Priority::Normal, Priority::High, Priority::High]);
771 }
772
773 #[tokio::test]
774 async fn stops_after_the_retry_budget_is_exhausted() {
775 let peers = [PeerId::random(), PeerId::random(), PeerId::random()];
776 let responses = peers.map(|peer| {
777 Ok(WithPeerId::new(
778 peer,
779 SnapResponse::ByteCodes(ByteCodesMessage { request_id: 1, codes: Vec::new() }),
780 ))
781 });
782 let client = Arc::new(TestSnapClient::new(responses));
783
784 let error = downloader(Arc::clone(&client), request(B256::repeat_byte(0x11)))
785 .unwrap()
786 .await
787 .unwrap_err();
788
789 assert_eq!(error, RequestError::BadResponse);
790 assert_eq!(*client.reported(), peers);
791 assert_eq!(*client.priorities(), [Priority::Normal, Priority::High, Priority::High]);
792 }
793}