1mod client;
4
5pub use client::FetchClient;
6
7use crate::{message::BlockRequest, session::BlockRangeInfo};
8use alloy_primitives::B256;
9use futures::StreamExt;
10use reth_eth_wire::{
11 snap::SnapProtocolMessage, BlockAccessLists, Capabilities, EthNetworkPrimitives, EthVersion,
12 GetBlockAccessLists, GetBlockBodies, GetBlockHeaders, GetReceipts, NetworkPrimitives,
13};
14use reth_network_api::test_utils::PeersHandle;
15use reth_network_p2p::{
16 block_access_lists::client::BalRequirement,
17 error::{EthResponseValidator, PeerRequestResult, RequestError, RequestResult},
18 headers::client::HeadersRequest,
19 priority::Priority,
20 receipts::client::ReceiptsResponse,
21 snap::client::SnapResponse,
22};
23use reth_network_peers::PeerId;
24use reth_network_types::ReputationChangeKind;
25use std::{
26 collections::{HashMap, VecDeque},
27 ops::RangeInclusive,
28 sync::{
29 atomic::{AtomicU64, AtomicUsize, Ordering},
30 Arc,
31 },
32 task::{Context, Poll},
33};
34use tokio::sync::{mpsc, mpsc::UnboundedSender, oneshot};
35use tokio_stream::wrappers::UnboundedReceiverStream;
36
37type InflightHeadersRequest<H> = Request<HeadersRequest, PeerRequestResult<Vec<H>>>;
38type InflightBodiesRequest<B> = Request<(), PeerRequestResult<Vec<B>>>;
39type InflightReceiptsRequest<R> = Request<(), PeerRequestResult<ReceiptsResponse<R>>>;
40type InflightBlockAccessListsRequest = Request<(), PeerRequestResult<BlockAccessLists>>;
41type InflightSnapRequest = Request<(), PeerRequestResult<SnapResponse>>;
42
43#[derive(Debug)]
50pub struct StateFetcher<N: NetworkPrimitives = EthNetworkPrimitives> {
51 inflight_headers_requests: HashMap<PeerId, InflightHeadersRequest<N::BlockHeader>>,
53 inflight_bodies_requests: HashMap<PeerId, InflightBodiesRequest<N::BlockBody>>,
55 inflight_bals_requests: HashMap<PeerId, InflightBlockAccessListsRequest>,
57 inflight_receipts_requests: HashMap<PeerId, InflightReceiptsRequest<N::Receipt>>,
59 inflight_snap_requests: HashMap<PeerId, InflightSnapRequest>,
61 peers: HashMap<PeerId, Peer>,
63 peers_handle: PeersHandle,
65 num_active_peers: Arc<AtomicUsize>,
67 queued_requests: VecDeque<DownloadRequest<N>>,
69 download_requests_rx: UnboundedReceiverStream<DownloadRequest<N>>,
71 download_requests_tx: UnboundedSender<DownloadRequest<N>>,
73}
74
75impl<N: NetworkPrimitives> StateFetcher<N> {
78 pub(crate) fn new(peers_handle: PeersHandle, num_active_peers: Arc<AtomicUsize>) -> Self {
79 let (download_requests_tx, download_requests_rx) = mpsc::unbounded_channel();
80 Self {
81 inflight_headers_requests: Default::default(),
82 inflight_bodies_requests: Default::default(),
83 inflight_bals_requests: Default::default(),
84 inflight_receipts_requests: Default::default(),
85 inflight_snap_requests: Default::default(),
86 peers: Default::default(),
87 peers_handle,
88 num_active_peers,
89 queued_requests: Default::default(),
90 download_requests_rx: UnboundedReceiverStream::new(download_requests_rx),
91 download_requests_tx,
92 }
93 }
94
95 pub(crate) fn new_active_peer(&mut self, peer: NewPeerInfo) {
97 let NewPeerInfo {
98 peer_id,
99 best_hash,
100 best_number,
101 capabilities,
102 timeout,
103 range_info,
104 supports_snap,
105 } = peer;
106 self.peers.insert(
107 peer_id,
108 Peer {
109 state: PeerState::Idle,
110 best_hash,
111 best_number,
112 capabilities,
113 timeout,
114 last_response_likely_bad: false,
115 range_info,
116 supports_snap,
117 },
118 );
119 }
120
121 pub(crate) fn on_session_closed(&mut self, peer: &PeerId) {
128 self.peers.remove(peer);
129 if let Some(req) = self.inflight_headers_requests.remove(peer) {
130 let _ = req.response.send(Err(RequestError::ConnectionDropped));
131 }
132 if let Some(req) = self.inflight_bodies_requests.remove(peer) {
133 let _ = req.response.send(Err(RequestError::ConnectionDropped));
134 }
135 if let Some(req) = self.inflight_bals_requests.remove(peer) {
136 let _ = req.response.send(Err(RequestError::ConnectionDropped));
137 }
138 if let Some(req) = self.inflight_receipts_requests.remove(peer) {
139 let _ = req.response.send(Err(RequestError::ConnectionDropped));
140 }
141 if let Some(req) = self.inflight_snap_requests.remove(peer) {
142 let _ = req.response.send(Err(RequestError::ConnectionDropped));
143 }
144 }
145
146 pub(crate) fn update_peer_block(&mut self, peer_id: &PeerId, hash: B256, number: u64) -> bool {
150 if let Some(peer) = self.peers.get_mut(peer_id) &&
151 number > peer.best_number
152 {
153 peer.best_hash = hash;
154 peer.best_number = number;
155 return true
156 }
157 false
158 }
159
160 pub(crate) fn on_pending_disconnect(&mut self, peer_id: &PeerId) {
162 if let Some(peer) = self.peers.get_mut(peer_id) {
163 peer.state = PeerState::Closing;
164 }
165 }
166
167 fn next_best_peer(&self, requirement: BestPeerRequirements) -> Option<PeerId> {
172 let mut idle = self
174 .peers
175 .iter()
176 .filter(|(_, peer)| peer.state.is_idle() && peer.satisfies(&requirement));
177
178 let mut best_peer = idle.next()?;
179
180 for maybe_better in idle {
181 if best_peer.1.last_response_likely_bad && !maybe_better.1.last_response_likely_bad {
183 best_peer = maybe_better;
184 continue
185 }
186
187 if maybe_better.1.is_better(best_peer.1, &requirement) {
189 best_peer = maybe_better;
190 continue
191 }
192
193 if maybe_better.1.timeout() < best_peer.1.timeout() &&
195 !maybe_better.1.last_response_likely_bad
196 {
197 best_peer = maybe_better;
198 }
199 }
200
201 Some(*best_peer.0)
202 }
203
204 fn has_eth71_peer(&self) -> bool {
206 self.peers.values().any(|peer| {
207 !matches!(peer.state, PeerState::Closing) &&
208 peer.capabilities.supports_eth_at_least(&EthVersion::Eth71)
209 })
210 }
211
212 fn poll_action(&mut self) -> PollAction {
214 if self.queued_requests.is_empty() {
216 return PollAction::NoRequests
217 }
218
219 let request = self.queued_requests.pop_front().expect("not empty");
220 let Some(peer_id) = self.next_best_peer(request.best_peer_requirements()) else {
221 if self.should_fail_fast(&request) {
224 request.send_err_response(RequestError::UnsupportedCapability);
225 } else {
226 self.queued_requests.push_back(request);
229 }
230 return PollAction::NoPeersAvailable
231 };
232
233 let request = self.prepare_block_request(peer_id, request);
234
235 PollAction::Ready(FetchAction::BlockRequest { peer_id, request })
236 }
237
238 pub(crate) fn poll(&mut self, cx: &mut Context<'_>) -> Poll<FetchAction> {
240 loop {
242 let no_peers_available = match self.poll_action() {
243 PollAction::Ready(action) => return Poll::Ready(action),
244 PollAction::NoRequests => false,
245 PollAction::NoPeersAvailable => true,
246 };
247
248 loop {
249 match self.download_requests_rx.poll_next_unpin(cx) {
251 Poll::Ready(Some(request)) => {
252 if self.should_fail_fast(&request) {
255 request.send_err_response(RequestError::UnsupportedCapability);
256 continue
257 }
258
259 match request.get_priority() {
260 Priority::High => {
261 let pos = self
264 .queued_requests
265 .iter()
266 .position(|req| req.is_normal_priority())
267 .unwrap_or(0);
268 self.queued_requests.insert(pos, request);
269 }
270 Priority::Normal => {
271 self.queued_requests.push_back(request);
272 }
273 }
274 }
275 Poll::Ready(None) => {
276 unreachable!("channel can't close")
277 }
278 Poll::Pending => break,
279 }
280 }
281
282 if self.queued_requests.is_empty() || no_peers_available {
283 return Poll::Pending
284 }
285 }
286 }
287
288 fn has_snap_peer(&self) -> bool {
290 self.peers
291 .values()
292 .any(|peer| !matches!(peer.state, PeerState::Closing) && peer.supports_snap)
293 }
294
295 fn should_fail_fast(&self, request: &DownloadRequest<N>) -> bool {
298 (request.is_optional_bal() && !self.has_eth71_peer()) ||
299 (request.is_snap() && !self.has_snap_peer())
300 }
301
302 fn prepare_block_request(&mut self, peer_id: PeerId, req: DownloadRequest<N>) -> BlockRequest {
306 if let Some(peer) = self.peers.get_mut(&peer_id) {
308 peer.state = req.peer_state();
309 }
310
311 self.prepare_inflight_block_request(peer_id, req)
312 }
313
314 fn prepare_inflight_block_request(
316 &mut self,
317 peer_id: PeerId,
318 req: DownloadRequest<N>,
319 ) -> BlockRequest {
320 match req {
321 DownloadRequest::GetBlockHeaders { request, response, .. } => {
322 let inflight = Request { request: request.clone(), response };
323 self.inflight_headers_requests.insert(peer_id, inflight);
324 let HeadersRequest { start, limit, direction } = request;
325 BlockRequest::GetBlockHeaders(GetBlockHeaders {
326 start_block: start,
327 limit,
328 skip: 0,
329 direction,
330 })
331 }
332 DownloadRequest::GetBlockBodies { request, response, .. } => {
333 let inflight = Request { request: (), response };
334 self.inflight_bodies_requests.insert(peer_id, inflight);
335 BlockRequest::GetBlockBodies(GetBlockBodies(request))
336 }
337 DownloadRequest::GetBlockAccessLists { request, response, .. } => {
338 let inflight = Request { request: (), response };
339 self.inflight_bals_requests.insert(peer_id, inflight);
340 BlockRequest::GetBlockAccessLists(GetBlockAccessLists(request))
341 }
342 DownloadRequest::GetReceipts { request, response, .. } => {
343 let inflight = Request { request: (), response };
344 self.inflight_receipts_requests.insert(peer_id, inflight);
345 BlockRequest::GetReceipts(GetReceipts(request))
346 }
347 DownloadRequest::GetSnap { request, response, .. } => {
348 let inflight = Request { request: (), response };
349 self.inflight_snap_requests.insert(peer_id, inflight);
350 BlockRequest::GetSnap(Box::new(request))
351 }
352 }
353 }
354
355 fn followup_request(&mut self, peer_id: PeerId) -> Option<BlockResponseOutcome> {
363 let peer = self.peers.get_mut(&peer_id)?;
364 let req_idx = self.queued_requests.iter().position(|req| {
365 peer.satisfies(&req.best_peer_requirements())
367 })?;
368 let req = self.queued_requests.remove(req_idx).expect("valid request index");
369
370 peer.state = req.peer_state();
371 let req = self.prepare_inflight_block_request(peer_id, req);
372 Some(BlockResponseOutcome::Request(peer_id, req))
373 }
374
375 pub(crate) fn on_block_headers_response(
381 &mut self,
382 peer_id: PeerId,
383 res: RequestResult<Vec<N::BlockHeader>>,
384 ) -> Option<BlockResponseOutcome> {
385 let is_error = res.is_err();
386 let maybe_reputation_change = res.reputation_change_err();
387
388 let resp = self.inflight_headers_requests.remove(&peer_id);
389
390 let is_likely_bad_response =
391 resp.as_ref().is_some_and(|r| res.is_likely_bad_headers_response(&r.request));
392
393 if let Some(resp) = resp {
394 let _ = resp.response.send(res.map(|h| (peer_id, h).into()));
396 }
397
398 if let Some(peer) = self.peers.get_mut(&peer_id) {
399 peer.last_response_likely_bad = is_likely_bad_response;
401
402 if peer.state.on_request_finished() && !is_error && !is_likely_bad_response {
405 return self.followup_request(peer_id)
406 }
407 }
408
409 maybe_reputation_change
412 .map(|reputation_change| BlockResponseOutcome::BadResponse(peer_id, reputation_change))
413 }
414
415 pub(crate) fn on_block_bodies_response(
417 &mut self,
418 peer_id: PeerId,
419 res: RequestResult<Vec<N::BlockBody>>,
420 ) -> Option<BlockResponseOutcome> {
421 let is_likely_bad_response = res.as_ref().map_or(true, |bodies| bodies.is_empty());
422
423 if let Some(resp) = self.inflight_bodies_requests.remove(&peer_id) {
424 let _ = resp.response.send(res.map(|b| (peer_id, b).into()));
425 }
426 if let Some(peer) = self.peers.get_mut(&peer_id) {
427 peer.last_response_likely_bad = is_likely_bad_response;
429
430 if peer.state.on_request_finished() && !is_likely_bad_response {
431 return self.followup_request(peer_id)
432 }
433 }
434 None
435 }
436
437 pub(crate) fn on_block_access_lists_response(
439 &mut self,
440 peer_id: PeerId,
441 res: RequestResult<BlockAccessLists>,
442 ) -> Option<BlockResponseOutcome> {
443 let is_likely_bad_response = res.is_err();
444
445 if let Some(resp) = self.inflight_bals_requests.remove(&peer_id) {
446 let _ = resp.response.send(res.map(|b| (peer_id, b).into()));
447 }
448 if let Some(peer) = self.peers.get_mut(&peer_id) {
449 peer.last_response_likely_bad = is_likely_bad_response;
450
451 if peer.state.on_request_finished() && !is_likely_bad_response {
452 return self.followup_request(peer_id)
453 }
454 }
455 None
456 }
457
458 pub(crate) fn on_receipts_response(
463 &mut self,
464 peer_id: PeerId,
465 res: RequestResult<ReceiptsResponse<N::Receipt>>,
466 ) -> Option<BlockResponseOutcome> {
467 let is_likely_bad_response = res.as_ref().map_or(true, |resp| resp.receipts.is_empty());
468
469 if let Some(resp) = self.inflight_receipts_requests.remove(&peer_id) {
470 let _ = resp.response.send(res.map(|r| (peer_id, r).into()));
471 }
472 if let Some(peer) = self.peers.get_mut(&peer_id) {
473 peer.last_response_likely_bad = is_likely_bad_response;
474
475 if peer.state.on_request_finished() && !is_likely_bad_response {
476 return self.followup_request(peer_id)
477 }
478 }
479 None
480 }
481
482 pub(crate) fn on_snap_response(
484 &mut self,
485 peer_id: PeerId,
486 res: RequestResult<SnapResponse>,
487 ) -> Option<BlockResponseOutcome> {
488 let is_likely_bad_response = match &res {
491 Ok(SnapResponse::AccountRange(response)) => {
492 response.accounts.is_empty() && response.proof.is_empty()
493 }
494 Ok(SnapResponse::StorageRanges(response)) => {
495 response.slots.is_empty() && response.proof.is_empty()
496 }
497 Ok(SnapResponse::ByteCodes(response)) => response.codes.is_empty(),
498 Ok(SnapResponse::BlockAccessLists(response)) => {
499 response.block_access_lists.0.is_empty() ||
500 response.block_access_lists.0.iter().any(Option::is_none)
501 }
502 Err(_) => true,
503 };
504
505 if let Some(resp) = self.inflight_snap_requests.remove(&peer_id) {
506 let _ = resp.response.send(res.map(|r| (peer_id, r).into()));
507 }
508 if let Some(peer) = self.peers.get_mut(&peer_id) {
509 peer.last_response_likely_bad = is_likely_bad_response;
510
511 if peer.state.on_request_finished() && !is_likely_bad_response {
512 return self.followup_request(peer_id)
513 }
514 }
515 None
516 }
517
518 pub(crate) fn client(&self) -> FetchClient<N> {
520 FetchClient {
521 request_tx: self.download_requests_tx.clone(),
522 peers_handle: self.peers_handle.clone(),
523 num_active_peers: Arc::clone(&self.num_active_peers),
524 }
525 }
526}
527
528enum PollAction {
530 Ready(FetchAction),
531 NoRequests,
532 NoPeersAvailable,
533}
534
535#[derive(Debug)]
537pub(crate) struct NewPeerInfo {
538 pub(crate) peer_id: PeerId,
540 pub(crate) best_hash: B256,
542 pub(crate) best_number: u64,
544 pub(crate) capabilities: Arc<Capabilities>,
546 pub(crate) timeout: Arc<AtomicU64>,
548 pub(crate) range_info: Option<BlockRangeInfo>,
550 pub(crate) supports_snap: bool,
552}
553
554#[derive(Debug)]
556struct Peer {
557 state: PeerState,
559 best_hash: B256,
561 best_number: u64,
563 #[allow(dead_code)]
565 capabilities: Arc<Capabilities>,
566 timeout: Arc<AtomicU64>,
568 last_response_likely_bad: bool,
575 range_info: Option<BlockRangeInfo>,
577 supports_snap: bool,
579}
580
581impl Peer {
582 fn timeout(&self) -> u64 {
583 self.timeout.load(Ordering::Relaxed)
584 }
585
586 fn earliest(&self) -> u64 {
588 self.range_info.as_ref().map_or(0, |info| info.earliest())
589 }
590
591 fn has_full_history(&self) -> bool {
593 self.earliest() == 0
594 }
595
596 fn range(&self) -> Option<RangeInclusive<u64>> {
597 self.range_info.as_ref().map(|info| info.range())
598 }
599
600 fn satisfies(&self, requirement: &BestPeerRequirements) -> bool {
602 match requirement {
603 BestPeerRequirements::EthVersion(ver) => self.capabilities.supports_eth_at_least(ver),
604 BestPeerRequirements::SupportsSnap => self.supports_snap,
605 BestPeerRequirements::None |
606 BestPeerRequirements::FullBlock |
607 BestPeerRequirements::FullBlockRange(_) => true,
608 }
609 }
610
611 fn has_better_range(&self, other: &Self, range: &RangeInclusive<u64>) -> bool {
620 let self_range = self.range();
621 let other_range = other.range();
622
623 match (self_range, other_range) {
624 (Some(self_r), Some(other_r)) => {
625 let self_covers = self_r.contains(range.start()) && self_r.contains(range.end());
627 let other_covers = other_r.contains(range.start()) && other_r.contains(range.end());
628
629 #[expect(clippy::match_same_arms)]
630 match (self_covers, other_covers) {
631 (true, false) => true, (false, true) => false, (true, true) => false, (false, false) => {
635 self_r.start() < other_r.start()
637 }
638 }
639 }
640 (Some(self_r), None) => {
641 self_r.contains(range.start()) && self_r.contains(range.end())
644 }
645 (None, Some(other_r)) => {
646 !(other_r.contains(range.start()) && other_r.contains(range.end()))
649 }
650 (None, None) => false, }
652 }
653
654 fn is_better(&self, other: &Self, requirement: &BestPeerRequirements) -> bool {
656 match requirement {
657 BestPeerRequirements::FullBlockRange(range) => self.has_better_range(other, range),
658 BestPeerRequirements::FullBlock => self.has_full_history() && !other.has_full_history(),
659 BestPeerRequirements::None |
662 BestPeerRequirements::EthVersion(_) |
663 BestPeerRequirements::SupportsSnap => false,
664 }
665 }
666}
667
668#[derive(Debug)]
670enum PeerState {
671 Idle,
673 GetBlockHeaders,
675 GetBlockBodies,
677 GetBlockAccessLists,
679 GetReceipts,
681 GetSnap,
683 Closing,
685}
686
687impl PeerState {
690 const fn is_idle(&self) -> bool {
692 matches!(self, Self::Idle)
693 }
694
695 const fn on_request_finished(&mut self) -> bool {
701 if !matches!(self, Self::Closing) {
702 *self = Self::Idle;
703 return true
704 }
705 false
706 }
707}
708
709#[derive(Debug)]
712struct Request<Req, Resp> {
713 request: Req,
716 response: oneshot::Sender<Resp>,
717}
718
719#[derive(Debug)]
721#[expect(clippy::enum_variant_names)]
722pub(crate) enum DownloadRequest<N: NetworkPrimitives> {
723 GetBlockHeaders {
725 request: HeadersRequest,
726 response: oneshot::Sender<PeerRequestResult<Vec<N::BlockHeader>>>,
727 priority: Priority,
728 },
729 GetBlockBodies {
731 request: Vec<B256>,
732 response: oneshot::Sender<PeerRequestResult<Vec<N::BlockBody>>>,
733 priority: Priority,
734 range_hint: Option<RangeInclusive<u64>>,
735 },
736 GetBlockAccessLists {
738 request: Vec<B256>,
739 response: oneshot::Sender<PeerRequestResult<BlockAccessLists>>,
740 priority: Priority,
741 requirement: BalRequirement,
742 },
743 GetReceipts {
745 request: Vec<B256>,
746 response: oneshot::Sender<PeerRequestResult<ReceiptsResponse<N::Receipt>>>,
747 priority: Priority,
748 },
749 GetSnap {
751 request: SnapProtocolMessage,
752 response: oneshot::Sender<PeerRequestResult<SnapResponse>>,
753 priority: Priority,
754 },
755}
756
757impl<N: NetworkPrimitives> DownloadRequest<N> {
760 const fn peer_state(&self) -> PeerState {
762 match self {
763 Self::GetBlockHeaders { .. } => PeerState::GetBlockHeaders,
764 Self::GetBlockBodies { .. } => PeerState::GetBlockBodies,
765 Self::GetBlockAccessLists { .. } => PeerState::GetBlockAccessLists,
766 Self::GetReceipts { .. } => PeerState::GetReceipts,
767 Self::GetSnap { .. } => PeerState::GetSnap,
768 }
769 }
770
771 const fn get_priority(&self) -> &Priority {
773 match self {
774 Self::GetBlockHeaders { priority, .. } |
775 Self::GetBlockBodies { priority, .. } |
776 Self::GetBlockAccessLists { priority, .. } |
777 Self::GetReceipts { priority, .. } |
778 Self::GetSnap { priority, .. } => priority,
779 }
780 }
781
782 const fn is_normal_priority(&self) -> bool {
784 self.get_priority().is_normal()
785 }
786
787 const fn is_optional_bal(&self) -> bool {
789 matches!(self, Self::GetBlockAccessLists { requirement: BalRequirement::Optional, .. })
790 }
791
792 const fn is_snap(&self) -> bool {
794 matches!(self, Self::GetSnap { .. })
795 }
796
797 fn send_err_response(self, err: RequestError) {
799 let _ = match self {
800 Self::GetBlockHeaders { response, .. } => response.send(Err(err)).ok(),
801 Self::GetBlockBodies { response, .. } => response.send(Err(err)).ok(),
802 Self::GetBlockAccessLists { response, .. } => response.send(Err(err)).ok(),
803 Self::GetReceipts { response, .. } => response.send(Err(err)).ok(),
804 Self::GetSnap { response, .. } => response.send(Err(err)).ok(),
805 };
806 }
807
808 fn best_peer_requirements(&self) -> BestPeerRequirements {
810 match self {
811 Self::GetBlockHeaders { .. } => BestPeerRequirements::None,
812 Self::GetBlockAccessLists { .. } => BestPeerRequirements::EthVersion(EthVersion::Eth71),
813 Self::GetBlockBodies { range_hint, .. } => {
814 if let Some(range) = range_hint {
815 BestPeerRequirements::FullBlockRange(range.clone())
816 } else {
817 BestPeerRequirements::FullBlock
818 }
819 }
820 Self::GetReceipts { .. } => BestPeerRequirements::FullBlock,
821 Self::GetSnap { .. } => BestPeerRequirements::SupportsSnap,
822 }
823 }
824}
825
826pub(crate) enum FetchAction {
828 BlockRequest {
830 peer_id: PeerId,
832 request: BlockRequest,
834 },
835}
836
837#[derive(Debug, PartialEq, Eq)]
841pub(crate) enum BlockResponseOutcome {
842 Request(PeerId, BlockRequest),
844 BadResponse(PeerId, ReputationChangeKind),
846}
847
848enum BestPeerRequirements {
850 None,
852 FullBlockRange(RangeInclusive<u64>),
854 FullBlock,
856 EthVersion(EthVersion),
858 SupportsSnap,
860}
861
862#[cfg(test)]
863mod tests {
864 use super::*;
865 use crate::{peers::PeersManager, PeersConfig};
866 use alloy_consensus::Header;
867 use alloy_primitives::{Bytes, B512};
868 use reth_eth_wire::Capability;
869 use reth_eth_wire_types::snap::{
870 AccountRangeMessage, BlockAccessListsMessage, ByteCodesMessage, GetAccountRangeMessage,
871 StorageRangesMessage,
872 };
873 use std::future::poll_fn;
874 use test_case::test_case;
875
876 #[tokio::test(flavor = "multi_thread")]
877 async fn test_poll_fetcher() {
878 let manager = PeersManager::new(PeersConfig::default());
879 let mut fetcher =
880 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
881
882 poll_fn(move |cx| {
883 assert!(fetcher.poll(cx).is_pending());
884 let (tx, _rx) = oneshot::channel();
885 fetcher.queued_requests.push_back(DownloadRequest::GetBlockBodies {
886 request: vec![],
887 response: tx,
888 priority: Priority::default(),
889 range_hint: None,
890 });
891 assert!(fetcher.poll(cx).is_pending());
892
893 Poll::Ready(())
894 })
895 .await;
896 }
897
898 #[tokio::test]
899 async fn test_peer_rotation() {
900 let manager = PeersManager::new(PeersConfig::default());
901 let mut fetcher =
902 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
903 let peer1 = B512::random();
905 let peer2 = B512::random();
906 let capabilities = Arc::new(Capabilities::from(vec![]));
907 fetcher.new_active_peer(NewPeerInfo {
908 peer_id: peer1,
909 best_hash: B256::random(),
910 best_number: 1,
911 capabilities: Arc::clone(&capabilities),
912 timeout: Arc::new(AtomicU64::new(1)),
913 range_info: None,
914 supports_snap: false,
915 });
916 fetcher.new_active_peer(NewPeerInfo {
917 peer_id: peer2,
918 best_hash: B256::random(),
919 best_number: 2,
920 capabilities: Arc::clone(&capabilities),
921 timeout: Arc::new(AtomicU64::new(1)),
922 range_info: None,
923 supports_snap: false,
924 });
925
926 let first_peer = fetcher.next_best_peer(BestPeerRequirements::None).unwrap();
927 assert!(first_peer == peer1 || first_peer == peer2);
928 fetcher.on_pending_disconnect(&first_peer);
930 let second_peer = fetcher.next_best_peer(BestPeerRequirements::None).unwrap();
932 assert!(first_peer == peer1 || first_peer == peer2);
933 assert_ne!(first_peer, second_peer);
934 fetcher.on_pending_disconnect(&second_peer);
936 assert_eq!(fetcher.next_best_peer(BestPeerRequirements::None), None);
937 }
938
939 #[tokio::test]
940 async fn test_peer_prioritization() {
941 let manager = PeersManager::new(PeersConfig::default());
942 let mut fetcher =
943 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
944 let peer1 = B512::random();
946 let peer2 = B512::random();
947 let peer3 = B512::random();
948
949 let peer2_timeout = Arc::new(AtomicU64::new(300));
950
951 let capabilities = Arc::new(Capabilities::from(vec![]));
952 fetcher.new_active_peer(NewPeerInfo {
953 peer_id: peer1,
954 best_hash: B256::random(),
955 best_number: 1,
956 capabilities: Arc::clone(&capabilities),
957 timeout: Arc::new(AtomicU64::new(30)),
958 range_info: None,
959 supports_snap: false,
960 });
961 fetcher.new_active_peer(NewPeerInfo {
962 peer_id: peer2,
963 best_hash: B256::random(),
964 best_number: 2,
965 capabilities: Arc::clone(&capabilities),
966 timeout: Arc::clone(&peer2_timeout),
967 range_info: None,
968 supports_snap: false,
969 });
970 fetcher.new_active_peer(NewPeerInfo {
971 peer_id: peer3,
972 best_hash: B256::random(),
973 best_number: 3,
974 capabilities: Arc::clone(&capabilities),
975 timeout: Arc::new(AtomicU64::new(50)),
976 range_info: None,
977 supports_snap: false,
978 });
979
980 assert_eq!(fetcher.next_best_peer(BestPeerRequirements::None), Some(peer1));
982 assert_eq!(fetcher.next_best_peer(BestPeerRequirements::None), Some(peer1));
983 peer2_timeout.store(10, Ordering::Relaxed);
985 assert_eq!(fetcher.next_best_peer(BestPeerRequirements::None), Some(peer2));
987 assert_eq!(fetcher.next_best_peer(BestPeerRequirements::None), Some(peer2));
988 }
989
990 #[tokio::test]
991 async fn test_on_block_headers_response() {
992 let manager = PeersManager::new(PeersConfig::default());
993 let mut fetcher =
994 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
995 let peer_id = B512::random();
996
997 assert_eq!(fetcher.on_block_headers_response(peer_id, Ok(vec![Header::default()])), None);
998
999 assert_eq!(
1000 fetcher.on_block_headers_response(peer_id, Err(RequestError::Timeout)),
1001 Some(BlockResponseOutcome::BadResponse(peer_id, ReputationChangeKind::Timeout))
1002 );
1003 assert_eq!(
1004 fetcher.on_block_headers_response(peer_id, Err(RequestError::BadResponse)),
1005 None
1006 );
1007 assert_eq!(
1008 fetcher.on_block_headers_response(peer_id, Err(RequestError::ChannelClosed)),
1009 None
1010 );
1011 assert_eq!(
1012 fetcher.on_block_headers_response(peer_id, Err(RequestError::ConnectionDropped)),
1013 None
1014 );
1015 assert_eq!(
1016 fetcher.on_block_headers_response(peer_id, Err(RequestError::UnsupportedCapability)),
1017 None
1018 );
1019 }
1020
1021 #[tokio::test]
1022 async fn test_header_response_outcome() {
1023 let manager = PeersManager::new(PeersConfig::default());
1024 let mut fetcher =
1025 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
1026 let peer_id = B512::random();
1027
1028 let request_pair = || {
1029 let (tx, _rx) = oneshot::channel();
1030 let req = Request {
1031 request: HeadersRequest {
1032 start: 0u64.into(),
1033 limit: 1,
1034 direction: Default::default(),
1035 },
1036 response: tx,
1037 };
1038 let header = Header { number: 0, ..Default::default() };
1039 (req, header)
1040 };
1041
1042 fetcher.new_active_peer(NewPeerInfo {
1043 peer_id,
1044 best_hash: Default::default(),
1045 best_number: Default::default(),
1046 capabilities: Arc::new(Capabilities::from(vec![])),
1047 timeout: Default::default(),
1048 range_info: None,
1049 supports_snap: false,
1050 });
1051
1052 let (req, header) = request_pair();
1053 fetcher.inflight_headers_requests.insert(peer_id, req);
1054
1055 let outcome = fetcher.on_block_headers_response(peer_id, Ok(vec![header]));
1056 assert!(outcome.is_none());
1057 assert!(fetcher.peers[&peer_id].state.is_idle());
1058
1059 let outcome =
1060 fetcher.on_block_headers_response(peer_id, Err(RequestError::Timeout)).unwrap();
1061
1062 assert!(EthResponseValidator::reputation_change_err(&Err::<Vec<Header>, _>(
1063 RequestError::Timeout
1064 ))
1065 .is_some());
1066
1067 match outcome {
1068 BlockResponseOutcome::BadResponse(peer, _) => {
1069 assert_eq!(peer, peer_id)
1070 }
1071 BlockResponseOutcome::Request(_, _) => {
1072 unreachable!()
1073 }
1074 };
1075
1076 assert!(fetcher.peers[&peer_id].state.is_idle());
1077 }
1078
1079 #[tokio::test]
1080 async fn test_partial_header_response_triggers_followup() {
1081 let (mut fetcher, peer_id) = fetcher_with_peer();
1082
1083 let (followup_tx, _followup_rx) = oneshot::channel();
1084 fetcher.queued_requests.push_back(DownloadRequest::GetBlockHeaders {
1085 request: HeadersRequest::falling(1u64.into(), 1),
1086 response: followup_tx,
1087 priority: Priority::High,
1088 });
1089
1090 let (response_tx, mut response_rx) = oneshot::channel();
1091 fetcher.inflight_headers_requests.insert(
1092 peer_id,
1093 Request { request: HeadersRequest::falling(2u64.into(), 2), response: response_tx },
1094 );
1095 fetcher.peers.get_mut(&peer_id).unwrap().state = PeerState::GetBlockHeaders;
1096
1097 let header = Header { number: 2, ..Default::default() };
1098 let outcome = fetcher.on_block_headers_response(peer_id, Ok(vec![header]));
1099
1100 let Some(BlockResponseOutcome::Request(
1101 dispatched_peer,
1102 BlockRequest::GetBlockHeaders(request),
1103 )) = outcome
1104 else {
1105 panic!("expected a header followup request")
1106 };
1107 assert_eq!(dispatched_peer, peer_id);
1108 assert_eq!(request.start_block, 1u64.into());
1109 assert_eq!(request.limit, 1);
1110 assert!(!fetcher.peers[&peer_id].last_response_likely_bad);
1111 assert!(response_rx.try_recv().is_ok());
1112 }
1113
1114 #[test]
1115 fn test_peer_is_better_none_requirement() {
1116 let peer1 = Peer {
1117 state: PeerState::Idle,
1118 best_hash: B256::random(),
1119 best_number: 100,
1120 capabilities: Arc::new(Capabilities::new(vec![])),
1121 timeout: Arc::new(AtomicU64::new(10)),
1122 last_response_likely_bad: false,
1123 range_info: Some(BlockRangeInfo::new(0, 100, B256::random())),
1124 supports_snap: false,
1125 };
1126
1127 let peer2 = Peer {
1128 state: PeerState::Idle,
1129 best_hash: B256::random(),
1130 best_number: 50,
1131 capabilities: Arc::new(Capabilities::new(vec![])),
1132 timeout: Arc::new(AtomicU64::new(20)),
1133 last_response_likely_bad: false,
1134 range_info: None,
1135 supports_snap: false,
1136 };
1137
1138 assert!(!peer1.is_better(&peer2, &BestPeerRequirements::None));
1140 assert!(!peer2.is_better(&peer1, &BestPeerRequirements::None));
1141 }
1142
1143 #[test]
1144 fn test_peer_is_better_full_block_requirement() {
1145 let peer_full = Peer {
1147 state: PeerState::Idle,
1148 best_hash: B256::random(),
1149 best_number: 100,
1150 capabilities: Arc::new(Capabilities::new(vec![])),
1151 timeout: Arc::new(AtomicU64::new(10)),
1152 last_response_likely_bad: false,
1153 range_info: Some(BlockRangeInfo::new(0, 100, B256::random())),
1154 supports_snap: false,
1155 };
1156
1157 let peer_partial = Peer {
1159 state: PeerState::Idle,
1160 best_hash: B256::random(),
1161 best_number: 100,
1162 capabilities: Arc::new(Capabilities::new(vec![])),
1163 timeout: Arc::new(AtomicU64::new(10)),
1164 last_response_likely_bad: false,
1165 range_info: Some(BlockRangeInfo::new(50, 100, B256::random())),
1166 supports_snap: false,
1167 };
1168
1169 let peer_no_range = Peer {
1171 state: PeerState::Idle,
1172 best_hash: B256::random(),
1173 best_number: 100,
1174 capabilities: Arc::new(Capabilities::new(vec![])),
1175 timeout: Arc::new(AtomicU64::new(10)),
1176 last_response_likely_bad: false,
1177 range_info: None,
1178 supports_snap: false,
1179 };
1180
1181 assert!(peer_full.is_better(&peer_partial, &BestPeerRequirements::FullBlock));
1183 assert!(!peer_partial.is_better(&peer_full, &BestPeerRequirements::FullBlock));
1184
1185 assert!(peer_no_range.is_better(&peer_partial, &BestPeerRequirements::FullBlock));
1187 assert!(!peer_partial.is_better(&peer_no_range, &BestPeerRequirements::FullBlock));
1188
1189 assert!(!peer_full.is_better(&peer_no_range, &BestPeerRequirements::FullBlock));
1191 assert!(!peer_no_range.is_better(&peer_full, &BestPeerRequirements::FullBlock));
1192 }
1193
1194 #[test]
1195 fn test_peer_is_better_full_block_range_requirement() {
1196 let range = RangeInclusive::new(40, 60);
1197
1198 let peer_covers = Peer {
1200 state: PeerState::Idle,
1201 best_hash: B256::random(),
1202 best_number: 100,
1203 capabilities: Arc::new(Capabilities::new(vec![])),
1204 timeout: Arc::new(AtomicU64::new(10)),
1205 last_response_likely_bad: false,
1206 range_info: Some(BlockRangeInfo::new(0, 100, B256::random())),
1207 supports_snap: false,
1208 };
1209
1210 let peer_no_cover = Peer {
1212 state: PeerState::Idle,
1213 best_hash: B256::random(),
1214 best_number: 100,
1215 capabilities: Arc::new(Capabilities::new(vec![])),
1216 timeout: Arc::new(AtomicU64::new(10)),
1217 last_response_likely_bad: false,
1218 range_info: Some(BlockRangeInfo::new(70, 100, B256::random())),
1219 supports_snap: false,
1220 };
1221
1222 assert!(peer_covers
1224 .is_better(&peer_no_cover, &BestPeerRequirements::FullBlockRange(range.clone())));
1225 assert!(
1226 !peer_no_cover.is_better(&peer_covers, &BestPeerRequirements::FullBlockRange(range))
1227 );
1228 }
1229
1230 #[test]
1231 fn test_peer_is_better_both_cover_range() {
1232 let range = RangeInclusive::new(30, 50);
1233
1234 let peer_full = Peer {
1236 state: PeerState::Idle,
1237 best_hash: B256::random(),
1238 best_number: 100,
1239 capabilities: Arc::new(Capabilities::new(vec![])),
1240 timeout: Arc::new(AtomicU64::new(10)),
1241 last_response_likely_bad: false,
1242 range_info: Some(BlockRangeInfo::new(0, 50, B256::random())),
1243 supports_snap: false,
1244 };
1245
1246 let peer_partial = Peer {
1248 state: PeerState::Idle,
1249 best_hash: B256::random(),
1250 best_number: 100,
1251 capabilities: Arc::new(Capabilities::new(vec![])),
1252 timeout: Arc::new(AtomicU64::new(10)),
1253 last_response_likely_bad: false,
1254 range_info: Some(BlockRangeInfo::new(30, 50, B256::random())),
1255 supports_snap: false,
1256 };
1257
1258 assert!(!peer_full
1260 .is_better(&peer_partial, &BestPeerRequirements::FullBlockRange(range.clone())));
1261 assert!(!peer_partial.is_better(&peer_full, &BestPeerRequirements::FullBlockRange(range)));
1262 }
1263
1264 #[test]
1265 fn test_peer_is_better_lower_start() {
1266 let range = RangeInclusive::new(30, 60);
1267
1268 let peer_full = Peer {
1270 state: PeerState::Idle,
1271 best_hash: B256::random(),
1272 best_number: 100,
1273 capabilities: Arc::new(Capabilities::new(vec![])),
1274 timeout: Arc::new(AtomicU64::new(10)),
1275 last_response_likely_bad: false,
1276 range_info: Some(BlockRangeInfo::new(0, 50, B256::random())),
1277 supports_snap: false,
1278 };
1279
1280 let peer_partial = Peer {
1282 state: PeerState::Idle,
1283 best_hash: B256::random(),
1284 best_number: 100,
1285 capabilities: Arc::new(Capabilities::new(vec![])),
1286 timeout: Arc::new(AtomicU64::new(10)),
1287 last_response_likely_bad: false,
1288 range_info: Some(BlockRangeInfo::new(30, 50, B256::random())),
1289 supports_snap: false,
1290 };
1291
1292 assert!(peer_full
1294 .is_better(&peer_partial, &BestPeerRequirements::FullBlockRange(range.clone())));
1295 assert!(!peer_partial.is_better(&peer_full, &BestPeerRequirements::FullBlockRange(range)));
1296 }
1297
1298 #[test]
1299 fn test_peer_is_better_neither_covers_range() {
1300 let range = RangeInclusive::new(40, 60);
1301
1302 let peer_full = Peer {
1304 state: PeerState::Idle,
1305 best_hash: B256::random(),
1306 best_number: 30,
1307 capabilities: Arc::new(Capabilities::new(vec![])),
1308 timeout: Arc::new(AtomicU64::new(10)),
1309 last_response_likely_bad: false,
1310 range_info: Some(BlockRangeInfo::new(0, 30, B256::random())),
1311 supports_snap: false,
1312 };
1313
1314 let peer_partial = Peer {
1316 state: PeerState::Idle,
1317 best_hash: B256::random(),
1318 best_number: 30,
1319 capabilities: Arc::new(Capabilities::new(vec![])),
1320 timeout: Arc::new(AtomicU64::new(10)),
1321 last_response_likely_bad: false,
1322 range_info: Some(BlockRangeInfo::new(10, 30, B256::random())),
1323 supports_snap: false,
1324 };
1325
1326 assert!(peer_full
1328 .is_better(&peer_partial, &BestPeerRequirements::FullBlockRange(range.clone())));
1329 assert!(!peer_partial.is_better(&peer_full, &BestPeerRequirements::FullBlockRange(range)));
1330 }
1331
1332 #[test]
1333 fn test_peer_is_better_no_range_info() {
1334 let range = RangeInclusive::new(40, 60);
1335
1336 let peer_with_range = Peer {
1338 state: PeerState::Idle,
1339 best_hash: B256::random(),
1340 best_number: 100,
1341 capabilities: Arc::new(Capabilities::new(vec![])),
1342 timeout: Arc::new(AtomicU64::new(10)),
1343 last_response_likely_bad: false,
1344 range_info: Some(BlockRangeInfo::new(30, 100, B256::random())),
1345 supports_snap: false,
1346 };
1347
1348 let peer_no_range = Peer {
1350 state: PeerState::Idle,
1351 best_hash: B256::random(),
1352 best_number: 100,
1353 capabilities: Arc::new(Capabilities::new(vec![])),
1354 timeout: Arc::new(AtomicU64::new(10)),
1355 last_response_likely_bad: false,
1356 range_info: None,
1357 supports_snap: false,
1358 };
1359
1360 assert!(!peer_no_range
1362 .is_better(&peer_with_range, &BestPeerRequirements::FullBlockRange(range.clone())));
1363
1364 assert!(
1366 peer_with_range.is_better(&peer_no_range, &BestPeerRequirements::FullBlockRange(range))
1367 );
1368 }
1369
1370 #[test]
1371 fn test_peer_is_better_one_peer_no_range_covers() {
1372 let range = RangeInclusive::new(40, 60);
1373
1374 let peer_with_range_covers = Peer {
1376 state: PeerState::Idle,
1377 best_hash: B256::random(),
1378 best_number: 100,
1379 capabilities: Arc::new(Capabilities::new(vec![])),
1380 timeout: Arc::new(AtomicU64::new(10)),
1381 last_response_likely_bad: false,
1382 range_info: Some(BlockRangeInfo::new(30, 100, B256::random())),
1383 supports_snap: false,
1384 };
1385
1386 let peer_no_range = Peer {
1388 state: PeerState::Idle,
1389 best_hash: B256::random(),
1390 best_number: 100,
1391 capabilities: Arc::new(Capabilities::new(vec![])),
1392 timeout: Arc::new(AtomicU64::new(10)),
1393 last_response_likely_bad: false,
1394 range_info: None,
1395 supports_snap: false,
1396 };
1397
1398 assert!(peer_with_range_covers
1400 .is_better(&peer_no_range, &BestPeerRequirements::FullBlockRange(range.clone())));
1401
1402 assert!(!peer_no_range
1404 .is_better(&peer_with_range_covers, &BestPeerRequirements::FullBlockRange(range)));
1405 }
1406
1407 #[test]
1408 fn test_peer_is_better_one_peer_no_range_doesnt_cover() {
1409 let range = RangeInclusive::new(40, 60);
1410
1411 let peer_with_range_no_cover = Peer {
1413 state: PeerState::Idle,
1414 best_hash: B256::random(),
1415 best_number: 100,
1416 capabilities: Arc::new(Capabilities::new(vec![])),
1417 timeout: Arc::new(AtomicU64::new(10)),
1418 last_response_likely_bad: false,
1419 range_info: Some(BlockRangeInfo::new(70, 100, B256::random())),
1420 supports_snap: false,
1421 };
1422
1423 let peer_no_range = Peer {
1425 state: PeerState::Idle,
1426 best_hash: B256::random(),
1427 best_number: 100,
1428 capabilities: Arc::new(Capabilities::new(vec![])),
1429 timeout: Arc::new(AtomicU64::new(10)),
1430 last_response_likely_bad: false,
1431 range_info: None,
1432 supports_snap: false,
1433 };
1434
1435 assert!(!peer_with_range_no_cover
1437 .is_better(&peer_no_range, &BestPeerRequirements::FullBlockRange(range.clone())));
1438
1439 assert!(peer_no_range
1441 .is_better(&peer_with_range_no_cover, &BestPeerRequirements::FullBlockRange(range)));
1442 }
1443
1444 #[test]
1445 fn test_peer_is_better_edge_cases() {
1446 let range = RangeInclusive::new(50, 100);
1448
1449 let peer_exact = Peer {
1451 state: PeerState::Idle,
1452 best_hash: B256::random(),
1453 best_number: 100,
1454 capabilities: Arc::new(Capabilities::new(vec![])),
1455 timeout: Arc::new(AtomicU64::new(10)),
1456 last_response_likely_bad: false,
1457 range_info: Some(BlockRangeInfo::new(50, 100, B256::random())),
1458 supports_snap: false,
1459 };
1460
1461 let peer_short_start = Peer {
1463 state: PeerState::Idle,
1464 best_hash: B256::random(),
1465 best_number: 100,
1466 capabilities: Arc::new(Capabilities::new(vec![])),
1467 timeout: Arc::new(AtomicU64::new(10)),
1468 last_response_likely_bad: false,
1469 range_info: Some(BlockRangeInfo::new(51, 100, B256::random())),
1470 supports_snap: false,
1471 };
1472
1473 let peer_short_end = Peer {
1475 state: PeerState::Idle,
1476 best_hash: B256::random(),
1477 best_number: 100,
1478 capabilities: Arc::new(Capabilities::new(vec![])),
1479 timeout: Arc::new(AtomicU64::new(10)),
1480 last_response_likely_bad: false,
1481 range_info: Some(BlockRangeInfo::new(50, 99, B256::random())),
1482 supports_snap: false,
1483 };
1484
1485 assert!(peer_exact
1487 .is_better(&peer_short_start, &BestPeerRequirements::FullBlockRange(range.clone())));
1488 assert!(peer_exact
1489 .is_better(&peer_short_end, &BestPeerRequirements::FullBlockRange(range.clone())));
1490
1491 assert!(!peer_short_start
1493 .is_better(&peer_exact, &BestPeerRequirements::FullBlockRange(range.clone())));
1494 assert!(
1495 !peer_short_end.is_better(&peer_exact, &BestPeerRequirements::FullBlockRange(range))
1496 );
1497 }
1498
1499 fn fetcher_with_peer() -> (StateFetcher<EthNetworkPrimitives>, PeerId) {
1501 let manager = PeersManager::new(PeersConfig::default());
1502 let mut fetcher =
1503 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
1504 let peer_id = B512::random();
1505
1506 fetcher.new_active_peer(NewPeerInfo {
1507 peer_id,
1508 best_hash: Default::default(),
1509 best_number: Default::default(),
1510 capabilities: Arc::new(Capabilities::from(vec![])),
1511 timeout: Default::default(),
1512 range_info: None,
1513 supports_snap: false,
1514 });
1515 (fetcher, peer_id)
1516 }
1517
1518 fn insert_inflight_receipts(
1521 fetcher: &mut StateFetcher<EthNetworkPrimitives>,
1522 peer_id: PeerId,
1523 ) -> oneshot::Receiver<PeerRequestResult<ReceiptsResponse<reth_ethereum_primitives::Receipt>>>
1524 {
1525 let (tx, rx) = oneshot::channel();
1526 fetcher.inflight_receipts_requests.insert(peer_id, Request { request: (), response: tx });
1527 fetcher.peers.get_mut(&peer_id).unwrap().state = PeerState::GetReceipts;
1528 rx
1529 }
1530
1531 #[tokio::test]
1534 async fn test_poll_dispatches_receipts_to_peer() {
1535 let (mut fetcher, peer_id) = fetcher_with_peer();
1536
1537 poll_fn(move |cx| {
1538 let (tx, _rx) = oneshot::channel();
1539 fetcher.queued_requests.push_back(DownloadRequest::GetReceipts {
1540 request: vec![B256::ZERO],
1541 response: tx,
1542 priority: Priority::default(),
1543 });
1544
1545 let Poll::Ready(FetchAction::BlockRequest { peer_id: dispatched_peer, request }) =
1546 fetcher.poll(cx)
1547 else {
1548 panic!("expected Ready(BlockRequest)");
1549 };
1550 assert_eq!(dispatched_peer, peer_id);
1551 assert!(matches!(request, BlockRequest::GetReceipts(_)));
1552
1553 assert!(matches!(fetcher.peers[&peer_id].state, PeerState::GetReceipts));
1555 assert!(fetcher.inflight_receipts_requests.contains_key(&peer_id));
1557
1558 Poll::Ready(())
1559 })
1560 .await;
1561 }
1562
1563 #[tokio::test]
1566 async fn test_receipts_complete_response_resolves_and_idles_peer() {
1567 let (mut fetcher, peer_id) = fetcher_with_peer();
1568
1569 let rx = insert_inflight_receipts(&mut fetcher, peer_id);
1570
1571 let resp = ReceiptsResponse::new(vec![vec![]]);
1572 let outcome = fetcher.on_receipts_response(peer_id, Ok(resp));
1573
1574 assert!(outcome.is_none());
1576 assert!(fetcher.peers[&peer_id].state.is_idle());
1578 assert!(!fetcher.inflight_receipts_requests.contains_key(&peer_id));
1580
1581 let result = rx.await.unwrap().unwrap();
1583 assert_eq!(result.1.receipts.len(), 1);
1584 }
1585
1586 #[tokio::test]
1587 async fn test_receipts_empty_response_marks_peer_bad() {
1588 let (mut fetcher, peer_id) = fetcher_with_peer();
1589 let _rx = insert_inflight_receipts(&mut fetcher, peer_id);
1590
1591 let resp = ReceiptsResponse::new(vec![]);
1592 let _ = fetcher.on_receipts_response(peer_id, Ok(resp));
1593
1594 assert!(fetcher.peers[&peer_id].last_response_likely_bad);
1595 }
1596
1597 #[tokio::test]
1598 async fn test_receipts_error_forwards_and_marks_peer_bad() {
1599 let (mut fetcher, peer_id) = fetcher_with_peer();
1600 let rx = insert_inflight_receipts(&mut fetcher, peer_id);
1601
1602 let _ = fetcher.on_receipts_response(peer_id, Err(RequestError::Timeout));
1603
1604 assert!(fetcher.peers[&peer_id].last_response_likely_bad);
1605 let result = rx.await.unwrap();
1607 assert_eq!(result.unwrap_err(), RequestError::Timeout);
1608 }
1609
1610 #[tokio::test]
1611 async fn test_session_closed_cancels_inflight_receipts() {
1612 let (mut fetcher, peer_id) = fetcher_with_peer();
1613 let rx = insert_inflight_receipts(&mut fetcher, peer_id);
1614
1615 fetcher.on_session_closed(&peer_id);
1616
1617 assert!(!fetcher.peers.contains_key(&peer_id));
1618 assert!(!fetcher.inflight_receipts_requests.contains_key(&peer_id));
1619
1620 let result = rx.await.unwrap();
1621 assert_eq!(result.unwrap_err(), RequestError::ConnectionDropped);
1622 }
1623
1624 #[tokio::test]
1625 async fn test_receipts_response_triggers_followup() {
1626 let (mut fetcher, peer_id) = fetcher_with_peer();
1627
1628 let (followup_tx, _followup_rx) = oneshot::channel();
1630 fetcher.queued_requests.push_back(DownloadRequest::GetBlockBodies {
1631 request: vec![B256::random()],
1632 response: followup_tx,
1633 priority: Priority::default(),
1634 range_hint: None,
1635 });
1636
1637 let _rx = insert_inflight_receipts(&mut fetcher, peer_id);
1638
1639 let resp = ReceiptsResponse::new(vec![vec![]]);
1640 let outcome = fetcher.on_receipts_response(peer_id, Ok(resp));
1641
1642 assert!(matches!(outcome, Some(BlockResponseOutcome::Request(pid, _)) if pid == peer_id));
1643 }
1644
1645 #[tokio::test]
1646 async fn test_followup_skips_request_peer_cannot_serve() {
1647 let (mut fetcher, peer_id) = fetcher_with_peer();
1648
1649 let peer_71 = B512::random();
1650 let caps_71 = Arc::new(Capabilities::from(vec![Capability::new("eth".into(), 71)]));
1651 fetcher.new_active_peer(NewPeerInfo {
1652 peer_id: peer_71,
1653 best_hash: B256::random(),
1654 best_number: 100,
1655 capabilities: caps_71,
1656 timeout: Arc::new(AtomicU64::new(10)),
1657 range_info: None,
1658 supports_snap: false,
1659 });
1660 fetcher.peers.get_mut(&peer_71).expect("peer exists").state = PeerState::GetBlockHeaders;
1661
1662 let (followup_tx, _followup_rx) = oneshot::channel();
1663 fetcher.queued_requests.push_back(DownloadRequest::GetBlockAccessLists {
1664 request: vec![B256::random()],
1665 response: followup_tx,
1666 priority: Priority::Normal,
1667 requirement: BalRequirement::Optional,
1668 });
1669
1670 let _rx = insert_inflight_receipts(&mut fetcher, peer_id);
1671
1672 let resp = ReceiptsResponse::new(vec![vec![]]);
1673 assert!(fetcher.on_receipts_response(peer_id, Ok(resp)).is_none());
1674 assert!(fetcher.peers[&peer_id].state.is_idle());
1675 assert!(!fetcher.inflight_bals_requests.contains_key(&peer_id));
1676 assert!(matches!(
1677 fetcher.queued_requests.front(),
1678 Some(DownloadRequest::GetBlockAccessLists {
1679 requirement: BalRequirement::Optional,
1680 ..
1681 })
1682 ));
1683 }
1684
1685 #[tokio::test]
1686 async fn test_followup_uses_first_satisfiable_request() {
1687 let (mut fetcher, peer_id) = fetcher_with_peer();
1688
1689 let peer_71 = B512::random();
1690 let caps_71 = Arc::new(Capabilities::from(vec![Capability::new("eth".into(), 71)]));
1691 fetcher.new_active_peer(NewPeerInfo {
1692 peer_id: peer_71,
1693 best_hash: B256::random(),
1694 best_number: 100,
1695 capabilities: caps_71,
1696 timeout: Arc::new(AtomicU64::new(10)),
1697 range_info: None,
1698 supports_snap: false,
1699 });
1700 fetcher.peers.get_mut(&peer_71).expect("peer exists").state = PeerState::GetBlockHeaders;
1701
1702 let (bal_tx, _bal_rx) = oneshot::channel();
1703 fetcher.queued_requests.push_back(DownloadRequest::GetBlockAccessLists {
1704 request: vec![B256::random()],
1705 response: bal_tx,
1706 priority: Priority::Normal,
1707 requirement: BalRequirement::Optional,
1708 });
1709
1710 let (bodies_tx, _bodies_rx) = oneshot::channel();
1711 fetcher.queued_requests.push_back(DownloadRequest::GetBlockBodies {
1712 request: vec![B256::random()],
1713 response: bodies_tx,
1714 priority: Priority::Normal,
1715 range_hint: None,
1716 });
1717
1718 let _rx = insert_inflight_receipts(&mut fetcher, peer_id);
1719
1720 let resp = ReceiptsResponse::new(vec![vec![]]);
1721 let outcome = fetcher.on_receipts_response(peer_id, Ok(resp));
1722
1723 assert!(matches!(
1724 outcome,
1725 Some(BlockResponseOutcome::Request(pid, BlockRequest::GetBlockBodies(_))) if pid == peer_id
1726 ));
1727 assert!(fetcher.inflight_bodies_requests.contains_key(&peer_id));
1728 assert!(matches!(fetcher.peers[&peer_id].state, PeerState::GetBlockBodies));
1729 assert_eq!(fetcher.queued_requests.len(), 1);
1730 assert!(matches!(
1731 fetcher.queued_requests.front(),
1732 Some(DownloadRequest::GetBlockAccessLists {
1733 requirement: BalRequirement::Optional,
1734 ..
1735 })
1736 ));
1737 }
1738
1739 #[tokio::test]
1740 async fn test_prepare_block_request_creates_inflight_receipts() {
1741 let (mut fetcher, peer_id) = fetcher_with_peer();
1742 let hashes = vec![B256::with_last_byte(1), B256::with_last_byte(2)];
1743
1744 let (tx, _rx) = oneshot::channel();
1745 let req = DownloadRequest::GetReceipts {
1746 request: hashes.clone(),
1747 response: tx,
1748 priority: Priority::default(),
1749 };
1750
1751 let block_request = fetcher.prepare_block_request(peer_id, req);
1752
1753 match block_request {
1755 BlockRequest::GetReceipts(ref get) => {
1756 assert_eq!(get.0, hashes);
1757 }
1758 other => panic!("expected GetReceipts, got {other:?}"),
1759 }
1760
1761 assert!(matches!(fetcher.peers[&peer_id].state, PeerState::GetReceipts));
1763
1764 assert!(fetcher.inflight_receipts_requests.contains_key(&peer_id));
1766 }
1767 #[tokio::test]
1768 async fn test_next_best_peer_eth71_no_support() {
1769 let manager = PeersManager::new(PeersConfig::default());
1770 let mut fetcher =
1771 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
1772
1773 let peer = B512::random();
1774
1775 let capabilities = Arc::new(Capabilities::new(vec![]));
1777
1778 fetcher.new_active_peer(NewPeerInfo {
1779 peer_id: peer,
1780 best_hash: B256::random(),
1781 best_number: 100,
1782 capabilities,
1783 timeout: Arc::new(AtomicU64::new(10)),
1784 range_info: None,
1785 supports_snap: false,
1786 });
1787
1788 assert_eq!(
1790 fetcher.next_best_peer(BestPeerRequirements::EthVersion(EthVersion::Eth71)),
1791 None
1792 );
1793 }
1794
1795 #[tokio::test]
1796 async fn test_next_best_peer_eth71_supported() {
1797 let manager = PeersManager::new(PeersConfig::default());
1798 let mut fetcher =
1799 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
1800
1801 let peer = B512::random();
1802
1803 let capabilities = Arc::new(Capabilities::from(vec![Capability::new("eth".into(), 71)]));
1805
1806 fetcher.new_active_peer(NewPeerInfo {
1807 peer_id: peer,
1808 best_hash: B256::random(),
1809 best_number: 100,
1810 capabilities,
1811 timeout: Arc::new(AtomicU64::new(10)),
1812 range_info: None,
1813 supports_snap: false,
1814 });
1815
1816 assert_eq!(
1817 fetcher.next_best_peer(BestPeerRequirements::EthVersion(EthVersion::Eth71)),
1818 Some(peer)
1819 );
1820 }
1821
1822 #[tokio::test]
1823 async fn test_next_best_peer_eth71_filters_correctly() {
1824 let manager = PeersManager::new(PeersConfig::default());
1825 let mut fetcher =
1826 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
1827
1828 let peer_no_71 = B512::random();
1829 let peer_with_71 = B512::random();
1830
1831 let caps_old = Arc::new(Capabilities::new(vec![]));
1833
1834 let caps_71 = Arc::new(Capabilities::from(vec![Capability::new("eth".into(), 71)]));
1836
1837 fetcher.new_active_peer(NewPeerInfo {
1838 peer_id: peer_no_71,
1839 best_hash: B256::random(),
1840 best_number: 100,
1841 capabilities: caps_old,
1842 timeout: Arc::new(AtomicU64::new(5)),
1843 range_info: None,
1844 supports_snap: false,
1845 });
1846
1847 fetcher.new_active_peer(NewPeerInfo {
1848 peer_id: peer_with_71,
1849 best_hash: B256::random(),
1850 best_number: 100,
1851 capabilities: caps_71,
1852 timeout: Arc::new(AtomicU64::new(50)),
1853 range_info: None,
1854 supports_snap: false,
1855 });
1856
1857 assert_eq!(
1860 fetcher.next_best_peer(BestPeerRequirements::EthVersion(EthVersion::Eth71)),
1861 Some(peer_with_71)
1862 );
1863 }
1864
1865 #[tokio::test]
1866 async fn test_wakes_when_eth71_peer_connects() {
1867 use futures::task::noop_waker;
1868 use std::task::{Context, Poll};
1869
1870 let manager = PeersManager::new(PeersConfig::default());
1871 let mut fetcher =
1872 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
1873
1874 let (tx, _rx) = oneshot::channel();
1876 fetcher.queued_requests.push_back(DownloadRequest::GetBlockAccessLists {
1877 request: vec![],
1878 response: tx,
1879 priority: Priority::Normal,
1880 requirement: BalRequirement::Mandatory,
1881 });
1882
1883 let waker = noop_waker();
1884 let mut cx = Context::from_waker(&waker);
1885
1886 assert!(matches!(fetcher.poll(&mut cx), Poll::Pending));
1888
1889 let peer_old = B512::random();
1891 let caps_old = Arc::new(Capabilities::new(vec![]));
1892
1893 fetcher.new_active_peer(NewPeerInfo {
1894 peer_id: peer_old,
1895 best_hash: B256::random(),
1896 best_number: 100,
1897 capabilities: caps_old,
1898 timeout: Arc::new(AtomicU64::new(10)),
1899 range_info: None,
1900 supports_snap: false,
1901 });
1902
1903 assert!(matches!(fetcher.poll(&mut cx), Poll::Pending));
1905
1906 let peer_71 = B512::random();
1908 let caps_71 = Arc::new(Capabilities::from(vec![Capability::new("eth".into(), 71)]));
1909
1910 fetcher.new_active_peer(NewPeerInfo {
1911 peer_id: peer_71,
1912 best_hash: B256::random(),
1913 best_number: 100,
1914 capabilities: caps_71,
1915 timeout: Arc::new(AtomicU64::new(10)),
1916 range_info: None,
1917 supports_snap: false,
1918 });
1919
1920 if let Poll::Ready(FetchAction::BlockRequest { peer_id, .. }) = fetcher.poll(&mut cx) {
1922 assert_eq!(peer_id, peer_71);
1923 }
1924 }
1925
1926 #[tokio::test]
1927 async fn test_optional_bal_request_rejected_without_eth71_peer() {
1928 use futures::task::noop_waker;
1929 use std::task::{Context, Poll};
1930
1931 let manager = PeersManager::new(PeersConfig::default());
1932 let mut fetcher =
1933 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
1934
1935 let peer_old = B512::random();
1936 let caps_old = Arc::new(Capabilities::new(vec![]));
1937 fetcher.new_active_peer(NewPeerInfo {
1938 peer_id: peer_old,
1939 best_hash: B256::random(),
1940 best_number: 100,
1941 capabilities: caps_old,
1942 timeout: Arc::new(AtomicU64::new(10)),
1943 range_info: None,
1944 supports_snap: false,
1945 });
1946
1947 let (tx, rx) = oneshot::channel();
1948 fetcher
1949 .download_requests_tx
1950 .send(DownloadRequest::GetBlockAccessLists {
1951 request: vec![],
1952 response: tx,
1953 priority: Priority::Normal,
1954 requirement: BalRequirement::Optional,
1955 })
1956 .unwrap();
1957
1958 let waker = noop_waker();
1959 let mut cx = Context::from_waker(&waker);
1960
1961 assert!(matches!(fetcher.poll(&mut cx), Poll::Pending));
1962 assert!(fetcher.queued_requests.is_empty());
1963 assert_eq!(rx.await.unwrap().unwrap_err(), RequestError::UnsupportedCapability);
1964 }
1965
1966 #[tokio::test]
1967 async fn test_optional_bal_request_waits_for_busy_eth71_peer() {
1968 use futures::task::noop_waker;
1969 use std::task::{Context, Poll};
1970
1971 let manager = PeersManager::new(PeersConfig::default());
1972 let mut fetcher =
1973 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
1974
1975 let peer_71 = B512::random();
1976 let caps_71 = Arc::new(Capabilities::from(vec![Capability::new("eth".into(), 71)]));
1977 fetcher.new_active_peer(NewPeerInfo {
1978 peer_id: peer_71,
1979 best_hash: B256::random(),
1980 best_number: 100,
1981 capabilities: caps_71,
1982 timeout: Arc::new(AtomicU64::new(10)),
1983 range_info: None,
1984 supports_snap: false,
1985 });
1986 fetcher.peers.get_mut(&peer_71).expect("peer exists").state = PeerState::GetBlockHeaders;
1987
1988 let (tx, _rx) = oneshot::channel();
1989 fetcher
1990 .download_requests_tx
1991 .send(DownloadRequest::GetBlockAccessLists {
1992 request: vec![],
1993 response: tx,
1994 priority: Priority::Normal,
1995 requirement: BalRequirement::Optional,
1996 })
1997 .unwrap();
1998
1999 let waker = noop_waker();
2000 let mut cx = Context::from_waker(&waker);
2001
2002 assert!(matches!(fetcher.poll(&mut cx), Poll::Pending));
2003 assert_eq!(fetcher.queued_requests.len(), 1);
2004 }
2005
2006 #[tokio::test]
2007 async fn test_queued_optional_bal_request_rejected_after_eth71_disconnect() {
2008 use futures::task::noop_waker;
2009 use std::task::{Context, Poll};
2010
2011 let manager = PeersManager::new(PeersConfig::default());
2012 let mut fetcher =
2013 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
2014
2015 let peer_old = B512::random();
2016 let caps_old = Arc::new(Capabilities::new(vec![]));
2017 fetcher.new_active_peer(NewPeerInfo {
2018 peer_id: peer_old,
2019 best_hash: B256::random(),
2020 best_number: 100,
2021 capabilities: caps_old,
2022 timeout: Arc::new(AtomicU64::new(10)),
2023 range_info: None,
2024 supports_snap: false,
2025 });
2026
2027 let peer_71 = B512::random();
2028 let caps_71 = Arc::new(Capabilities::from(vec![Capability::new("eth".into(), 71)]));
2029 fetcher.new_active_peer(NewPeerInfo {
2030 peer_id: peer_71,
2031 best_hash: B256::random(),
2032 best_number: 100,
2033 capabilities: caps_71,
2034 timeout: Arc::new(AtomicU64::new(10)),
2035 range_info: None,
2036 supports_snap: false,
2037 });
2038 fetcher.peers.get_mut(&peer_71).expect("peer exists").state = PeerState::GetBlockHeaders;
2039
2040 let (tx, rx) = oneshot::channel();
2041 fetcher
2042 .download_requests_tx
2043 .send(DownloadRequest::GetBlockAccessLists {
2044 request: vec![],
2045 response: tx,
2046 priority: Priority::Normal,
2047 requirement: BalRequirement::Optional,
2048 })
2049 .unwrap();
2050
2051 let waker = noop_waker();
2052 let mut cx = Context::from_waker(&waker);
2053
2054 assert!(matches!(fetcher.poll(&mut cx), Poll::Pending));
2055 assert_eq!(fetcher.queued_requests.len(), 1);
2056
2057 fetcher.on_session_closed(&peer_71);
2058
2059 assert!(matches!(fetcher.poll(&mut cx), Poll::Pending));
2060 assert!(fetcher.queued_requests.is_empty());
2061 assert_eq!(rx.await.unwrap().unwrap_err(), RequestError::UnsupportedCapability);
2062 }
2063
2064 #[tokio::test]
2065 async fn test_next_best_peer_snap_no_support() {
2066 let manager = PeersManager::new(PeersConfig::default());
2067 let mut fetcher =
2068 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
2069
2070 let peer = B512::random();
2071 fetcher.new_active_peer(NewPeerInfo {
2072 peer_id: peer,
2073 best_hash: B256::random(),
2074 best_number: 100,
2075 capabilities: Arc::new(Capabilities::new(vec![])),
2076 timeout: Arc::new(AtomicU64::new(10)),
2077 range_info: None,
2078 supports_snap: false,
2079 });
2080
2081 assert_eq!(fetcher.next_best_peer(BestPeerRequirements::SupportsSnap), None);
2082 }
2083
2084 #[tokio::test]
2085 async fn test_next_best_peer_snap_filters_correctly() {
2086 let manager = PeersManager::new(PeersConfig::default());
2087 let mut fetcher =
2088 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
2089
2090 let peer_no_snap = B512::random();
2091 let peer_with_snap = B512::random();
2092
2093 fetcher.new_active_peer(NewPeerInfo {
2094 peer_id: peer_no_snap,
2095 best_hash: B256::random(),
2096 best_number: 100,
2097 capabilities: Arc::new(Capabilities::new(vec![])),
2098 timeout: Arc::new(AtomicU64::new(5)),
2099 range_info: None,
2100 supports_snap: false,
2101 });
2102 fetcher.new_active_peer(NewPeerInfo {
2103 peer_id: peer_with_snap,
2104 best_hash: B256::random(),
2105 best_number: 100,
2106 capabilities: Arc::new(Capabilities::new(vec![])),
2107 timeout: Arc::new(AtomicU64::new(50)),
2108 range_info: None,
2109 supports_snap: true,
2110 });
2111
2112 assert_eq!(
2114 fetcher.next_best_peer(BestPeerRequirements::SupportsSnap),
2115 Some(peer_with_snap)
2116 );
2117 }
2118
2119 #[tokio::test]
2120 async fn test_snap_request_rejected_without_snap_peer() {
2121 use futures::task::noop_waker;
2122 use std::task::{Context, Poll};
2123
2124 let manager = PeersManager::new(PeersConfig::default());
2125 let mut fetcher =
2126 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
2127
2128 fetcher.new_active_peer(NewPeerInfo {
2130 peer_id: B512::random(),
2131 best_hash: B256::random(),
2132 best_number: 100,
2133 capabilities: Arc::new(Capabilities::new(vec![])),
2134 timeout: Arc::new(AtomicU64::new(10)),
2135 range_info: None,
2136 supports_snap: false,
2137 });
2138
2139 let (tx, rx) = oneshot::channel();
2140 fetcher
2141 .download_requests_tx
2142 .send(DownloadRequest::GetSnap {
2143 request: SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
2144 request_id: 0,
2145 root_hash: B256::ZERO,
2146 starting_hash: B256::ZERO,
2147 limit_hash: B256::ZERO,
2148 response_bytes: 0,
2149 }),
2150 response: tx,
2151 priority: Priority::Normal,
2152 })
2153 .unwrap();
2154
2155 let waker = noop_waker();
2156 let mut cx = Context::from_waker(&waker);
2157
2158 assert!(matches!(fetcher.poll(&mut cx), Poll::Pending));
2159 assert!(fetcher.queued_requests.is_empty());
2160 assert_eq!(rx.await.unwrap().unwrap_err(), RequestError::UnsupportedCapability);
2161 }
2162
2163 #[test_case(SnapResponse::AccountRange(AccountRangeMessage {
2164 request_id: 1, accounts: vec![], proof: vec![Bytes::from_static(&[0xc0])],
2165 }); "account proof")]
2166 #[test_case(SnapResponse::StorageRanges(StorageRangesMessage {
2167 request_id: 1, slots: vec![], proof: vec![Bytes::from_static(&[0xc0])],
2168 }); "storage proof")]
2169 #[test_case(SnapResponse::ByteCodes(ByteCodesMessage {
2170 request_id: 1, codes: vec![Bytes::new()],
2171 }); "empty bytecode")]
2172 #[test_case(SnapResponse::BlockAccessLists(BlockAccessListsMessage {
2173 request_id: 1,
2174 block_access_lists: BlockAccessLists(vec![Some(Bytes::from_static(&[alloy_rlp::EMPTY_LIST_CODE]))]),
2175 }); "present empty BAL")]
2176 #[tokio::test]
2177 async fn test_snap_response_triggers_followup(resp: SnapResponse) {
2178 let manager = PeersManager::new(PeersConfig::default());
2179 let mut fetcher =
2180 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
2181
2182 let peer_id = B512::random();
2183 fetcher.new_active_peer(NewPeerInfo {
2184 peer_id,
2185 best_hash: B256::random(),
2186 best_number: 100,
2187 capabilities: Arc::new(Capabilities::new(vec![])),
2188 timeout: Arc::new(AtomicU64::new(10)),
2189 range_info: None,
2190 supports_snap: true,
2191 });
2192
2193 let (followup_tx, _followup_rx) = oneshot::channel();
2195 fetcher.queued_requests.push_back(DownloadRequest::GetSnap {
2196 request: SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
2197 request_id: 0,
2198 root_hash: B256::ZERO,
2199 starting_hash: B256::ZERO,
2200 limit_hash: B256::ZERO,
2201 response_bytes: 0,
2202 }),
2203 response: followup_tx,
2204 priority: Priority::Normal,
2205 });
2206
2207 let (tx, mut rx) = oneshot::channel();
2208 fetcher.inflight_snap_requests.insert(peer_id, Request { request: (), response: tx });
2209 fetcher.peers.get_mut(&peer_id).unwrap().state = PeerState::GetSnap;
2210
2211 let outcome = fetcher.on_snap_response(peer_id, Ok(resp));
2212
2213 assert!(matches!(outcome, Some(BlockResponseOutcome::Request(pid, _)) if pid == peer_id));
2214 assert!(rx.try_recv().is_ok());
2215 }
2216
2217 #[tokio::test]
2218 async fn test_queued_snap_request_rejected_after_last_peer_disconnects() {
2219 use futures::task::noop_waker;
2220 use std::task::{Context, Poll};
2221
2222 let manager = PeersManager::new(PeersConfig::default());
2223 let mut fetcher =
2224 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
2225
2226 let peer = B512::random();
2228 fetcher.new_active_peer(NewPeerInfo {
2229 peer_id: peer,
2230 best_hash: B256::random(),
2231 best_number: 100,
2232 capabilities: Arc::new(Capabilities::new(vec![])),
2233 timeout: Arc::new(AtomicU64::new(10)),
2234 range_info: None,
2235 supports_snap: true,
2236 });
2237 fetcher.peers.get_mut(&peer).expect("peer exists").state = PeerState::GetBlockHeaders;
2238
2239 let (tx, rx) = oneshot::channel();
2240 fetcher
2241 .download_requests_tx
2242 .send(DownloadRequest::GetSnap {
2243 request: SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
2244 request_id: 0,
2245 root_hash: B256::ZERO,
2246 starting_hash: B256::ZERO,
2247 limit_hash: B256::ZERO,
2248 response_bytes: 0,
2249 }),
2250 response: tx,
2251 priority: Priority::Normal,
2252 })
2253 .unwrap();
2254
2255 let waker = noop_waker();
2256 let mut cx = Context::from_waker(&waker);
2257
2258 assert!(matches!(fetcher.poll(&mut cx), Poll::Pending));
2259 assert_eq!(fetcher.queued_requests.len(), 1);
2260
2261 fetcher.on_session_closed(&peer);
2264
2265 assert!(matches!(fetcher.poll(&mut cx), Poll::Pending));
2266 assert!(fetcher.queued_requests.is_empty());
2267 assert_eq!(rx.await.unwrap().unwrap_err(), RequestError::UnsupportedCapability);
2268 }
2269
2270 #[tokio::test]
2271 async fn unavailable_snap_responses_yield_to_another_peer() {
2272 let responses = [
2273 SnapResponse::BlockAccessLists(BlockAccessListsMessage {
2274 request_id: 1,
2275 block_access_lists: BlockAccessLists(vec![None, None]),
2276 }),
2277 SnapResponse::AccountRange(AccountRangeMessage {
2278 request_id: 1,
2279 accounts: vec![],
2280 proof: vec![],
2281 }),
2282 SnapResponse::StorageRanges(StorageRangesMessage {
2283 request_id: 1,
2284 slots: vec![],
2285 proof: vec![],
2286 }),
2287 SnapResponse::ByteCodes(ByteCodesMessage { request_id: 1, codes: vec![] }),
2288 SnapResponse::BlockAccessLists(BlockAccessListsMessage {
2289 request_id: 1,
2290 block_access_lists: BlockAccessLists(vec![]),
2291 }),
2292 SnapResponse::BlockAccessLists(BlockAccessListsMessage {
2293 request_id: 1,
2294 block_access_lists: BlockAccessLists(vec![
2295 None,
2296 Some(Bytes::from_static(&[alloy_rlp::EMPTY_LIST_CODE])),
2297 ]),
2298 }),
2299 ];
2300 for response in responses {
2301 let manager = PeersManager::new(PeersConfig::default());
2302 let mut fetcher =
2303 StateFetcher::<EthNetworkPrimitives>::new(manager.handle(), Default::default());
2304 let fast = B512::random();
2305 let other = B512::random();
2306 for (peer_id, timeout) in [(fast, 10), (other, 50)] {
2307 fetcher.new_active_peer(NewPeerInfo {
2308 peer_id,
2309 best_hash: B256::random(),
2310 best_number: 100,
2311 capabilities: Arc::new(Capabilities::new(vec![])),
2312 timeout: Arc::new(AtomicU64::new(timeout)),
2313 range_info: None,
2314 supports_snap: true,
2315 });
2316 }
2317 assert_eq!(fetcher.next_best_peer(BestPeerRequirements::SupportsSnap), Some(fast));
2318 let (tx, rx) = oneshot::channel();
2319 fetcher.inflight_snap_requests.insert(fast, Request { request: (), response: tx });
2320 fetcher.peers.get_mut(&fast).unwrap().state = PeerState::GetSnap;
2321 let (followup_tx, _followup_rx) = oneshot::channel();
2322 fetcher.queued_requests.push_back(DownloadRequest::GetSnap {
2323 request: SnapProtocolMessage::GetAccountRange(GetAccountRangeMessage {
2324 request_id: 2,
2325 root_hash: B256::random(),
2326 starting_hash: B256::ZERO,
2327 limit_hash: B256::repeat_byte(0xff),
2328 response_bytes: 1024,
2329 }),
2330 response: followup_tx,
2331 priority: Priority::Normal,
2332 });
2333
2334 assert_eq!(fetcher.on_snap_response(fast, Ok(response.clone())), None);
2335 assert_eq!(rx.await.unwrap().unwrap().1, response);
2336 assert_eq!(
2337 fetcher.next_best_peer(BestPeerRequirements::SupportsSnap),
2338 Some(other),
2339 "an unavailable response must not monopolize requests: {response:?}"
2340 );
2341 assert!(matches!(
2342 fetcher.poll_action(),
2343 PollAction::Ready(FetchAction::BlockRequest { peer_id, .. }) if peer_id == other
2344 ));
2345 }
2346 }
2347}