Skip to main content

reth_downloaders/headers/
reverse_headers.rs

1//! A headers downloader that can handle multiple requests concurrently.
2
3use 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
36/// A heuristic that is used to determine the number of requests that should be prepared for a peer.
37/// This should ensure that there are always requests lined up for peers to handle while the
38/// downloader is yielding a next batch of headers that is being committed to the database.
39const REQUESTS_PER_PEER_MULTIPLIER: usize = 5;
40
41/// Wrapper for internal downloader errors.
42#[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/// Downloads headers concurrently.
57///
58/// This [`HeaderDownloader`] downloads headers using the configured [`HeadersClient`].
59/// Headers can be requested by hash or block number and take a `limit` parameter. This downloader
60/// tries to fill the gap between the local head of the node and the chain tip by issuing multiple
61/// requests at a time but yielding them in batches on [`Stream::poll_next`].
62///
63/// **Note:** This downloader downloads in reverse, see also
64/// [`reth_network_p2p::headers::client::HeadersDirection`], this means the batches of headers that
65/// this downloader yields will start at the chain tip and move towards the local head: falling
66/// block numbers.
67#[must_use = "Stream does nothing unless polled"]
68#[derive(Debug)]
69pub struct ReverseHeadersDownloader<H: HeadersClient> {
70    /// Consensus client used to validate headers
71    consensus: Arc<dyn HeaderValidator<H::Header>>,
72    /// Client used to download headers.
73    client: Arc<H>,
74    /// The local head of the chain.
75    local_head: Option<SealedHeader<H::Header>>,
76    /// Block we want to close the gap to.
77    sync_target: Option<SyncTargetBlock>,
78    /// The block number to use for requests.
79    next_request_block_number: u64,
80    /// Keeps track of the block we need to validate next.
81    lowest_validated_header: Option<SealedHeader<H::Header>>,
82    /// Tip block number to start validating from (in reverse)
83    next_chain_tip_block_number: u64,
84    /// The batch size per one request
85    request_limit: u64,
86    /// Minimum amount of requests to handle concurrently.
87    min_concurrent_requests: usize,
88    /// Maximum amount of requests to handle concurrently.
89    max_concurrent_requests: usize,
90    /// The number of block headers to return at once
91    stream_batch_size: usize,
92    /// Maximum amount of received headers to buffer internally.
93    max_buffered_responses: usize,
94    /// Contains the request to retrieve the headers for the sync target
95    ///
96    /// This will give us the block number of the `sync_target`, after which we can send multiple
97    /// requests at a time.
98    sync_target_request: Option<HeadersRequestFuture<H::Output>>,
99    /// requests in progress
100    in_progress_queue: FuturesUnordered<HeadersRequestFuture<H::Output>>,
101    /// Buffered, unvalidated responses
102    buffered_responses: BinaryHeap<OrderedHeadersResponse<H::Header>>,
103    /// Buffered, _sorted_ and validated headers ready to be returned.
104    ///
105    /// Note: headers are sorted from high to low
106    queued_validated_headers: Vec<SealedHeader<H::Header>>,
107    /// Header downloader metrics.
108    metrics: HeaderDownloaderMetrics,
109}
110
111// === impl ReverseHeadersDownloader ===
112
113impl<H> ReverseHeadersDownloader<H>
114where
115    H: HeadersClient<Header: reth_primitives_traits::BlockHeader> + 'static,
116{
117    /// Convenience method to create a [`ReverseHeadersDownloaderBuilder`] without importing it
118    pub fn builder() -> ReverseHeadersDownloaderBuilder {
119        ReverseHeadersDownloaderBuilder::default()
120    }
121
122    /// Returns the block number the local node is at.
123    #[inline]
124    fn local_block_number(&self) -> Option<BlockNumber> {
125        self.local_head.as_ref().map(|h| h.number())
126    }
127
128    /// Returns the existing local head block number
129    ///
130    /// # Panics
131    ///
132    /// If the local head has not been set.
133    #[inline]
134    fn existing_local_block_number(&self) -> BlockNumber {
135        self.local_head.as_ref().expect("is initialized").number()
136    }
137
138    /// Returns the existing sync target.
139    ///
140    /// # Panics
141    ///
142    /// If the sync target has never been set.
143    #[inline]
144    fn existing_sync_target(&self) -> SyncTargetBlock {
145        self.sync_target.as_ref().expect("is initialized").clone()
146    }
147
148    /// Max requests to handle at the same time
149    ///
150    /// This depends on the number of active peers but will always be
151    /// `min_concurrent_requests..max_concurrent_requests`
152    #[inline]
153    fn concurrent_request_limit(&self) -> usize {
154        let num_peers = self.client.num_connected_peers();
155
156        // we try to keep more requests than available peers active so that there's always a
157        // followup request available for a peer
158        let dynamic_target = num_peers * REQUESTS_PER_PEER_MULTIPLIER;
159        let max_dynamic = dynamic_target.max(self.min_concurrent_requests);
160
161        // If only a few peers are connected we keep it low
162        if num_peers < self.min_concurrent_requests {
163            return max_dynamic
164        }
165
166        max_dynamic.min(self.max_concurrent_requests)
167    }
168
169    /// Returns the next header request
170    ///
171    /// This will advance the current block towards the local head.
172    ///
173    /// Returns `None` if no more requests are required.
174    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            // need to shift the tracked request block number based on the number of requested
181            // headers so follow-up requests will use that as start.
182            self.next_request_block_number -= request.limit;
183
184            return Some(request)
185        }
186
187        None
188    }
189
190    /// Returns the next header to use for validation.
191    ///
192    /// Since this downloader downloads blocks with falling block number, this will return the
193    /// lowest (in terms of block number) validated header.
194    ///
195    /// This is either the last `queued_validated_headers`, or if has been drained entirely the
196    /// `lowest_validated_header`.
197    ///
198    /// This only returns `None` if we haven't fetched the initial chain tip yet.
199    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    /// Resets the request trackers and clears the sync target.
204    ///
205    /// This ensures the downloader will restart after a new sync target has been set.
206    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    /// Validate that the received header matches the expected sync target.
214    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    /// Processes the next headers in line.
247    ///
248    /// This will validate all headers and insert them into the validated buffer.
249    ///
250    /// Returns an error if the given headers are invalid.
251    ///
252    /// Caution: this expects the `headers` to be sorted with _falling_ block numbers
253    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            // Validate that the header is the parent header of the last validated header.
265            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 the last (smallest) validated header attaches to the local head, validate it.
282        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            // Every header must be valid on its own
288            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 the header is valid on its own, but not against its parent, we return it as
303            // detached head error.
304            // In stage sync this will trigger an unwind because this means that the local head
305            // is not part of the chain the sync target is on. In other words, the downloader was
306            // unable to connect the sync target with the local head because the sync target and
307            // the local head or on different chains.
308            if let Err(error) = self.consensus.validate_header_against_parent(&*last_header, head) {
309                let local_head = head.clone();
310                // Replace the last header with a detached variant
311                error!(target: "downloaders::headers", %error, number = last_header.number(), hash = ?last_header.hash(), "Header cannot be attached to known canonical chain");
312
313                // Reset trackers so that we can start over the next time the sync target is
314                // updated.
315                // The expected event flow when that happens is that the node will unwind the local
316                // chain and restart the downloader.
317                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        // update tracked block info (falling block number)
329        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    /// Updates the state based on the given `target_block_number`
337    ///
338    /// There are three different outcomes:
339    ///  * This is the first time this is called: current `sync_target` block is still `None`. In
340    ///    which case we're initializing the request trackers to `next_block`
341    ///  * The `target_block_number` is _higher_ than the current target. In which case we start
342    ///    over with a new range
343    ///  * The `target_block_number` is _lower_ than the current target or the _same_. In which case
344    ///    we don't need to update the request trackers but need to ensure already buffered headers
345    ///    are _not_ higher than the new `target_block_number`.
346    fn on_block_number_update(&mut self, target_block_number: u64, next_block: u64) {
347        // Update the trackers
348        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                // the new target is higher than the old target we need to update the
353                // request tracker and reset everything
354                self.next_request_block_number = next_block;
355                self.next_chain_tip_block_number = next_block;
356                self.clear();
357            } else {
358                // ensure already validated headers are in range
359                let skip = self
360                    .queued_validated_headers
361                    .iter()
362                    .take_while(|last| last.number() > target_block_number)
363                    .count();
364                // removes all headers that are higher than current target
365                self.queued_validated_headers.drain(..skip);
366            }
367        } else {
368            // this occurs on the initial sync target request
369            self.next_request_block_number = next_block;
370            self.next_chain_tip_block_number = next_block;
371        }
372    }
373
374    /// Handles the response for the request for the sync target
375    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                // update total downloaded metric
386                self.metrics.total_downloaded.increment(headers.len() as u64);
387
388                // sort headers from highest to lowest block number
389                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                // This is the next block we need to start issuing requests from
434                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                // try to validate all buffered responses blocked by this successful response
440                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    /// Invoked when we received a response
453    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                // update total downloaded metric
465                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                // sort headers from highest to lowest block number
493                headers.sort_unstable_by_key(|h| Reverse(h.number()));
494
495                // validate the response
496                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                // check if the response is the next expected
513                if highest.number() == self.next_chain_tip_block_number {
514                    // is next response, validate it
515                    self.process_next_headers(request, headers, peer_id)?;
516                    // request the missing headers before validating buffered responses, because a
517                    // buffered validation error returns early and would otherwise leave the
518                    // remainder of this range unrequested
519                    self.requeue_missing_headers(
520                        requested_block_number,
521                        received_headers,
522                        missing_headers,
523                    );
524                    // try to validate all buffered responses blocked by this successful response
525                    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                    // can't validate yet
531                    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            // most likely a noop, because this error
546            // would've been handled by the fetcher internally
547            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        // Penalize the peer for bad response
556        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    /// Handles the error of a bad response
563    ///
564    /// This will re-submit the request.
565    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        // Update error metric
571        self.metrics.increment_errors(&error);
572
573        // Re-submit the request
574        self.submit_request(request, Priority::High);
575    }
576
577    /// Attempts to validate the buffered responses
578    ///
579    /// Returns an error if the next expected response was popped, but failed validation.
580    fn try_validate_buffered(&mut self) -> Option<ReverseHeadersDownloaderError<H::Header>> {
581        loop {
582            // Check to see if we've already received the next value
583            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    /// Returns the request for the `sync_target` header.
605    const fn get_sync_target_request(&self, start: BlockHashOrNumber) -> HeadersRequest {
606        HeadersRequest::falling(start, 1)
607    }
608
609    /// Starts a request future
610    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    /// Submits a high-priority request for the missing suffix of a partial response.
617    ///
618    /// Peers are allowed to respond with fewer headers than requested, for example due to
619    /// response size limits. Expects that the caller has verified that the response started at
620    /// `requested_block_number` and contained `received_headers` headers.
621    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    /// Validate whether the header is valid in relation to it's parent
652    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    /// Clears all requests/responses.
661    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    /// Splits off the next batch of headers
672    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        // If the downloader consumer does not flush headers at the same rate that the downloader
677        // queues them, then the `queued_validated_headers` buffer can grow unbounded.
678        //
679        // The semantics of `split_off` state that the capacity of the original buffer is
680        // unchanged, so queued_validated_headers will then have only `batch_size` elements, and
681        // its original capacity. Because `rem` is initially populated with elements `[batch_size,
682        // len)` of `queued_validated_headers`, it will have a capacity of at least `len -
683        // batch_size`, and the total memory allocated by the two buffers will be around double the
684        // original size of `queued_validated_headers`.
685        //
686        // These are then mem::swapped, leaving `rem` with a large capacity, but small length.
687        //
688        // To prevent these allocations from leaking to the consumer, we shrink the capacity of the
689        // new buffer. The total memory allocated should then be not much more than the original
690        // size of `queued_validated_headers`.
691        rem.shrink_to_fit();
692        rem
693    }
694}
695
696impl<H> ReverseHeadersDownloader<H>
697where
698    H: HeadersClient,
699    Self: HeaderDownloader + 'static,
700{
701    /// Convert the downloader into a [`TaskDownloader`] by spawning it via the given [`Runtime`].
702    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        // ensure we're only yielding headers that are in range and follow the current local head.
718        while self
719            .queued_validated_headers
720            .last()
721            .is_some_and(|last| last.number() <= head.number())
722        {
723            // headers are sorted high to low
724            self.queued_validated_headers.pop();
725        }
726        trace!(
727            target: "downloaders::headers",
728            head=?head.num_hash(),
729            "Updating local head"
730        );
731        // update the local head
732        self.local_head = Some(head);
733    }
734
735    /// If the given target is different from the current target, we need to update the sync target
736    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 the new sync target is the next queued request we don't need to re-start
751                    // the target update
752                    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                    // there could be a sync target request in progress
774                    self.sync_target_request.take();
775                    // If the target has changed, update the request pointers based on the new
776                    // targeted block number
777                    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                    // Update the sync target hash
782                    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                    // just update the sync target
794                    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        // The downloader boundaries (local head and sync target) have to be set in order
818        // to start downloading data.
819        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        // If we have a new tip request we need to complete that first before we send batched
830        // requests
831        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                                // download channel closed which means the network was dropped
840                                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        // shrink the buffer after handling sync target outcomes
862        this.buffered_responses.shrink_to_fit();
863
864        // this loop will submit new requests and poll them, if a new batch is ready it is returned
865        // The actual work is done by the receiver of the request channel, this means, polling the
866        // request future is just reading from a `oneshot::Receiver`. Hence, this loop tries to keep
867        // the downloader at capacity at all times The order of loops is as follows:
868        // 1. poll futures to make room for followup requests (this will also prepare validated
869        // headers for 3.) 2. exhaust all capacity by sending requests
870        // 3. return batch, if enough validated
871        // 4. return Pending if 2.) did not submit a new request, else continue
872        loop {
873            // poll requests
874            while let Poll::Ready(Some(outcome)) = this.in_progress_queue.poll_next_unpin(cx) {
875                this.metrics.in_flight_requests.decrement(1.);
876                // handle response
877                match this.on_headers_outcome(outcome) {
878                    Ok(()) => (),
879                    Err(ReverseHeadersDownloaderError::Response(error)) => {
880                        if error.is_channel_closed() {
881                            // download channel closed which means the network was dropped
882                            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            // shrink the buffer after handling headers outcomes
894            this.buffered_responses.shrink_to_fit();
895
896            // marks the loop's exit condition: exit if no requests submitted
897            let mut progress = false;
898
899            let concurrent_request_limit = this.concurrent_request_limit();
900            // populate requests
901            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                    // no more requests
913                    break
914                }
915            }
916
917            // yield next batch
918            if this.queued_validated_headers.len() >= this.stream_batch_size {
919                let next_batch = this.split_next_batch();
920
921                // Note: if this would drain all headers, we need to keep the lowest (last index)
922                // around so we can continue validating headers responses.
923                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        // all requests are handled, stream is finished
939        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/// A future that returns a list of headers on success.
954#[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
975/// The outcome of the [`HeadersRequestFuture`]
976struct HeadersRequestOutcome<H> {
977    request: HeadersRequest,
978    outcome: PeerRequestResult<Vec<H>>,
979}
980
981// === impl OrderedHeadersResponse ===
982
983impl<H> HeadersRequestOutcome<H> {
984    const fn block_number(&self) -> u64 {
985        self.request.start.as_number().expect("is number")
986    }
987}
988
989/// Wrapper type to order responses
990#[derive(Debug)]
991struct OrderedHeadersResponse<H> {
992    headers: Vec<H>,
993    request: HeadersRequest,
994    peer_id: PeerId,
995}
996
997// === impl OrderedHeadersResponse ===
998
999impl<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/// Type returned if a bad response was processed
1026#[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    /// Returns true if the error was caused by a closed channel to the network.
1037    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/// The block to which we want to close the gap: (local head...sync target]
1046/// This tracks the sync target block, so this could be either a block number or hash.
1047#[derive(Clone, Debug)]
1048pub enum SyncTargetBlock {
1049    /// Block hash of the targeted block
1050    Hash(B256),
1051    /// Block number of the targeted block
1052    Number(u64),
1053    /// Both the block hash and number of the targeted block
1054    HashAndNumber {
1055        /// Block hash of the targeted block
1056        hash: B256,
1057        /// Block number of the targeted block
1058        number: u64,
1059    },
1060}
1061
1062impl SyncTargetBlock {
1063    /// Create new instance from hash.
1064    const fn from_hash(hash: B256) -> Self {
1065        Self::Hash(hash)
1066    }
1067
1068    /// Create new instance from number.
1069    const fn from_number(num: u64) -> Self {
1070        Self::Number(num)
1071    }
1072
1073    /// Set the hash for the sync target.
1074    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    /// Set a number on the instance.
1084    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    /// Replace the target block number, and return the old block number, if it was set.
1094    ///
1095    /// If the target block is a hash, this be converted into a `HashAndNumber`, but return `None`.
1096    /// The semantics should be equivalent to that of `Option::replace`.
1097    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    /// Return the hash of the target block, if it is set.
1117    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    /// Return the block number of the sync target, if it is set.
1125    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/// The builder for [`ReverseHeadersDownloader`] with
1134/// some default settings
1135#[derive(Debug)]
1136pub struct ReverseHeadersDownloaderBuilder {
1137    /// The batch size per one request
1138    request_limit: u64,
1139    /// Batch size for headers
1140    stream_batch_size: usize,
1141    /// Batch size for headers
1142    min_concurrent_requests: usize,
1143    /// Batch size for headers
1144    max_concurrent_requests: usize,
1145    /// How many responses to buffer
1146    max_buffered_responses: usize,
1147}
1148
1149impl ReverseHeadersDownloaderBuilder {
1150    /// Creates a new [`ReverseHeadersDownloaderBuilder`] with configurations based on the provided
1151    /// [`HeadersConfig`].
1152    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            // This is just below the max number of headers commonly in a headers response (1024), see also <https://github.com/ethereum/go-ethereum/blob/b0d44338bbcefee044f1f635a84487cbbd8f0538/eth/protocols/eth/handler.go#L38-L40>
1167            // with ~500bytes per header this around 0.5MB per request max
1168            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    /// Set the request batch size.
1178    ///
1179    /// This determines the `limit` for a `GetBlockHeaders` requests, the number of headers we ask
1180    /// for.
1181    pub const fn request_limit(mut self, limit: u64) -> Self {
1182        self.request_limit = limit;
1183        self
1184    }
1185
1186    /// Set the stream batch size
1187    ///
1188    /// This determines the number of headers the [`ReverseHeadersDownloader`] will yield on
1189    /// `Stream::next`. This will be the amount of headers the headers stage will commit at a
1190    /// time.
1191    pub const fn stream_batch_size(mut self, size: usize) -> Self {
1192        self.stream_batch_size = size;
1193        self
1194    }
1195
1196    /// Set the min amount of concurrent requests.
1197    ///
1198    /// If there's capacity the [`ReverseHeadersDownloader`] will keep at least this many requests
1199    /// active at a time.
1200    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    /// Set the max amount of concurrent requests.
1206    ///
1207    /// The downloader's concurrent requests won't exceed the given amount.
1208    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    /// How many responses to buffer internally.
1214    ///
1215    /// This essentially determines how much memory the downloader can use for buffering responses
1216    /// that arrive out of order. The total number of buffered headers is `request_limit *
1217    /// max_buffered_responses`. If the [`ReverseHeadersDownloader`]'s buffered responses exceeds
1218    /// this threshold it waits until there's capacity again before sending new requests.
1219    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    /// Build [`ReverseHeadersDownloader`] with provided consensus
1225    /// and header client implementations
1226    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            // Note: we set these to `0` first, they'll be updated once the sync target response is
1247            // handled and only used afterwards
1248            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/// Configures and returns the next [`HeadersRequest`] based on the given parameters
1266///
1267/// The request will start at the given `next_request_block_number` block.
1268/// The `limit` of the request will either be the targeted `request_limit` or the difference of
1269/// `next_request_block_number` and the `local_head` in case this is smaller than the targeted
1270/// `request_limit`.
1271#[inline]
1272fn calc_next_request(
1273    local_head: u64,
1274    next_request_block_number: u64,
1275    request_limit: u64,
1276) -> HeadersRequest {
1277    // downloading is in reverse
1278    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    /// Tests that `replace_number` works the same way as `Option::replace`
1362    #[test]
1363    fn test_replace_number_semantics() {
1364        struct Fixture {
1365            // input fields (both SyncTargetBlock and Option<u64>)
1366            sync_target_block: SyncTargetBlock,
1367            sync_target_option: Option<u64>,
1368
1369            // option to replace
1370            replace_number: u64,
1371
1372            // expected method result
1373            expected_result: Option<u64>,
1374
1375            // output state
1376            new_number: u64,
1377        }
1378
1379        let fixtures = vec![
1380            Fixture {
1381                sync_target_block: SyncTargetBlock::Hash(B256::random()),
1382                // Hash maps to None here, all other variants map to Some
1383                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    /// Tests that request calc works
1421    #[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    /// Tests that request calc works
1452    #[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        // request an entire batch
1473        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        // only request 1
1481        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    /// Tests that request calc works
1498    #[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        // a partial response that can't be validated yet is buffered, but the missing suffix must
1703        // be requested immediately
1704        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}