1use alloy_primitives::B256;
7use futures::Future;
8use reth_eth_wire_types::snap::{
9 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 accounts: Vec<(B256, TrieAccount)>,
94 has_more: bool,
96 next: Option<B256>,
98}
99
100impl VerifiedAccountRange {
101 pub const fn state_root(&self) -> B256 {
103 self.state_root
104 }
105
106 pub fn accounts(&self) -> &[(B256, TrieAccount)] {
108 &self.accounts
109 }
110
111 pub const fn has_more(&self) -> bool {
115 self.has_more
116 }
117
118 pub const fn next(&self) -> Option<B256> {
121 self.next
122 }
123
124 pub fn batch(&self) -> VerifiedAccountBatch<'_> {
126 VerifiedAccountBatch {
127 state_root: self.state_root,
128 accounts: self.accounts.iter().map(|(hash, account)| (*hash, account)).collect(),
129 }
130 }
131
132 pub fn storage_batch(&self) -> VerifiedAccountBatch<'_> {
138 VerifiedAccountBatch {
139 state_root: self.state_root,
140 accounts: self
141 .accounts
142 .iter()
143 .filter(|(_, account)| account.storage_root != EMPTY_ROOT_HASH)
144 .map(|(hash, account)| (*hash, account))
145 .collect(),
146 }
147 }
148}
149
150#[derive(Clone, Debug, PartialEq, Eq)]
155pub struct VerifiedAccountBatch<'a> {
156 state_root: B256,
158 accounts: Vec<(B256, &'a TrieAccount)>,
160}
161
162impl<'a> VerifiedAccountBatch<'a> {
163 pub const fn state_root(&self) -> B256 {
165 self.state_root
166 }
167
168 pub fn accounts(&self) -> &[(B256, &'a TrieAccount)] {
170 &self.accounts
171 }
172
173 pub fn range(&self, range: Range<usize>) -> Option<Self> {
178 self.accounts
179 .get(range)
180 .map(|accounts| Self { state_root: self.state_root, accounts: accounts.to_vec() })
181 }
182
183 pub(super) fn verify_batch(
186 &self,
187 request: &GetStorageRangesMessage,
188 ) -> Result<(), InvalidStorageRangeRequest> {
189 if request.root_hash != self.state_root {
190 return Err(InvalidStorageRangeRequest::StateRootMismatch {
191 requested: request.root_hash,
192 authenticated: self.state_root,
193 })
194 }
195 if request.account_hashes.len() != self.accounts.len() {
196 return Err(InvalidStorageRangeRequest::AccountCount {
197 requested: request.account_hashes.len(),
198 supplied: self.accounts.len(),
199 })
200 }
201 for (index, (requested, (supplied, _))) in
202 request.account_hashes.iter().zip(&self.accounts).enumerate()
203 {
204 if requested != supplied {
205 return Err(InvalidStorageRangeRequest::AccountMismatch {
206 index,
207 requested: *requested,
208 supplied: *supplied,
209 })
210 }
211 }
212 Ok(())
213 }
214
215 pub(super) fn slice(mut self, from: usize) -> Option<Self> {
217 (from <= self.accounts.len()).then(|| {
218 self.accounts.drain(..from);
219 self
220 })
221 }
222}
223
224#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
226#[error("account range origin {origin} exceeds limit {limit}")]
227pub struct InvalidAccountRange {
228 pub origin: B256,
230 pub limit: B256,
232}
233
234impl SnapVerifier for GetAccountRangeMessage {
236 type Request = Self;
237 type Output = AccountRangeOutcome;
238
239 fn verify(self, peer_id: PeerId, response: SnapResponse) -> Result<Self::Output, RequestError> {
240 let SnapResponse::AccountRange(response) = response else {
241 debug!(target: "downloaders::snap", "Expected account range response");
242 return Err(RequestError::BadResponse)
243 };
244 if response.request_id != self.request_id {
245 debug!(
246 target: "downloaders::snap",
247 expected = self.request_id,
248 got = response.request_id,
249 "Account range response id mismatch"
250 );
251 return Err(RequestError::BadResponse)
252 }
253 if response.accounts.is_empty() && response.proof.is_empty() {
254 return if self.root_hash == EMPTY_ROOT_HASH {
255 Ok(AccountRangeOutcome::Verified(VerifiedAccountRange {
256 state_root: self.root_hash,
257 accounts: Vec::new(),
258 has_more: false,
259 next: None,
260 }))
261 } else {
262 Ok(AccountRangeOutcome::Unavailable { peer_id })
263 }
264 }
265
266 verify_account_range(&self, response).map(AccountRangeOutcome::Verified)
267 }
268}
269
270fn verify_account_range(
272 request: &GetAccountRangeMessage,
273 response: AccountRangeMessage,
274) -> Result<VerifiedAccountRange, RequestError> {
275 if response.accounts.iter().filter(|data| data.hash > request.limit_hash).nth(1).is_some() {
277 debug!(target: "downloaders::snap", "Account range runs past the requested limit");
278 return Err(RequestError::BadResponse)
279 }
280
281 let mut accounts = response
283 .accounts
284 .into_iter()
285 .map(|data| {
286 data.into_trie_entry().map_err(|error| {
287 debug!(target: "downloaders::snap", %error, "Invalid account data");
288 RequestError::BadResponse
289 })
290 })
291 .collect::<Result<Vec<_>, _>>()?;
292 let next = verify_proof(request, &accounts, &response.proof)?;
293
294 accounts.truncate(accounts.partition_point(|(hash, _)| *hash <= request.limit_hash));
296 let has_more = next.is_some_and(|next| next <= request.limit_hash);
297
298 Ok(VerifiedAccountRange { state_root: request.root_hash, accounts, has_more, next })
299}
300
301fn verify_proof(
303 request: &GetAccountRangeMessage,
304 accounts: &[(B256, TrieAccount)],
305 proof: &[alloy_primitives::Bytes],
306) -> Result<Option<B256>, RequestError> {
307 let leaves = accounts.iter().map(|(hash, account)| (*hash, alloy_rlp::encode(account)));
308 verify_range_proof(request.root_hash, request.starting_hash, request.limit_hash, leaves, proof)
309 .map_err(|error| {
310 debug!(target: "downloaders::snap", %error, "Invalid account range proof");
311 RequestError::BadResponse
312 })
313}
314
315#[cfg(test)]
316mod tests {
317 use super::{request::MAX_RETRIES, test_utils::TestSnapClient, *};
318 use alloy_primitives::{Bytes, KECCAK256_EMPTY, U256};
319 use reth_eth_wire_types::snap::{AccountData, ByteCodesMessage};
320 use reth_network_p2p::{error::PeerRequestResult, priority::Priority};
321 use reth_network_peers::WithPeerId;
322 use reth_trie_common::{proof::ProofRetainer, HashBuilder, Nibbles};
323 use std::sync::Arc;
324
325 const MAX_HASH: B256 = B256::new([0xff; B256::len_bytes()]);
326
327 fn key(value: u64) -> B256 {
328 B256::left_padding_from(&value.to_be_bytes())
329 }
330
331 fn account(nonce: u64) -> TrieAccount {
332 TrieAccount {
333 nonce,
334 balance: U256::from(1),
335 storage_root: EMPTY_ROOT_HASH,
336 code_hash: KECCAK256_EMPTY,
337 }
338 }
339
340 fn root(accounts: &[(B256, TrieAccount)]) -> B256 {
341 let mut builder = HashBuilder::default();
342 for (key, account) in accounts {
343 builder.add_leaf(Nibbles::unpack(*key), &alloy_rlp::encode(account));
344 }
345 builder.root()
346 }
347
348 fn root_and_proof(accounts: &[(B256, TrieAccount)], targets: &[B256]) -> (B256, Vec<Bytes>) {
349 let targets = targets.iter().copied().map(Nibbles::unpack).collect();
350 let mut builder = HashBuilder::default().with_proof_retainer(ProofRetainer::new(targets));
351 for (key, account) in accounts {
352 builder.add_leaf(Nibbles::unpack(*key), &alloy_rlp::encode(account));
353 }
354 let root = builder.root();
355 let proof = builder
356 .take_proof_nodes()
357 .into_nodes_sorted()
358 .into_iter()
359 .map(|(_, node)| node)
360 .collect();
361 (root, proof)
362 }
363
364 fn request(root_hash: B256) -> GetAccountRangeMessage {
365 GetAccountRangeMessage {
366 request_id: 1,
367 root_hash,
368 starting_hash: B256::ZERO,
369 limit_hash: MAX_HASH,
370 response_bytes: 512 * 1024,
371 }
372 }
373
374 fn response(peer: PeerId, message: AccountRangeMessage) -> PeerRequestResult<SnapResponse> {
375 Ok(WithPeerId::new(peer, SnapResponse::AccountRange(message)))
376 }
377
378 fn downloader(
379 client: Arc<TestSnapClient>,
380 request: GetAccountRangeMessage,
381 ) -> Result<AccountRangeDownloader<Arc<TestSnapClient>>, InvalidAccountRange> {
382 AccountRangeDownloader::new(client, request, Runtime::test())
383 }
384
385 #[test]
386 fn verifies_and_decodes_without_an_ambient_runtime() {
387 let accounts = vec![(key(1), account(7)), (key(2), account(8))];
388 let root_hash = root(&accounts);
389 let peer = PeerId::random();
390 let message = AccountRangeMessage {
391 request_id: 1,
392 accounts: accounts
393 .iter()
394 .map(|(key, account)| AccountData::from_trie_account(*key, account))
395 .collect(),
396 proof: Vec::new(),
397 };
398 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
399
400 let downloader = downloader(Arc::clone(&client), request(root_hash)).unwrap();
401 let outcome = futures::executor::block_on(downloader).unwrap();
402
403 assert_eq!(
404 outcome,
405 AccountRangeOutcome::Verified(VerifiedAccountRange {
406 state_root: root_hash,
407 accounts,
408 has_more: false,
409 next: None,
410 })
411 );
412 assert!(client.reported().is_empty());
413 assert_eq!(*client.priorities(), [Priority::Normal]);
414 }
415
416 #[tokio::test]
417 async fn invalid_peer_is_reported_and_request_is_retried_at_high_priority() {
418 let accounts = vec![(key(1), account(7))];
419 let root_hash = root(&accounts);
420 let bad_peer = PeerId::random();
421 let good_peer = PeerId::random();
422 let bad = Ok(WithPeerId::new(
423 bad_peer,
424 SnapResponse::ByteCodes(ByteCodesMessage { request_id: 1, codes: Vec::new() }),
425 ));
426 let good = response(
427 good_peer,
428 AccountRangeMessage {
429 request_id: 1,
430 accounts: vec![AccountData::from_trie_account(accounts[0].0, &accounts[0].1)],
431 proof: Vec::new(),
432 },
433 );
434 let client = Arc::new(TestSnapClient::new([bad, good]));
435
436 let outcome = downloader(Arc::clone(&client), request(root_hash)).unwrap().await.unwrap();
437
438 assert!(matches!(outcome, AccountRangeOutcome::Verified(_)));
439 assert_eq!(*client.reported(), [bad_peer]);
440 assert_eq!(*client.priorities(), [Priority::Normal, Priority::High]);
441 }
442
443 #[tokio::test]
444 async fn unavailable_state_is_not_a_bad_peer_response() {
445 let peer = PeerId::random();
446 let message =
447 AccountRangeMessage { request_id: 1, accounts: Vec::new(), proof: Vec::new() };
448 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
449
450 let outcome = downloader(Arc::clone(&client), request(B256::repeat_byte(0x11)))
451 .unwrap()
452 .await
453 .unwrap();
454
455 assert_eq!(outcome, AccountRangeOutcome::Unavailable { peer_id: peer });
456 assert!(client.reported().is_empty());
457 }
458
459 #[test]
460 fn a_subrange_narrows_the_accounts_and_keeps_their_root() {
461 let accounts = vec![(key(1), account(7)), (key(2), account(8)), (key(3), account(9))];
462 let root_hash = root(&accounts);
463 let range = VerifiedAccountRange {
464 state_root: root_hash,
465 accounts: accounts.clone(),
466 has_more: false,
467 next: None,
468 };
469
470 let batch = range.batch();
471 let chunk = batch.range(1..3).expect("chunk is inside the batch");
472 let expected =
473 accounts[1..3].iter().map(|(hash, account)| (*hash, account)).collect::<Vec<_>>();
474 assert_eq!(chunk.accounts(), expected);
475 assert_eq!(chunk.state_root(), root_hash);
476
477 assert_eq!(batch.range(2..4), None);
478 }
479
480 #[test]
481 fn storage_batch_omits_interleaved_accounts_without_storage() {
482 let mut first = account(1);
483 first.storage_root = B256::repeat_byte(0x11);
484 let empty = account(2);
485 let mut third = account(3);
486 third.storage_root = B256::repeat_byte(0x33);
487 let accounts = vec![(key(1), first), (key(2), empty), (key(3), third)];
488 let root_hash = root(&accounts);
489 let range =
490 VerifiedAccountRange { state_root: root_hash, accounts, has_more: false, next: None };
491
492 let batch = range.storage_batch();
493
494 assert_eq!(batch.state_root(), root_hash);
495 assert_eq!(
496 batch.accounts().iter().map(|(hash, _)| *hash).collect::<Vec<_>>(),
497 vec![key(1), key(3)]
498 );
499
500 let chunk = batch.range(1..2).expect("chunk is inside the batch");
501 assert_eq!(
502 chunk.accounts().iter().map(|(hash, _)| *hash).collect::<Vec<_>>(),
503 vec![key(3)]
504 );
505 assert_eq!(chunk.state_root(), root_hash);
506 }
507
508 #[test]
509 fn invalid_request_range_is_rejected_before_submission() {
510 let client = Arc::new(TestSnapClient::new(std::iter::empty()));
511 let mut request = request(B256::repeat_byte(0x11));
512 request.starting_hash = key(2);
513 request.limit_hash = key(1);
514
515 assert!(matches!(
516 downloader(Arc::clone(&client), request),
517 Err(InvalidAccountRange { .. })
518 ));
519 assert!(client.priorities().is_empty());
520 }
521
522 #[tokio::test]
523 async fn authenticates_then_trims_an_account_past_the_limit() {
524 let accounts = vec![(key(1), account(7)), (key(3), account(8)), (key(4), account(9))];
525 let (root_hash, proof) = root_and_proof(&accounts, &[key(1), key(3)]);
526 let peer = PeerId::random();
527 let message = AccountRangeMessage {
528 request_id: 1,
529 accounts: accounts[..2]
530 .iter()
531 .map(|(key, account)| AccountData::from_trie_account(*key, account))
532 .collect(),
533 proof,
534 };
535 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
536 let mut request = request(root_hash);
537 request.limit_hash = key(2);
538
539 let outcome = downloader(Arc::clone(&client), request).unwrap().await.unwrap();
540
541 assert_eq!(
542 outcome,
543 AccountRangeOutcome::Verified(VerifiedAccountRange {
544 state_root: root_hash,
545 accounts: vec![accounts[0]],
546 has_more: false,
547 next: Some(key(4)),
548 })
549 );
550 assert!(client.reported().is_empty());
551 }
552
553 #[tokio::test]
554 async fn range_running_past_the_limit_is_rejected() {
555 let accounts = vec![(key(1), account(7)), (key(3), account(8)), (key(4), account(9))];
556 let (root_hash, proof) = root_and_proof(&accounts, &[key(1), key(4)]);
557 let peer = PeerId::random();
558 let message = AccountRangeMessage {
559 request_id: 1,
560 accounts: accounts
561 .iter()
562 .map(|(key, account)| AccountData::from_trie_account(*key, account))
563 .collect(),
564 proof,
565 };
566 let attempts = usize::from(MAX_RETRIES) + 1;
567 let client = Arc::new(TestSnapClient::new(
568 std::iter::repeat_with(|| response(peer, message.clone())).take(attempts),
569 ));
570 let mut request = request(root_hash);
571 request.limit_hash = key(2);
572
573 let error = downloader(Arc::clone(&client), request).unwrap().await.unwrap_err();
574
575 assert_eq!(error, RequestError::BadResponse);
576 assert_eq!(client.reported().len(), attempts);
577 }
578
579 #[tokio::test]
580 async fn account_at_the_limit_completes_the_requested_interval() {
581 let accounts = vec![(key(1), account(7)), (key(2), account(8)), (key(3), account(9))];
582 let (root_hash, proof) = root_and_proof(&accounts, &[key(1), key(2)]);
583 let peer = PeerId::random();
584 let message = AccountRangeMessage {
585 request_id: 1,
586 accounts: accounts[..2]
587 .iter()
588 .map(|(key, account)| AccountData::from_trie_account(*key, account))
589 .collect(),
590 proof,
591 };
592 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
593 let mut request = request(root_hash);
594 request.limit_hash = key(2);
595
596 let outcome = downloader(Arc::clone(&client), request).unwrap().await.unwrap();
597
598 assert_eq!(
599 outcome,
600 AccountRangeOutcome::Verified(VerifiedAccountRange {
601 state_root: root_hash,
602 accounts: accounts[..2].to_vec(),
603 has_more: false,
604 next: Some(key(3)),
605 })
606 );
607 assert!(client.reported().is_empty());
608 }
609
610 #[tokio::test]
612 async fn empty_interval_is_proven_by_the_first_account_after_the_limit() {
613 let accounts = vec![(key(1), account(7)), (key(9), account(8))];
614 let (root_hash, proof) = root_and_proof(&accounts, &[key(3), key(9)]);
615 let peer = PeerId::random();
616 let message = AccountRangeMessage {
617 request_id: 1,
618 accounts: vec![AccountData::from_trie_account(accounts[1].0, &accounts[1].1)],
619 proof,
620 };
621 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
622 let mut request = request(root_hash);
623 request.starting_hash = key(3);
624 request.limit_hash = key(5);
625
626 let outcome = downloader(Arc::clone(&client), request).unwrap().await.unwrap();
627
628 assert_eq!(
629 outcome,
630 AccountRangeOutcome::Verified(VerifiedAccountRange {
631 state_root: root_hash,
632 accounts: Vec::new(),
633 has_more: false,
634 next: None,
635 })
636 );
637 assert!(client.reported().is_empty());
638 }
639
640 #[tokio::test]
641 async fn empty_interval_is_proven_without_a_boundary_account() {
642 let accounts = vec![(key(1), account(7)), (key(9), account(8))];
643 let (root_hash, proof) = root_and_proof(&accounts, &[key(3), key(5)]);
644 let peer = PeerId::random();
645 let message = AccountRangeMessage { request_id: 1, accounts: Vec::new(), proof };
646 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
647 let mut request = request(root_hash);
648 request.starting_hash = key(3);
649 request.limit_hash = key(5);
650
651 let outcome = downloader(Arc::clone(&client), request).unwrap().await.unwrap();
652
653 assert_eq!(
654 outcome,
655 AccountRangeOutcome::Verified(VerifiedAccountRange {
656 state_root: root_hash,
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]
667 async fn range_ending_before_the_limit_needs_no_further_request() {
668 let accounts = vec![(key(1), account(7)), (key(9), account(8))];
669 let (root_hash, proof) = root_and_proof(&accounts, &[key(1)]);
670 let peer = PeerId::random();
671 let message = AccountRangeMessage {
672 request_id: 1,
673 accounts: vec![AccountData::from_trie_account(accounts[0].0, &accounts[0].1)],
674 proof,
675 };
676 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
677 let mut request = request(root_hash);
678 request.limit_hash = key(5);
679
680 let outcome = downloader(Arc::clone(&client), request).unwrap().await.unwrap();
681
682 assert_eq!(
683 outcome,
684 AccountRangeOutcome::Verified(VerifiedAccountRange {
685 state_root: root_hash,
686 accounts: vec![accounts[0]],
687 has_more: false,
688 next: Some(key(9)),
689 })
690 );
691 }
692
693 #[tokio::test]
694 async fn range_ending_before_a_covered_key_reports_more() {
695 let accounts = vec![(key(1), account(7)), (key(3), account(8))];
696 let (root_hash, proof) = root_and_proof(&accounts, &[key(1)]);
697 let peer = PeerId::random();
698 let message = AccountRangeMessage {
699 request_id: 1,
700 accounts: vec![AccountData::from_trie_account(accounts[0].0, &accounts[0].1)],
701 proof,
702 };
703 let client = Arc::new(TestSnapClient::new([response(peer, message)]));
704 let mut request = request(root_hash);
705 request.limit_hash = key(5);
706
707 let outcome = downloader(Arc::clone(&client), request).unwrap().await.unwrap();
708
709 assert_eq!(
710 outcome,
711 AccountRangeOutcome::Verified(VerifiedAccountRange {
712 state_root: root_hash,
713 accounts: vec![accounts[0]],
714 has_more: true,
715 next: Some(key(3)),
716 })
717 );
718 }
719
720 #[tokio::test]
721 async fn request_errors_retry_without_duplicate_peer_penalties() {
722 let accounts = vec![(key(1), account(7))];
723 let root_hash = root(&accounts);
724 let peer = PeerId::random();
725 let good = response(
726 peer,
727 AccountRangeMessage {
728 request_id: 1,
729 accounts: vec![AccountData::from_trie_account(accounts[0].0, &accounts[0].1)],
730 proof: Vec::new(),
731 },
732 );
733 let client = Arc::new(TestSnapClient::new([
734 Err(RequestError::Timeout),
735 Err(RequestError::BadResponse),
736 good,
737 ]));
738
739 downloader(Arc::clone(&client), request(root_hash)).unwrap().await.unwrap();
740
741 assert!(client.reported().is_empty());
742 assert_eq!(*client.priorities(), [Priority::Normal, Priority::High, Priority::High]);
743 }
744
745 #[tokio::test]
746 async fn stops_after_the_retry_budget_is_exhausted() {
747 let peers = [PeerId::random(), PeerId::random(), PeerId::random()];
748 let responses = peers.map(|peer| {
749 Ok(WithPeerId::new(
750 peer,
751 SnapResponse::ByteCodes(ByteCodesMessage { request_id: 1, codes: Vec::new() }),
752 ))
753 });
754 let client = Arc::new(TestSnapClient::new(responses));
755
756 let error = downloader(Arc::clone(&client), request(B256::repeat_byte(0x11)))
757 .unwrap()
758 .await
759 .unwrap_err();
760
761 assert_eq!(error, RequestError::BadResponse);
762 assert_eq!(*client.reported(), peers);
763 assert_eq!(*client.priorities(), [Priority::Normal, Priority::High, Priority::High]);
764 }
765}