1use super::task::TaskDownloader;
4use crate::metrics::HeaderDownloaderMetrics;
5use alloy_consensus::BlockHeader;
6use alloy_eips::BlockHashOrNumber;
7use alloy_primitives::{BlockNumber, Sealable, B256};
8use futures::{stream::Stream, FutureExt};
9use futures_util::{stream::FuturesUnordered, StreamExt};
10use rayon::prelude::*;
11use reth_config::config::HeadersConfig;
12use reth_consensus::HeaderValidator;
13use reth_network_p2p::{
14 error::{DownloadError, DownloadResult, PeerRequestResult},
15 headers::{
16 client::{HeadersClient, HeadersRequest},
17 downloader::{validate_header_download, HeaderDownloader, SyncTarget},
18 error::{HeadersDownloaderError, HeadersDownloaderResult},
19 },
20 priority::Priority,
21};
22use reth_network_peers::PeerId;
23use reth_primitives_traits::{GotExpected, SealedHeader};
24use reth_tasks::Runtime;
25use std::{
26 cmp::{Ordering, Reverse},
27 collections::{binary_heap::PeekMut, BinaryHeap},
28 future::Future,
29 pin::Pin,
30 sync::Arc,
31 task::{ready, Context, Poll},
32};
33use thiserror::Error;
34use tracing::{debug, error, trace};
35
36const REQUESTS_PER_PEER_MULTIPLIER: usize = 5;
40
41#[derive(Error, Debug)]
43enum ReverseHeadersDownloaderError<H: Sealable> {
44 #[error(transparent)]
45 Downloader(#[from] HeadersDownloaderError<H>),
46 #[error(transparent)]
47 Response(#[from] Box<HeadersResponseError>),
48}
49
50impl<H: Sealable> From<HeadersResponseError> for ReverseHeadersDownloaderError<H> {
51 fn from(value: HeadersResponseError) -> Self {
52 Self::Response(Box::new(value))
53 }
54}
55
56#[must_use = "Stream does nothing unless polled"]
68#[derive(Debug)]
69pub struct ReverseHeadersDownloader<H: HeadersClient> {
70 consensus: Arc<dyn HeaderValidator<H::Header>>,
72 client: Arc<H>,
74 local_head: Option<SealedHeader<H::Header>>,
76 sync_target: Option<SyncTargetBlock>,
78 next_request_block_number: u64,
80 lowest_validated_header: Option<SealedHeader<H::Header>>,
82 next_chain_tip_block_number: u64,
84 request_limit: u64,
86 min_concurrent_requests: usize,
88 max_concurrent_requests: usize,
90 stream_batch_size: usize,
92 max_buffered_responses: usize,
94 sync_target_request: Option<HeadersRequestFuture<H::Output>>,
99 in_progress_queue: FuturesUnordered<HeadersRequestFuture<H::Output>>,
101 buffered_responses: BinaryHeap<OrderedHeadersResponse<H::Header>>,
103 queued_validated_headers: Vec<SealedHeader<H::Header>>,
107 metrics: HeaderDownloaderMetrics,
109}
110
111impl<H> ReverseHeadersDownloader<H>
114where
115 H: HeadersClient<Header: reth_primitives_traits::BlockHeader> + 'static,
116{
117 pub fn builder() -> ReverseHeadersDownloaderBuilder {
119 ReverseHeadersDownloaderBuilder::default()
120 }
121
122 #[inline]
124 fn local_block_number(&self) -> Option<BlockNumber> {
125 self.local_head.as_ref().map(|h| h.number())
126 }
127
128 #[inline]
134 fn existing_local_block_number(&self) -> BlockNumber {
135 self.local_head.as_ref().expect("is initialized").number()
136 }
137
138 #[inline]
144 fn existing_sync_target(&self) -> SyncTargetBlock {
145 self.sync_target.as_ref().expect("is initialized").clone()
146 }
147
148 #[inline]
153 fn concurrent_request_limit(&self) -> usize {
154 let num_peers = self.client.num_connected_peers();
155
156 let dynamic_target = num_peers * REQUESTS_PER_PEER_MULTIPLIER;
159 let max_dynamic = dynamic_target.max(self.min_concurrent_requests);
160
161 if num_peers < self.min_concurrent_requests {
163 return max_dynamic
164 }
165
166 max_dynamic.min(self.max_concurrent_requests)
167 }
168
169 fn next_request(&mut self) -> Option<HeadersRequest> {
175 if let Some(local_head) = self.local_block_number() &&
176 self.next_request_block_number > local_head
177 {
178 let request =
179 calc_next_request(local_head, self.next_request_block_number, self.request_limit);
180 self.next_request_block_number -= request.limit;
183
184 return Some(request)
185 }
186
187 None
188 }
189
190 fn lowest_validated_header(&self) -> Option<&SealedHeader<H::Header>> {
200 self.queued_validated_headers.last().or(self.lowest_validated_header.as_ref())
201 }
202
203 fn reset(&mut self) {
207 debug!(target: "downloaders::headers", "Resetting headers downloader");
208 self.next_request_block_number = 0;
209 self.next_chain_tip_block_number = 0;
210 self.sync_target.take();
211 }
212
213 fn validate_sync_target(
215 &self,
216 header: &SealedHeader<H::Header>,
217 request: HeadersRequest,
218 peer_id: PeerId,
219 ) -> Result<(), Box<HeadersResponseError>> {
220 match self.existing_sync_target() {
221 SyncTargetBlock::Hash(hash) | SyncTargetBlock::HashAndNumber { hash, .. }
222 if header.hash() != hash =>
223 {
224 Err(Box::new(HeadersResponseError {
225 request,
226 peer_id: Some(peer_id),
227 error: DownloadError::InvalidTip(
228 GotExpected { got: header.hash(), expected: hash }.into(),
229 ),
230 }))
231 }
232 SyncTargetBlock::Number(number) if header.number() != number => {
233 Err(Box::new(HeadersResponseError {
234 request,
235 peer_id: Some(peer_id),
236 error: DownloadError::InvalidTipNumber(GotExpected {
237 got: header.number(),
238 expected: number,
239 }),
240 }))
241 }
242 _ => Ok(()),
243 }
244 }
245
246 fn process_next_headers(
254 &mut self,
255 request: HeadersRequest,
256 headers: Vec<H::Header>,
257 peer_id: PeerId,
258 ) -> Result<(), ReverseHeadersDownloaderError<H::Header>> {
259 let mut validated = Vec::with_capacity(headers.len());
260
261 let sealed_headers =
262 headers.into_par_iter().map(SealedHeader::seal_slow).collect::<Vec<_>>();
263 for parent in sealed_headers {
264 if let Some(validated_header) =
266 validated.last().or_else(|| self.lowest_validated_header())
267 {
268 if let Err(error) = self.validate(validated_header, &parent) {
269 trace!(target: "downloaders::headers", %error ,"Failed to validate header");
270 return Err(
271 HeadersResponseError { request, peer_id: Some(peer_id), error }.into()
272 )
273 }
274 } else {
275 self.validate_sync_target(&parent, request.clone(), peer_id)?;
276 }
277
278 validated.push(parent);
279 }
280
281 if let Some((last_header, head)) = validated
283 .last_mut()
284 .zip(self.local_head.as_ref())
285 .filter(|(last, head)| last.number() == head.number() + 1)
286 {
287 if let Err(error) = self.consensus.validate_header(&*last_header) {
289 trace!(target: "downloaders::headers", %error, "Failed to validate header");
290 return Err(HeadersResponseError {
291 request,
292 peer_id: Some(peer_id),
293 error: DownloadError::HeaderValidation {
294 hash: head.hash(),
295 number: head.number(),
296 error: Box::new(error),
297 },
298 }
299 .into())
300 }
301
302 if let Err(error) = self.consensus.validate_header_against_parent(&*last_header, head) {
309 let local_head = head.clone();
310 error!(target: "downloaders::headers", %error, number = last_header.number(), hash = ?last_header.hash(), "Header cannot be attached to known canonical chain");
312
313 self.reset();
318
319 return Err(HeadersDownloaderError::DetachedHead {
320 local_head: Box::new(local_head),
321 header: Box::new(last_header.clone()),
322 error: Box::new(error),
323 }
324 .into())
325 }
326 }
327
328 self.next_chain_tip_block_number =
330 validated.last().expect("exists").number().saturating_sub(1);
331 self.queued_validated_headers.extend(validated);
332
333 Ok(())
334 }
335
336 fn on_block_number_update(&mut self, target_block_number: u64, next_block: u64) {
347 if let Some(old_target) =
349 self.sync_target.as_mut().and_then(|t| t.replace_number(target_block_number))
350 {
351 if target_block_number > old_target {
352 self.next_request_block_number = next_block;
355 self.next_chain_tip_block_number = next_block;
356 self.clear();
357 } else {
358 let skip = self
360 .queued_validated_headers
361 .iter()
362 .take_while(|last| last.number() > target_block_number)
363 .count();
364 self.queued_validated_headers.drain(..skip);
366 }
367 } else {
368 self.next_request_block_number = next_block;
370 self.next_chain_tip_block_number = next_block;
371 }
372 }
373
374 fn on_sync_target_outcome(
376 &mut self,
377 response: HeadersRequestOutcome<H::Header>,
378 ) -> Result<(), ReverseHeadersDownloaderError<H::Header>> {
379 let sync_target = self.existing_sync_target();
380 let HeadersRequestOutcome { request, outcome } = response;
381 match outcome {
382 Ok(res) => {
383 let (peer_id, mut headers) = res.split();
384
385 self.metrics.total_downloaded.increment(headers.len() as u64);
387
388 headers.sort_unstable_by_key(|h| Reverse(h.number()));
390
391 if headers.is_empty() {
392 return Err(HeadersResponseError {
393 request,
394 peer_id: Some(peer_id),
395 error: DownloadError::EmptyResponse,
396 }
397 .into())
398 }
399
400 let header = headers.swap_remove(0);
401 let target = SealedHeader::seal_slow(header);
402
403 match sync_target {
404 SyncTargetBlock::Hash(hash) | SyncTargetBlock::HashAndNumber { hash, .. } => {
405 if target.hash() != hash {
406 return Err(HeadersResponseError {
407 request,
408 peer_id: Some(peer_id),
409 error: DownloadError::InvalidTip(
410 GotExpected { got: target.hash(), expected: hash }.into(),
411 ),
412 }
413 .into())
414 }
415 }
416 SyncTargetBlock::Number(number) => {
417 if target.number() != number {
418 return Err(HeadersResponseError {
419 request,
420 peer_id: Some(peer_id),
421 error: DownloadError::InvalidTipNumber(GotExpected {
422 got: target.number(),
423 expected: number,
424 }),
425 }
426 .into())
427 }
428 }
429 }
430
431 trace!(target: "downloaders::headers", head=?self.local_block_number(), hash=?target.hash(), number=%target.number(), "Received sync target");
432
433 let parent_block_number = target.number().saturating_sub(1);
435 self.on_block_number_update(target.number(), parent_block_number);
436
437 self.queued_validated_headers.push(target);
438
439 self.try_validate_buffered()
441 .map(Err::<(), ReverseHeadersDownloaderError<H::Header>>)
442 .transpose()?;
443
444 Ok(())
445 }
446 Err(err) => {
447 Err(HeadersResponseError { request, peer_id: None, error: err.into() }.into())
448 }
449 }
450 }
451
452 fn on_headers_outcome(
454 &mut self,
455 response: HeadersRequestOutcome<H::Header>,
456 ) -> Result<(), ReverseHeadersDownloaderError<H::Header>> {
457 let requested_block_number = response.block_number();
458 let HeadersRequestOutcome { request, outcome } = response;
459
460 match outcome {
461 Ok(res) => {
462 let (peer_id, mut headers) = res.split();
463
464 self.metrics.total_downloaded.increment(headers.len() as u64);
466
467 trace!(target: "downloaders::headers", len=%headers.len(), "Received headers response");
468
469 if headers.is_empty() {
470 return Err(HeadersResponseError {
471 request,
472 peer_id: Some(peer_id),
473 error: DownloadError::EmptyResponse,
474 }
475 .into())
476 }
477
478 let received_headers = headers.len() as u64;
479 if received_headers > request.limit {
480 return Err(HeadersResponseError {
481 peer_id: Some(peer_id),
482 error: DownloadError::HeadersResponseTooLong(GotExpected {
483 got: received_headers,
484 expected: request.limit,
485 }),
486 request,
487 }
488 .into())
489 }
490 let missing_headers = request.limit - received_headers;
491
492 headers.sort_unstable_by_key(|h| Reverse(h.number()));
494
495 let highest = &headers[0];
497
498 trace!(target: "downloaders::headers", requested_block_number, highest=?highest.number(), "Validating non-empty headers response");
499
500 if highest.number() != requested_block_number {
501 return Err(HeadersResponseError {
502 request,
503 peer_id: Some(peer_id),
504 error: DownloadError::HeadersResponseStartBlockMismatch(GotExpected {
505 got: highest.number(),
506 expected: requested_block_number,
507 }),
508 }
509 .into())
510 }
511
512 if highest.number() == self.next_chain_tip_block_number {
514 self.process_next_headers(request, headers, peer_id)?;
516 self.requeue_missing_headers(
520 requested_block_number,
521 received_headers,
522 missing_headers,
523 );
524 self.try_validate_buffered()
526 .map(Err::<(), ReverseHeadersDownloaderError<H::Header>>)
527 .transpose()?;
528 } else if highest.number() > self.existing_local_block_number() {
529 self.metrics.buffered_responses.increment(1.);
530 self.buffered_responses.push(OrderedHeadersResponse {
532 headers,
533 request,
534 peer_id,
535 });
536 self.requeue_missing_headers(
537 requested_block_number,
538 received_headers,
539 missing_headers,
540 );
541 }
542
543 Ok(())
544 }
545 Err(err) => {
548 trace!(target: "downloaders::headers", %err, "Response error");
549 Err(HeadersResponseError { request, peer_id: None, error: err.into() }.into())
550 }
551 }
552 }
553
554 fn penalize_peer(&self, peer_id: Option<PeerId>, error: &DownloadError) {
555 if let Some(peer_id) = peer_id {
557 trace!(target: "downloaders::headers", ?peer_id, %error, "Penalizing peer");
558 self.client.report_bad_message(peer_id);
559 }
560 }
561
562 fn on_headers_error(&self, err: Box<HeadersResponseError>) {
566 let HeadersResponseError { request, peer_id, error } = *err;
567
568 self.penalize_peer(peer_id, &error);
569
570 self.metrics.increment_errors(&error);
572
573 self.submit_request(request, Priority::High);
575 }
576
577 fn try_validate_buffered(&mut self) -> Option<ReverseHeadersDownloaderError<H::Header>> {
581 loop {
582 let next_response = self.buffered_responses.peek_mut()?;
584 let next_block_number = next_response.block_number();
585 match next_block_number.cmp(&self.next_chain_tip_block_number) {
586 Ordering::Less => return None,
587 Ordering::Equal => {
588 let OrderedHeadersResponse { headers, request, peer_id } =
589 PeekMut::pop(next_response);
590 self.metrics.buffered_responses.decrement(1.);
591
592 if let Err(err) = self.process_next_headers(request, headers, peer_id) {
593 return Some(err)
594 }
595 }
596 Ordering::Greater => {
597 self.metrics.buffered_responses.decrement(1.);
598 PeekMut::pop(next_response);
599 }
600 }
601 }
602 }
603
604 const fn get_sync_target_request(&self, start: BlockHashOrNumber) -> HeadersRequest {
606 HeadersRequest::falling(start, 1)
607 }
608
609 fn submit_request(&self, request: HeadersRequest, priority: Priority) {
611 trace!(target: "downloaders::headers", ?request, "Submitting headers request");
612 self.in_progress_queue.push(self.request_fut(request, priority));
613 self.metrics.in_flight_requests.increment(1.);
614 }
615
616 fn requeue_missing_headers(
622 &self,
623 requested_block_number: u64,
624 received_headers: u64,
625 missing_headers: u64,
626 ) {
627 if missing_headers > 0 {
628 self.metrics.partial_responses.increment(1);
629 self.submit_request(
630 HeadersRequest::falling(
631 (requested_block_number - received_headers).into(),
632 missing_headers,
633 ),
634 Priority::High,
635 );
636 }
637 }
638
639 fn request_fut(
640 &self,
641 request: HeadersRequest,
642 priority: Priority,
643 ) -> HeadersRequestFuture<H::Output> {
644 let client = Arc::clone(&self.client);
645 HeadersRequestFuture {
646 request: Some(request.clone()),
647 fut: client.get_headers_with_priority(request, priority),
648 }
649 }
650
651 fn validate(
653 &self,
654 header: &SealedHeader<H::Header>,
655 parent: &SealedHeader<H::Header>,
656 ) -> DownloadResult<()> {
657 validate_header_download(&self.consensus, header, parent)
658 }
659
660 fn clear(&mut self) {
662 self.lowest_validated_header.take();
663 self.queued_validated_headers = Vec::new();
664 self.buffered_responses = BinaryHeap::new();
665 self.in_progress_queue.clear();
666
667 self.metrics.in_flight_requests.set(0.);
668 self.metrics.buffered_responses.set(0.);
669 }
670
671 fn split_next_batch(&mut self) -> Vec<SealedHeader<H::Header>> {
673 let batch_size = self.stream_batch_size.min(self.queued_validated_headers.len());
674 let mut rem = self.queued_validated_headers.split_off(batch_size);
675 std::mem::swap(&mut rem, &mut self.queued_validated_headers);
676 rem.shrink_to_fit();
692 rem
693 }
694}
695
696impl<H> ReverseHeadersDownloader<H>
697where
698 H: HeadersClient,
699 Self: HeaderDownloader + 'static,
700{
701 pub fn into_task_with(
703 self,
704 runtime: &Runtime,
705 ) -> TaskDownloader<<Self as HeaderDownloader>::Header> {
706 TaskDownloader::spawn_with(self, runtime)
707 }
708}
709
710impl<H> HeaderDownloader for ReverseHeadersDownloader<H>
711where
712 H: HeadersClient<Header: reth_primitives_traits::BlockHeader> + 'static,
713{
714 type Header = H::Header;
715
716 fn update_local_head(&mut self, head: SealedHeader<H::Header>) {
717 while self
719 .queued_validated_headers
720 .last()
721 .is_some_and(|last| last.number() <= head.number())
722 {
723 self.queued_validated_headers.pop();
725 }
726 trace!(
727 target: "downloaders::headers",
728 head=?head.num_hash(),
729 "Updating local head"
730 );
731 self.local_head = Some(head);
733 }
734
735 fn update_sync_target(&mut self, target: SyncTarget) {
737 let current_tip = self.sync_target.as_ref().and_then(|t| t.hash());
738 trace!(
739 target: "downloaders::headers",
740 sync_target=?target,
741 current_tip=?current_tip,
742 "Updating sync target"
743 );
744 match target {
745 SyncTarget::Tip(tip) => {
746 if Some(tip) != current_tip {
747 trace!(target: "downloaders::headers", current=?current_tip, new=?tip, "Update sync target");
748 let new_sync_target = SyncTargetBlock::from_hash(tip);
749
750 if let Some(target_number) = self
753 .queued_validated_headers
754 .first()
755 .filter(|h| h.hash() == tip)
756 .map(|h| h.number())
757 {
758 self.sync_target = Some(new_sync_target.with_number(target_number));
759 return
760 }
761
762 trace!(target: "downloaders::headers", new=?target, "Request new sync target");
763 self.metrics.out_of_order_requests.increment(1);
764 self.sync_target = Some(new_sync_target);
765 self.sync_target_request = Some(
766 self.request_fut(self.get_sync_target_request(tip.into()), Priority::High),
767 );
768 }
769 }
770 SyncTarget::Gap(existing) => {
771 let target = existing.parent;
772 if Some(target) != current_tip {
773 self.sync_target_request.take();
775 let parent_block_number = existing.block.number.saturating_sub(1);
778
779 trace!(target: "downloaders::headers", current=?current_tip, new=?target, %parent_block_number, "Updated sync target");
780
781 self.sync_target = match self.sync_target.take() {
783 Some(sync_target) => Some(sync_target.with_hash(target)),
784 None => Some(SyncTargetBlock::from_hash(target)),
785 };
786 self.on_block_number_update(parent_block_number, parent_block_number);
787 }
788 }
789 SyncTarget::TipNum(num) => {
790 let current_tip_num = self.sync_target.as_ref().and_then(|t| t.number());
791 if Some(num) != current_tip_num {
792 trace!(target: "downloaders::headers", %num, "Updating sync target based on num");
793 self.sync_target = Some(SyncTargetBlock::from_number(num));
795 self.sync_target_request = Some(
796 self.request_fut(self.get_sync_target_request(num.into()), Priority::High),
797 );
798 }
799 }
800 }
801 }
802
803 fn set_batch_size(&mut self, batch_size: usize) {
804 self.stream_batch_size = batch_size;
805 }
806}
807
808impl<H> Stream for ReverseHeadersDownloader<H>
809where
810 H: HeadersClient<Header: reth_primitives_traits::BlockHeader> + 'static,
811{
812 type Item = HeadersDownloaderResult<Vec<SealedHeader<H::Header>>, H::Header>;
813
814 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
815 let this = self.get_mut();
816
817 if this.local_head.is_none() || this.sync_target.is_none() {
820 trace!(
821 target: "downloaders::headers",
822 head=?this.local_block_number(),
823 sync_target=?this.sync_target,
824 "The downloader sync boundaries have not been set"
825 );
826 return Poll::Pending
827 }
828
829 while let Some(mut req) = this.sync_target_request.take() {
832 match req.poll_unpin(cx) {
833 Poll::Ready(outcome) => {
834 match this.on_sync_target_outcome(outcome) {
835 Ok(()) => break,
836 Err(ReverseHeadersDownloaderError::Response(error)) => {
837 trace!(target: "downloaders::headers", %error, "invalid sync target response");
838 if error.is_channel_closed() {
839 return Poll::Ready(None)
841 }
842
843 this.penalize_peer(error.peer_id, &error.error);
844 this.metrics.increment_errors(&error.error);
845 this.sync_target_request =
846 Some(this.request_fut(error.request, Priority::High));
847 }
848 Err(ReverseHeadersDownloaderError::Downloader(error)) => {
849 this.clear();
850 return Poll::Ready(Some(Err(error)))
851 }
852 };
853 }
854 Poll::Pending => {
855 this.sync_target_request = Some(req);
856 return Poll::Pending
857 }
858 }
859 }
860
861 this.buffered_responses.shrink_to_fit();
863
864 loop {
873 while let Poll::Ready(Some(outcome)) = this.in_progress_queue.poll_next_unpin(cx) {
875 this.metrics.in_flight_requests.decrement(1.);
876 match this.on_headers_outcome(outcome) {
878 Ok(()) => (),
879 Err(ReverseHeadersDownloaderError::Response(error)) => {
880 if error.is_channel_closed() {
881 return Poll::Ready(None)
883 }
884 this.on_headers_error(error);
885 }
886 Err(ReverseHeadersDownloaderError::Downloader(error)) => {
887 this.clear();
888 return Poll::Ready(Some(Err(error)))
889 }
890 };
891 }
892
893 this.buffered_responses.shrink_to_fit();
895
896 let mut progress = false;
898
899 let concurrent_request_limit = this.concurrent_request_limit();
900 while this.in_progress_queue.len() < concurrent_request_limit &&
902 this.buffered_responses.len() < this.max_buffered_responses
903 {
904 if let Some(request) = this.next_request() {
905 trace!(
906 target: "downloaders::headers",
907 "Requesting headers {request:?}"
908 );
909 progress = true;
910 this.submit_request(request, Priority::Normal);
911 } else {
912 break
914 }
915 }
916
917 if this.queued_validated_headers.len() >= this.stream_batch_size {
919 let next_batch = this.split_next_batch();
920
921 if this.queued_validated_headers.is_empty() {
924 this.lowest_validated_header = next_batch.last().cloned();
925 }
926
927 trace!(target: "downloaders::headers", batch=%next_batch.len(), "Returning validated batch");
928
929 this.metrics.total_flushed.increment(next_batch.len() as u64);
930 return Poll::Ready(Some(Ok(next_batch)))
931 }
932
933 if !progress {
934 break
935 }
936 }
937
938 if this.in_progress_queue.is_empty() {
940 let next_batch = this.split_next_batch();
941 if next_batch.is_empty() {
942 this.clear();
943 return Poll::Ready(None)
944 }
945 this.metrics.total_flushed.increment(next_batch.len() as u64);
946 return Poll::Ready(Some(Ok(next_batch)))
947 }
948
949 Poll::Pending
950 }
951}
952
953#[derive(Debug)]
955struct HeadersRequestFuture<F> {
956 request: Option<HeadersRequest>,
957 fut: F,
958}
959
960impl<F, H> Future for HeadersRequestFuture<F>
961where
962 F: Future<Output = PeerRequestResult<Vec<H>>> + Sync + Send + Unpin,
963{
964 type Output = HeadersRequestOutcome<H>;
965
966 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
967 let this = self.get_mut();
968 let outcome = ready!(this.fut.poll_unpin(cx));
969 let request = this.request.take().unwrap();
970
971 Poll::Ready(HeadersRequestOutcome { request, outcome })
972 }
973}
974
975struct HeadersRequestOutcome<H> {
977 request: HeadersRequest,
978 outcome: PeerRequestResult<Vec<H>>,
979}
980
981impl<H> HeadersRequestOutcome<H> {
984 const fn block_number(&self) -> u64 {
985 self.request.start.as_number().expect("is number")
986 }
987}
988
989#[derive(Debug)]
991struct OrderedHeadersResponse<H> {
992 headers: Vec<H>,
993 request: HeadersRequest,
994 peer_id: PeerId,
995}
996
997impl<H> OrderedHeadersResponse<H> {
1000 const fn block_number(&self) -> u64 {
1001 self.request.start.as_number().expect("is number")
1002 }
1003}
1004
1005impl<H> PartialEq for OrderedHeadersResponse<H> {
1006 fn eq(&self, other: &Self) -> bool {
1007 self.block_number() == other.block_number()
1008 }
1009}
1010
1011impl<H> Eq for OrderedHeadersResponse<H> {}
1012
1013impl<H> PartialOrd for OrderedHeadersResponse<H> {
1014 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1015 Some(self.cmp(other))
1016 }
1017}
1018
1019impl<H> Ord for OrderedHeadersResponse<H> {
1020 fn cmp(&self, other: &Self) -> Ordering {
1021 self.block_number().cmp(&other.block_number())
1022 }
1023}
1024
1025#[derive(Debug, Error)]
1027#[error("error requesting headers from peer {peer_id:?}: {error}; request: {request:?}")]
1028struct HeadersResponseError {
1029 request: HeadersRequest,
1030 peer_id: Option<PeerId>,
1031 #[source]
1032 error: DownloadError,
1033}
1034
1035impl HeadersResponseError {
1036 const fn is_channel_closed(&self) -> bool {
1038 if let DownloadError::RequestError(ref err) = self.error {
1039 return err.is_channel_closed()
1040 }
1041 false
1042 }
1043}
1044
1045#[derive(Clone, Debug)]
1048pub enum SyncTargetBlock {
1049 Hash(B256),
1051 Number(u64),
1053 HashAndNumber {
1055 hash: B256,
1057 number: u64,
1059 },
1060}
1061
1062impl SyncTargetBlock {
1063 const fn from_hash(hash: B256) -> Self {
1065 Self::Hash(hash)
1066 }
1067
1068 const fn from_number(num: u64) -> Self {
1070 Self::Number(num)
1071 }
1072
1073 const fn with_hash(self, hash: B256) -> Self {
1075 match self {
1076 Self::Hash(_) => Self::Hash(hash),
1077 Self::Number(number) | Self::HashAndNumber { number, .. } => {
1078 Self::HashAndNumber { hash, number }
1079 }
1080 }
1081 }
1082
1083 const fn with_number(self, number: u64) -> Self {
1085 match self {
1086 Self::Hash(hash) | Self::HashAndNumber { hash, .. } => {
1087 Self::HashAndNumber { hash, number }
1088 }
1089 Self::Number(_) => Self::Number(number),
1090 }
1091 }
1092
1093 const fn replace_number(&mut self, number: u64) -> Option<u64> {
1098 match self {
1099 Self::Hash(hash) => {
1100 *self = Self::HashAndNumber { hash: *hash, number };
1101 None
1102 }
1103 Self::Number(old_number) => {
1104 let res = Some(*old_number);
1105 *self = Self::Number(number);
1106 res
1107 }
1108 Self::HashAndNumber { number: old_number, hash } => {
1109 let res = Some(*old_number);
1110 *self = Self::HashAndNumber { hash: *hash, number };
1111 res
1112 }
1113 }
1114 }
1115
1116 const fn hash(&self) -> Option<B256> {
1118 match self {
1119 Self::Hash(hash) | Self::HashAndNumber { hash, .. } => Some(*hash),
1120 Self::Number(_) => None,
1121 }
1122 }
1123
1124 const fn number(&self) -> Option<u64> {
1126 match self {
1127 Self::Hash(_) => None,
1128 Self::Number(number) | Self::HashAndNumber { number, .. } => Some(*number),
1129 }
1130 }
1131}
1132
1133#[derive(Debug)]
1136pub struct ReverseHeadersDownloaderBuilder {
1137 request_limit: u64,
1139 stream_batch_size: usize,
1141 min_concurrent_requests: usize,
1143 max_concurrent_requests: usize,
1145 max_buffered_responses: usize,
1147}
1148
1149impl ReverseHeadersDownloaderBuilder {
1150 pub fn new(config: HeadersConfig) -> Self {
1153 Self::default()
1154 .request_limit(config.downloader_request_limit)
1155 .min_concurrent_requests(config.downloader_min_concurrent_requests)
1156 .max_concurrent_requests(config.downloader_max_concurrent_requests)
1157 .max_buffered_responses(config.downloader_max_buffered_responses)
1158 .stream_batch_size(config.commit_threshold as usize)
1159 }
1160}
1161
1162impl Default for ReverseHeadersDownloaderBuilder {
1163 fn default() -> Self {
1164 Self {
1165 stream_batch_size: 10_000,
1166 request_limit: 1_000,
1169 max_concurrent_requests: 100,
1170 min_concurrent_requests: 5,
1171 max_buffered_responses: 100,
1172 }
1173 }
1174}
1175
1176impl ReverseHeadersDownloaderBuilder {
1177 pub const fn request_limit(mut self, limit: u64) -> Self {
1182 self.request_limit = limit;
1183 self
1184 }
1185
1186 pub const fn stream_batch_size(mut self, size: usize) -> Self {
1192 self.stream_batch_size = size;
1193 self
1194 }
1195
1196 pub const fn min_concurrent_requests(mut self, min_concurrent_requests: usize) -> Self {
1201 self.min_concurrent_requests = min_concurrent_requests;
1202 self
1203 }
1204
1205 pub const fn max_concurrent_requests(mut self, max_concurrent_requests: usize) -> Self {
1209 self.max_concurrent_requests = max_concurrent_requests;
1210 self
1211 }
1212
1213 pub const fn max_buffered_responses(mut self, max_buffered_responses: usize) -> Self {
1220 self.max_buffered_responses = max_buffered_responses;
1221 self
1222 }
1223
1224 pub fn build<H>(
1227 self,
1228 client: H,
1229 consensus: Arc<dyn HeaderValidator<H::Header>>,
1230 ) -> ReverseHeadersDownloader<H>
1231 where
1232 H: HeadersClient + 'static,
1233 {
1234 let Self {
1235 request_limit,
1236 stream_batch_size,
1237 min_concurrent_requests,
1238 max_concurrent_requests,
1239 max_buffered_responses,
1240 } = self;
1241 ReverseHeadersDownloader {
1242 consensus,
1243 client: Arc::new(client),
1244 local_head: None,
1245 sync_target: None,
1246 next_request_block_number: 0,
1249 next_chain_tip_block_number: 0,
1250 lowest_validated_header: None,
1251 request_limit,
1252 min_concurrent_requests,
1253 max_concurrent_requests,
1254 stream_batch_size,
1255 max_buffered_responses,
1256 sync_target_request: None,
1257 in_progress_queue: Default::default(),
1258 buffered_responses: Default::default(),
1259 queued_validated_headers: Default::default(),
1260 metrics: Default::default(),
1261 }
1262 }
1263}
1264
1265#[inline]
1272fn calc_next_request(
1273 local_head: u64,
1274 next_request_block_number: u64,
1275 request_limit: u64,
1276) -> HeadersRequest {
1277 let diff = next_request_block_number - local_head;
1279 let limit = diff.min(request_limit);
1280 let start = next_request_block_number;
1281 HeadersRequest::falling(start.into(), limit)
1282}
1283
1284#[cfg(test)]
1285mod tests {
1286 use super::*;
1287 use crate::headers::test_utils::child_header;
1288 use alloy_consensus::Header;
1289 use alloy_eips::{eip1898::BlockWithParent, BlockNumHash};
1290 use assert_matches::assert_matches;
1291 use reth_consensus::test_utils::TestConsensus;
1292 use reth_network_p2p::{
1293 download::DownloadClient, error::PeerRequestResult, test_utils::TestHeadersClient,
1294 };
1295 use reth_network_peers::WithPeerId;
1296 use std::sync::{
1297 atomic::{AtomicU64, Ordering as AtomicOrdering},
1298 Mutex,
1299 };
1300
1301 #[derive(Clone, Debug)]
1302 struct CappedHeadersClient {
1303 responses: Arc<Mutex<Vec<Header>>>,
1304 requests: Arc<Mutex<Vec<HeadersRequest>>>,
1305 bad_messages: Arc<AtomicU64>,
1306 response_limit: usize,
1307 }
1308
1309 impl CappedHeadersClient {
1310 fn new(responses: Vec<Header>, response_limit: usize) -> Self {
1311 Self {
1312 responses: Arc::new(Mutex::new(responses)),
1313 requests: Default::default(),
1314 bad_messages: Default::default(),
1315 response_limit,
1316 }
1317 }
1318
1319 fn numeric_requests(&self) -> Vec<(u64, u64)> {
1320 self.requests
1321 .lock()
1322 .unwrap()
1323 .iter()
1324 .filter_map(|request| request.start.as_number().map(|start| (start, request.limit)))
1325 .collect()
1326 }
1327
1328 fn bad_message_count(&self) -> u64 {
1329 self.bad_messages.load(AtomicOrdering::SeqCst)
1330 }
1331 }
1332
1333 impl DownloadClient for CappedHeadersClient {
1334 fn report_bad_message(&self, _peer_id: PeerId) {
1335 self.bad_messages.fetch_add(1, AtomicOrdering::SeqCst);
1336 }
1337
1338 fn num_connected_peers(&self) -> usize {
1339 1
1340 }
1341 }
1342
1343 impl HeadersClient for CappedHeadersClient {
1344 type Header = Header;
1345 type Output = futures::future::Ready<PeerRequestResult<Vec<Header>>>;
1346
1347 fn get_headers_with_priority(
1348 &self,
1349 request: HeadersRequest,
1350 _priority: Priority,
1351 ) -> Self::Output {
1352 self.requests.lock().unwrap().push(request.clone());
1353 let mut responses = self.responses.lock().unwrap();
1354 let response_length = request.limit.min(self.response_limit as u64) as usize;
1355 let response = responses.drain(..response_length).collect();
1356
1357 futures::future::ready(Ok(WithPeerId::new(PeerId::default(), response)))
1358 }
1359 }
1360
1361 #[test]
1363 fn test_replace_number_semantics() {
1364 struct Fixture {
1365 sync_target_block: SyncTargetBlock,
1367 sync_target_option: Option<u64>,
1368
1369 replace_number: u64,
1371
1372 expected_result: Option<u64>,
1374
1375 new_number: u64,
1377 }
1378
1379 let fixtures = vec![
1380 Fixture {
1381 sync_target_block: SyncTargetBlock::Hash(B256::random()),
1382 sync_target_option: None,
1384 replace_number: 1,
1385 expected_result: None,
1386 new_number: 1,
1387 },
1388 Fixture {
1389 sync_target_block: SyncTargetBlock::Number(1),
1390 sync_target_option: Some(1),
1391 replace_number: 2,
1392 expected_result: Some(1),
1393 new_number: 2,
1394 },
1395 Fixture {
1396 sync_target_block: SyncTargetBlock::HashAndNumber {
1397 hash: B256::random(),
1398 number: 1,
1399 },
1400 sync_target_option: Some(1),
1401 replace_number: 2,
1402 expected_result: Some(1),
1403 new_number: 2,
1404 },
1405 ];
1406
1407 for fixture in fixtures {
1408 let mut sync_target_block = fixture.sync_target_block;
1409 let result = sync_target_block.replace_number(fixture.replace_number);
1410 assert_eq!(result, fixture.expected_result);
1411 assert_eq!(sync_target_block.number(), Some(fixture.new_number));
1412
1413 let mut sync_target_option = fixture.sync_target_option;
1414 let option_result = sync_target_option.replace(fixture.replace_number);
1415 assert_eq!(option_result, fixture.expected_result);
1416 assert_eq!(sync_target_option, Some(fixture.new_number));
1417 }
1418 }
1419
1420 #[test]
1422 fn test_sync_target_update() {
1423 let client = Arc::new(TestHeadersClient::default());
1424
1425 let genesis = SealedHeader::default();
1426
1427 let mut downloader = ReverseHeadersDownloaderBuilder::default()
1428 .build(Arc::clone(&client), Arc::new(TestConsensus::default()));
1429 downloader.update_local_head(genesis);
1430 downloader.update_sync_target(SyncTarget::Tip(B256::random()));
1431
1432 downloader.sync_target_request.take();
1433
1434 let target = SyncTarget::Tip(B256::random());
1435 downloader.update_sync_target(target);
1436 assert!(downloader.sync_target_request.is_some());
1437
1438 downloader.sync_target_request.take();
1439 let target = SyncTarget::Gap(BlockWithParent {
1440 block: BlockNumHash::new(0, B256::random()),
1441 parent: Default::default(),
1442 });
1443 downloader.update_sync_target(target);
1444 assert!(downloader.sync_target_request.is_none());
1445 assert_matches!(
1446 downloader.sync_target,
1447 Some(target) => target.number().is_some()
1448 );
1449 }
1450
1451 #[test]
1453 fn test_head_update() {
1454 let client = Arc::new(TestHeadersClient::default());
1455
1456 let header: SealedHeader = SealedHeader::default();
1457
1458 let mut downloader = ReverseHeadersDownloaderBuilder::default()
1459 .build(Arc::clone(&client), Arc::new(TestConsensus::default()));
1460 downloader.update_local_head(header.clone());
1461 downloader.update_sync_target(SyncTarget::Tip(B256::random()));
1462
1463 downloader.queued_validated_headers.push(header.clone());
1464 let mut next = header.as_ref().clone();
1465 next.number += 1;
1466 downloader.update_local_head(SealedHeader::new(next, B256::random()));
1467 assert!(downloader.queued_validated_headers.is_empty());
1468 }
1469
1470 #[test]
1471 fn test_request_calc() {
1472 let local = 0;
1474 let next = 1000;
1475 let batch_size = 2;
1476 let request = calc_next_request(local, next, batch_size);
1477 assert_eq!(request.start, next.into());
1478 assert_eq!(request.limit, batch_size);
1479
1480 let local = 999;
1482 let next = 1000;
1483 let batch_size = 2;
1484 let request = calc_next_request(local, next, batch_size);
1485 assert_eq!(request.start, next.into());
1486 assert_eq!(request.limit, 1);
1487 }
1488
1489 #[test]
1490 fn default_request_limit_matches_sync_config() {
1491 assert_eq!(
1492 ReverseHeadersDownloaderBuilder::default().request_limit,
1493 HeadersConfig::default().downloader_request_limit
1494 );
1495 }
1496
1497 #[test]
1499 fn test_next_request() {
1500 let client = Arc::new(TestHeadersClient::default());
1501
1502 let genesis = SealedHeader::default();
1503
1504 let batch_size = 99;
1505 let start = 1000;
1506 let mut downloader = ReverseHeadersDownloaderBuilder::default()
1507 .request_limit(batch_size)
1508 .build(Arc::clone(&client), Arc::new(TestConsensus::default()));
1509 downloader.update_local_head(genesis);
1510 downloader.update_sync_target(SyncTarget::Tip(B256::random()));
1511
1512 downloader.next_request_block_number = start;
1513
1514 let mut total = 0;
1515 while let Some(req) = downloader.next_request() {
1516 assert_eq!(req.start, (start - total).into());
1517 total += req.limit;
1518 }
1519 assert_eq!(total, start);
1520 assert_eq!(Some(downloader.next_request_block_number), downloader.local_block_number());
1521 }
1522
1523 #[test]
1524 fn test_resp_order() {
1525 let mut heap = BinaryHeap::new();
1526 let hi = 1u64;
1527 heap.push(OrderedHeadersResponse::<Header> {
1528 headers: vec![],
1529 request: HeadersRequest { start: hi.into(), limit: 0, direction: Default::default() },
1530 peer_id: Default::default(),
1531 });
1532
1533 let lo = 0u64;
1534 heap.push(OrderedHeadersResponse {
1535 headers: vec![],
1536 request: HeadersRequest { start: lo.into(), limit: 0, direction: Default::default() },
1537 peer_id: Default::default(),
1538 });
1539
1540 assert_eq!(heap.pop().unwrap().block_number(), hi);
1541 assert_eq!(heap.pop().unwrap().block_number(), lo);
1542 }
1543
1544 #[tokio::test]
1545 async fn download_at_fork_head() {
1546 reth_tracing::init_test_tracing();
1547
1548 let client = Arc::new(TestHeadersClient::default());
1549
1550 let p3 = SealedHeader::default();
1551 let p2 = child_header(&p3);
1552 let p1 = child_header(&p2);
1553 let p0 = child_header(&p1);
1554
1555 let mut downloader = ReverseHeadersDownloaderBuilder::default()
1556 .stream_batch_size(3)
1557 .request_limit(3)
1558 .build(Arc::clone(&client), Arc::new(TestConsensus::default()));
1559 downloader.update_local_head(p3.clone());
1560 downloader.update_sync_target(SyncTarget::Tip(p0.hash()));
1561
1562 client
1563 .extend(vec![
1564 p0.as_ref().clone(),
1565 p1.as_ref().clone(),
1566 p2.as_ref().clone(),
1567 p3.as_ref().clone(),
1568 ])
1569 .await;
1570
1571 let headers = downloader.next().await.unwrap();
1572 assert_eq!(headers.unwrap(), vec![p0, p1, p2,]);
1573 assert!(downloader.buffered_responses.is_empty());
1574 assert!(downloader.next().await.is_none());
1575 assert!(downloader.next().await.is_none());
1576 }
1577
1578 #[tokio::test]
1579 async fn download_one_by_one() {
1580 reth_tracing::init_test_tracing();
1581 let p3 = SealedHeader::default();
1582 let p2 = child_header(&p3);
1583 let p1 = child_header(&p2);
1584 let p0 = child_header(&p1);
1585
1586 let client = Arc::new(TestHeadersClient::default());
1587 let mut downloader = ReverseHeadersDownloaderBuilder::default()
1588 .stream_batch_size(1)
1589 .request_limit(1)
1590 .build(Arc::clone(&client), Arc::new(TestConsensus::default()));
1591 downloader.update_local_head(p3.clone());
1592 downloader.update_sync_target(SyncTarget::Tip(p0.hash()));
1593
1594 client
1595 .extend(vec![
1596 p0.as_ref().clone(),
1597 p1.as_ref().clone(),
1598 p2.as_ref().clone(),
1599 p3.as_ref().clone(),
1600 ])
1601 .await;
1602
1603 let headers = downloader.next().await.unwrap();
1604 let headers = headers.unwrap();
1605 assert_eq!(headers, vec![p0]);
1606 assert_eq!(headers.capacity(), headers.len());
1607
1608 let headers = downloader.next().await.unwrap();
1609 let headers = headers.unwrap();
1610 assert_eq!(headers, vec![p1]);
1611 assert_eq!(headers.capacity(), headers.len());
1612
1613 let headers = downloader.next().await.unwrap();
1614 let headers = headers.unwrap();
1615 assert_eq!(headers, vec![p2]);
1616 assert_eq!(headers.capacity(), headers.len());
1617
1618 assert!(downloader.next().await.is_none());
1619 }
1620
1621 #[tokio::test]
1622 async fn download_one_by_one_larger_request_limit() {
1623 reth_tracing::init_test_tracing();
1624 let p3 = SealedHeader::default();
1625 let p2 = child_header(&p3);
1626 let p1 = child_header(&p2);
1627 let p0 = child_header(&p1);
1628
1629 let client = Arc::new(TestHeadersClient::default());
1630 let mut downloader = ReverseHeadersDownloaderBuilder::default()
1631 .stream_batch_size(1)
1632 .request_limit(3)
1633 .build(Arc::clone(&client), Arc::new(TestConsensus::default()));
1634 downloader.update_local_head(p3.clone());
1635 downloader.update_sync_target(SyncTarget::Tip(p0.hash()));
1636
1637 client
1638 .extend(vec![
1639 p0.as_ref().clone(),
1640 p1.as_ref().clone(),
1641 p2.as_ref().clone(),
1642 p3.as_ref().clone(),
1643 ])
1644 .await;
1645
1646 let headers = downloader.next().await.unwrap();
1647 let headers = headers.unwrap();
1648 assert_eq!(headers, vec![p0]);
1649 assert_eq!(headers.capacity(), headers.len());
1650
1651 let headers = downloader.next().await.unwrap();
1652 let headers = headers.unwrap();
1653 assert_eq!(headers, vec![p1]);
1654 assert_eq!(headers.capacity(), headers.len());
1655
1656 let headers = downloader.next().await.unwrap();
1657 let headers = headers.unwrap();
1658 assert_eq!(headers, vec![p2]);
1659 assert_eq!(headers.capacity(), headers.len());
1660
1661 assert!(downloader.next().await.is_none());
1662 }
1663
1664 #[tokio::test]
1665 async fn downloads_capped_header_responses() {
1666 let p3 = SealedHeader::default();
1667 let p2 = child_header(&p3);
1668 let p1 = child_header(&p2);
1669 let p0 = child_header(&p1);
1670
1671 let client = CappedHeadersClient::new(
1672 vec![p0.as_ref().clone(), p1.as_ref().clone(), p2.as_ref().clone()],
1673 1,
1674 );
1675 let mut downloader = ReverseHeadersDownloaderBuilder::default()
1676 .stream_batch_size(1)
1677 .request_limit(2)
1678 .min_concurrent_requests(1)
1679 .max_concurrent_requests(1)
1680 .build(client.clone(), Arc::new(TestConsensus::default()));
1681 downloader.update_local_head(p3);
1682 downloader.update_sync_target(SyncTarget::Tip(p0.hash()));
1683
1684 assert_eq!(downloader.next().await.unwrap().unwrap(), vec![p0]);
1685 assert_eq!(downloader.next().await.unwrap().unwrap(), vec![p1]);
1686 assert_eq!(downloader.next().await.unwrap().unwrap(), vec![p2]);
1687 assert!(downloader.next().await.is_none());
1688
1689 assert_eq!(client.numeric_requests(), vec![(2, 2), (1, 1)]);
1690 assert_eq!(client.bad_message_count(), 0);
1691 }
1692
1693 #[test]
1694 fn requeues_missing_headers_for_buffered_partial_response() {
1695 let client = CappedHeadersClient::new(Vec::new(), 0);
1696 let mut downloader = ReverseHeadersDownloaderBuilder::default()
1697 .build(client.clone(), Arc::new(TestConsensus::default()));
1698 downloader.local_head = Some(SealedHeader::default());
1699 downloader.sync_target = Some(SyncTargetBlock::from_number(10));
1700 downloader.next_chain_tip_block_number = 10;
1701
1702 let request = HeadersRequest::falling(5u64.into(), 3);
1705 let response = vec![Header { number: 5, ..Default::default() }];
1706 let outcome = downloader.on_headers_outcome(HeadersRequestOutcome {
1707 request,
1708 outcome: Ok(WithPeerId::new(PeerId::default(), response)),
1709 });
1710
1711 assert!(outcome.is_ok());
1712 assert_eq!(downloader.buffered_responses.len(), 1);
1713 assert_eq!(client.numeric_requests(), vec![(4, 2)]);
1714 assert_eq!(client.bad_message_count(), 0);
1715 }
1716
1717 #[test]
1718 fn rejects_over_long_headers_response() {
1719 let client = CappedHeadersClient::new(Vec::new(), 0);
1720 let mut downloader = ReverseHeadersDownloaderBuilder::default()
1721 .build(client.clone(), Arc::new(TestConsensus::default()));
1722
1723 let request = HeadersRequest::falling(5u64.into(), 1);
1724 let response = vec![
1725 Header { number: 5, ..Default::default() },
1726 Header { number: 4, ..Default::default() },
1727 ];
1728 let outcome = downloader.on_headers_outcome(HeadersRequestOutcome {
1729 request,
1730 outcome: Ok(WithPeerId::new(PeerId::default(), response)),
1731 });
1732
1733 assert_matches!(
1734 outcome,
1735 Err(ReverseHeadersDownloaderError::Response(err))
1736 if matches!(err.error, DownloadError::HeadersResponseTooLong(_))
1737 );
1738 assert!(client.numeric_requests().is_empty());
1739 }
1740}