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) = validated.last() {
283            self.validate_attachment(last_header, request, peer_id)?;
284        }
285
286        // update tracked block info (falling block number)
287        self.next_chain_tip_block_number =
288            validated.last().expect("exists").number().saturating_sub(1);
289        self.queued_validated_headers.extend(validated);
290
291        Ok(())
292    }
293
294    /// Validates `header` against the local head if it is the head's direct successor.
295    ///
296    /// Returns [`HeadersDownloaderError::DetachedHead`] if the header does not attach to it.
297    fn validate_attachment(
298        &mut self,
299        header: &SealedHeader<H::Header>,
300        request: HeadersRequest,
301        peer_id: PeerId,
302    ) -> Result<(), ReverseHeadersDownloaderError<H::Header>> {
303        let Some(head) =
304            self.local_head.as_ref().filter(|head| header.number() == head.number() + 1)
305        else {
306            return Ok(())
307        };
308        // Every header must be valid on its own
309        if let Err(error) = self.consensus.validate_header(header) {
310            trace!(target: "downloaders::headers", %error, "Failed to validate header");
311            return Err(HeadersResponseError {
312                request,
313                peer_id: Some(peer_id),
314                error: DownloadError::HeaderValidation {
315                    hash: head.hash(),
316                    number: head.number(),
317                    error: Box::new(error),
318                },
319            }
320            .into())
321        }
322
323        // If the header is valid on its own, but not against its parent, we return it as
324        // detached head error.
325        // In stage sync this will trigger an unwind because this means that the local head
326        // is not part of the chain the sync target is on. In other words, the downloader was
327        // unable to connect the sync target with the local head because the sync target and
328        // the local head or on different chains.
329        if let Err(error) = self.consensus.validate_header_against_parent(header, head) {
330            let local_head = head.clone();
331            // Replace the last header with a detached variant
332            error!(target: "downloaders::headers", %error, number = header.number(), hash = ?header.hash(), "Header cannot be attached to known canonical chain");
333
334            // Reset trackers so that we can start over the next time the sync target is
335            // updated.
336            // The expected event flow when that happens is that the node will unwind the local
337            // chain and restart the downloader.
338            self.reset();
339
340            return Err(HeadersDownloaderError::DetachedHead {
341                local_head: Box::new(local_head),
342                header: Box::new(header.clone()),
343                error: Box::new(error),
344            }
345            .into())
346        }
347        Ok(())
348    }
349
350    /// Updates the state based on the given `target_block_number`
351    ///
352    /// There are three different outcomes:
353    ///  * This is the first time this is called: current `sync_target` block is still `None`. In
354    ///    which case we're initializing the request trackers to `next_block`
355    ///  * The `target_block_number` is _higher_ than the current target. In which case we start
356    ///    over with a new range
357    ///  * The `target_block_number` is _lower_ than the current target or the _same_. In which case
358    ///    we don't need to update the request trackers but need to ensure already buffered headers
359    ///    are _not_ higher than the new `target_block_number`.
360    fn on_block_number_update(&mut self, target_block_number: u64, next_block: u64) {
361        // Update the trackers
362        if let Some(old_target) =
363            self.sync_target.as_mut().and_then(|t| t.replace_number(target_block_number))
364        {
365            if target_block_number > old_target {
366                // the new target is higher than the old target we need to update the
367                // request tracker and reset everything
368                self.next_request_block_number = next_block;
369                self.next_chain_tip_block_number = next_block;
370                self.clear();
371            } else {
372                // ensure already validated headers are in range
373                let skip = self
374                    .queued_validated_headers
375                    .iter()
376                    .take_while(|last| last.number() > target_block_number)
377                    .count();
378                // removes all headers that are higher than current target
379                self.queued_validated_headers.drain(..skip);
380            }
381        } else {
382            // this occurs on the initial sync target request
383            self.next_request_block_number = next_block;
384            self.next_chain_tip_block_number = next_block;
385        }
386    }
387
388    /// Handles the response for the request for the sync target
389    fn on_sync_target_outcome(
390        &mut self,
391        response: HeadersRequestOutcome<H::Header>,
392    ) -> Result<(), ReverseHeadersDownloaderError<H::Header>> {
393        let sync_target = self.existing_sync_target();
394        let HeadersRequestOutcome { request, outcome } = response;
395        match outcome {
396            Ok(res) => {
397                let (peer_id, mut headers) = res.split();
398
399                // update total downloaded metric
400                self.metrics.total_downloaded.increment(headers.len() as u64);
401
402                // sort headers from highest to lowest block number
403                headers.sort_unstable_by_key(|h| Reverse(h.number()));
404
405                if headers.is_empty() {
406                    return Err(HeadersResponseError {
407                        request,
408                        peer_id: Some(peer_id),
409                        error: DownloadError::EmptyResponse,
410                    }
411                    .into())
412                }
413
414                let header = headers.swap_remove(0);
415                let target = SealedHeader::seal_slow(header);
416
417                match sync_target {
418                    SyncTargetBlock::Hash(hash) | SyncTargetBlock::HashAndNumber { hash, .. } => {
419                        if target.hash() != hash {
420                            return Err(HeadersResponseError {
421                                request,
422                                peer_id: Some(peer_id),
423                                error: DownloadError::InvalidTip(
424                                    GotExpected { got: target.hash(), expected: hash }.into(),
425                                ),
426                            }
427                            .into())
428                        }
429                    }
430                    SyncTargetBlock::Number(number) => {
431                        if target.number() != number {
432                            return Err(HeadersResponseError {
433                                request,
434                                peer_id: Some(peer_id),
435                                error: DownloadError::InvalidTipNumber(GotExpected {
436                                    got: target.number(),
437                                    expected: number,
438                                }),
439                            }
440                            .into())
441                        }
442                    }
443                }
444
445                trace!(target: "downloaders::headers", head=?self.local_block_number(), hash=?target.hash(), number=%target.number(), "Received sync target");
446
447                // A target right above the local head has no batch below it to attach.
448                self.validate_attachment(&target, request, peer_id)?;
449
450                // This is the next block we need to start issuing requests from
451                let parent_block_number = target.number().saturating_sub(1);
452                self.on_block_number_update(target.number(), parent_block_number);
453
454                self.queued_validated_headers.push(target);
455
456                // try to validate all buffered responses blocked by this successful response
457                self.try_validate_buffered()
458                    .map(Err::<(), ReverseHeadersDownloaderError<H::Header>>)
459                    .transpose()?;
460
461                Ok(())
462            }
463            Err(err) => {
464                Err(HeadersResponseError { request, peer_id: None, error: err.into() }.into())
465            }
466        }
467    }
468
469    /// Invoked when we received a response
470    fn on_headers_outcome(
471        &mut self,
472        response: HeadersRequestOutcome<H::Header>,
473    ) -> Result<(), ReverseHeadersDownloaderError<H::Header>> {
474        let requested_block_number = response.block_number();
475        let HeadersRequestOutcome { request, outcome } = response;
476
477        match outcome {
478            Ok(res) => {
479                let (peer_id, mut headers) = res.split();
480
481                // update total downloaded metric
482                self.metrics.total_downloaded.increment(headers.len() as u64);
483
484                trace!(target: "downloaders::headers", len=%headers.len(), "Received headers response");
485
486                if headers.is_empty() {
487                    return Err(HeadersResponseError {
488                        request,
489                        peer_id: Some(peer_id),
490                        error: DownloadError::EmptyResponse,
491                    }
492                    .into())
493                }
494
495                let received_headers = headers.len() as u64;
496                if received_headers > request.limit {
497                    return Err(HeadersResponseError {
498                        peer_id: Some(peer_id),
499                        error: DownloadError::HeadersResponseTooLong(GotExpected {
500                            got: received_headers,
501                            expected: request.limit,
502                        }),
503                        request,
504                    }
505                    .into())
506                }
507                let missing_headers = request.limit - received_headers;
508
509                // sort headers from highest to lowest block number
510                headers.sort_unstable_by_key(|h| Reverse(h.number()));
511
512                // validate the response
513                let highest = &headers[0];
514
515                trace!(target: "downloaders::headers", requested_block_number, highest=?highest.number(), "Validating non-empty headers response");
516
517                if highest.number() != requested_block_number {
518                    return Err(HeadersResponseError {
519                        request,
520                        peer_id: Some(peer_id),
521                        error: DownloadError::HeadersResponseStartBlockMismatch(GotExpected {
522                            got: highest.number(),
523                            expected: requested_block_number,
524                        }),
525                    }
526                    .into())
527                }
528
529                // check if the response is the next expected
530                if highest.number() == self.next_chain_tip_block_number {
531                    // is next response, validate it
532                    self.process_next_headers(request, headers, peer_id)?;
533                    // request the missing headers before validating buffered responses, because a
534                    // buffered validation error returns early and would otherwise leave the
535                    // remainder of this range unrequested
536                    self.requeue_missing_headers(
537                        requested_block_number,
538                        received_headers,
539                        missing_headers,
540                    );
541                    // try to validate all buffered responses blocked by this successful response
542                    self.try_validate_buffered()
543                        .map(Err::<(), ReverseHeadersDownloaderError<H::Header>>)
544                        .transpose()?;
545                } else if highest.number() > self.existing_local_block_number() {
546                    self.metrics.buffered_responses.increment(1.);
547                    // can't validate yet
548                    self.buffered_responses.push(OrderedHeadersResponse {
549                        headers,
550                        request,
551                        peer_id,
552                    });
553                    self.requeue_missing_headers(
554                        requested_block_number,
555                        received_headers,
556                        missing_headers,
557                    );
558                }
559
560                Ok(())
561            }
562            // most likely a noop, because this error
563            // would've been handled by the fetcher internally
564            Err(err) => {
565                trace!(target: "downloaders::headers", %err, "Response error");
566                Err(HeadersResponseError { request, peer_id: None, error: err.into() }.into())
567            }
568        }
569    }
570
571    fn penalize_peer(&self, peer_id: Option<PeerId>, error: &DownloadError) {
572        // Penalize the peer for bad response
573        if let Some(peer_id) = peer_id {
574            trace!(target: "downloaders::headers", ?peer_id, %error, "Penalizing peer");
575            self.client.report_bad_message(peer_id);
576        }
577    }
578
579    /// Handles the error of a bad response
580    ///
581    /// This will re-submit the request.
582    fn on_headers_error(&self, err: Box<HeadersResponseError>) {
583        let HeadersResponseError { request, peer_id, error } = *err;
584
585        self.penalize_peer(peer_id, &error);
586
587        // Update error metric
588        self.metrics.increment_errors(&error);
589
590        // Re-submit the request
591        self.submit_request(request, Priority::High);
592    }
593
594    /// Attempts to validate the buffered responses
595    ///
596    /// Returns an error if the next expected response was popped, but failed validation.
597    fn try_validate_buffered(&mut self) -> Option<ReverseHeadersDownloaderError<H::Header>> {
598        loop {
599            // Check to see if we've already received the next value
600            let next_response = self.buffered_responses.peek_mut()?;
601            let next_block_number = next_response.block_number();
602            match next_block_number.cmp(&self.next_chain_tip_block_number) {
603                Ordering::Less => return None,
604                Ordering::Equal => {
605                    let OrderedHeadersResponse { headers, request, peer_id } =
606                        PeekMut::pop(next_response);
607                    self.metrics.buffered_responses.decrement(1.);
608
609                    if let Err(err) = self.process_next_headers(request, headers, peer_id) {
610                        return Some(err)
611                    }
612                }
613                Ordering::Greater => {
614                    self.metrics.buffered_responses.decrement(1.);
615                    PeekMut::pop(next_response);
616                }
617            }
618        }
619    }
620
621    /// Returns the request for the `sync_target` header.
622    const fn get_sync_target_request(&self, start: BlockHashOrNumber) -> HeadersRequest {
623        HeadersRequest::falling(start, 1)
624    }
625
626    /// Starts a request future
627    fn submit_request(&self, request: HeadersRequest, priority: Priority) {
628        trace!(target: "downloaders::headers", ?request, "Submitting headers request");
629        self.in_progress_queue.push(self.request_fut(request, priority));
630        self.metrics.in_flight_requests.increment(1.);
631    }
632
633    /// Submits a high-priority request for the missing suffix of a partial response.
634    ///
635    /// Peers are allowed to respond with fewer headers than requested, for example due to
636    /// response size limits. Expects that the caller has verified that the response started at
637    /// `requested_block_number` and contained `received_headers` headers.
638    fn requeue_missing_headers(
639        &self,
640        requested_block_number: u64,
641        received_headers: u64,
642        missing_headers: u64,
643    ) {
644        if missing_headers > 0 {
645            self.metrics.partial_responses.increment(1);
646            self.submit_request(
647                HeadersRequest::falling(
648                    (requested_block_number - received_headers).into(),
649                    missing_headers,
650                ),
651                Priority::High,
652            );
653        }
654    }
655
656    fn request_fut(
657        &self,
658        request: HeadersRequest,
659        priority: Priority,
660    ) -> HeadersRequestFuture<H::Output> {
661        let client = Arc::clone(&self.client);
662        HeadersRequestFuture {
663            request: Some(request.clone()),
664            fut: client.get_headers_with_priority(request, priority),
665        }
666    }
667
668    /// Validate whether the header is valid in relation to it's parent
669    fn validate(
670        &self,
671        header: &SealedHeader<H::Header>,
672        parent: &SealedHeader<H::Header>,
673    ) -> DownloadResult<()> {
674        validate_header_download(&self.consensus, header, parent)
675    }
676
677    /// Clears all requests/responses.
678    fn clear(&mut self) {
679        self.lowest_validated_header.take();
680        self.queued_validated_headers = Vec::new();
681        self.buffered_responses = BinaryHeap::new();
682        self.in_progress_queue.clear();
683
684        self.metrics.in_flight_requests.set(0.);
685        self.metrics.buffered_responses.set(0.);
686    }
687
688    /// Splits off the next batch of headers
689    fn split_next_batch(&mut self) -> Vec<SealedHeader<H::Header>> {
690        let batch_size = self.stream_batch_size.min(self.queued_validated_headers.len());
691        let mut rem = self.queued_validated_headers.split_off(batch_size);
692        std::mem::swap(&mut rem, &mut self.queued_validated_headers);
693        // If the downloader consumer does not flush headers at the same rate that the downloader
694        // queues them, then the `queued_validated_headers` buffer can grow unbounded.
695        //
696        // The semantics of `split_off` state that the capacity of the original buffer is
697        // unchanged, so queued_validated_headers will then have only `batch_size` elements, and
698        // its original capacity. Because `rem` is initially populated with elements `[batch_size,
699        // len)` of `queued_validated_headers`, it will have a capacity of at least `len -
700        // batch_size`, and the total memory allocated by the two buffers will be around double the
701        // original size of `queued_validated_headers`.
702        //
703        // These are then mem::swapped, leaving `rem` with a large capacity, but small length.
704        //
705        // To prevent these allocations from leaking to the consumer, we shrink the capacity of the
706        // new buffer. The total memory allocated should then be not much more than the original
707        // size of `queued_validated_headers`.
708        rem.shrink_to_fit();
709        rem
710    }
711}
712
713impl<H> ReverseHeadersDownloader<H>
714where
715    H: HeadersClient,
716    Self: HeaderDownloader + 'static,
717{
718    /// Convert the downloader into a [`TaskDownloader`] by spawning it via the given [`Runtime`].
719    pub fn into_task_with(
720        self,
721        runtime: &Runtime,
722    ) -> TaskDownloader<<Self as HeaderDownloader>::Header> {
723        TaskDownloader::spawn_with(self, runtime)
724    }
725}
726
727impl<H> HeaderDownloader for ReverseHeadersDownloader<H>
728where
729    H: HeadersClient<Header: reth_primitives_traits::BlockHeader> + 'static,
730{
731    type Header = H::Header;
732
733    fn update_local_head(&mut self, head: SealedHeader<H::Header>) {
734        // ensure we're only yielding headers that are in range and follow the current local head.
735        while self
736            .queued_validated_headers
737            .last()
738            .is_some_and(|last| last.number() <= head.number())
739        {
740            // headers are sorted high to low
741            self.queued_validated_headers.pop();
742        }
743        trace!(
744            target: "downloaders::headers",
745            head=?head.num_hash(),
746            "Updating local head"
747        );
748        // update the local head
749        self.local_head = Some(head);
750    }
751
752    /// If the given target is different from the current target, we need to update the sync target
753    fn update_sync_target(&mut self, target: SyncTarget) {
754        let current_tip = self.sync_target.as_ref().and_then(|t| t.hash());
755        trace!(
756            target: "downloaders::headers",
757            sync_target=?target,
758            current_tip=?current_tip,
759            "Updating sync target"
760        );
761        match target {
762            SyncTarget::Tip(tip) => {
763                if Some(tip) != current_tip {
764                    trace!(target: "downloaders::headers", current=?current_tip, new=?tip, "Update sync target");
765                    let new_sync_target = SyncTargetBlock::from_hash(tip);
766
767                    // if the new sync target is the next queued request we don't need to re-start
768                    // the target update
769                    if let Some(target_number) = self
770                        .queued_validated_headers
771                        .first()
772                        .filter(|h| h.hash() == tip)
773                        .map(|h| h.number())
774                    {
775                        self.sync_target = Some(new_sync_target.with_number(target_number));
776                        return
777                    }
778
779                    trace!(target: "downloaders::headers", new=?target, "Request new sync target");
780                    self.metrics.out_of_order_requests.increment(1);
781                    self.sync_target = Some(new_sync_target);
782                    self.sync_target_request = Some(
783                        self.request_fut(self.get_sync_target_request(tip.into()), Priority::High),
784                    );
785                }
786            }
787            SyncTarget::Gap(existing) => {
788                let target = existing.parent;
789                if Some(target) != current_tip {
790                    // there could be a sync target request in progress
791                    self.sync_target_request.take();
792                    // If the target has changed, update the request pointers based on the new
793                    // targeted block number
794                    let parent_block_number = existing.block.number.saturating_sub(1);
795
796                    trace!(target: "downloaders::headers", current=?current_tip, new=?target, %parent_block_number, "Updated sync target");
797
798                    // Update the sync target hash
799                    self.sync_target = match self.sync_target.take() {
800                        Some(sync_target) => Some(sync_target.with_hash(target)),
801                        None => Some(SyncTargetBlock::from_hash(target)),
802                    };
803                    self.on_block_number_update(parent_block_number, parent_block_number);
804                }
805            }
806            SyncTarget::TipNum(num) => {
807                let current_tip_num = self.sync_target.as_ref().and_then(|t| t.number());
808                if Some(num) != current_tip_num {
809                    trace!(target: "downloaders::headers", %num, "Updating sync target based on num");
810                    // just update the sync target
811                    self.sync_target = Some(SyncTargetBlock::from_number(num));
812                    self.sync_target_request = Some(
813                        self.request_fut(self.get_sync_target_request(num.into()), Priority::High),
814                    );
815                }
816            }
817        }
818    }
819
820    fn set_batch_size(&mut self, batch_size: usize) {
821        self.stream_batch_size = batch_size;
822    }
823}
824
825impl<H> Stream for ReverseHeadersDownloader<H>
826where
827    H: HeadersClient<Header: reth_primitives_traits::BlockHeader> + 'static,
828{
829    type Item = HeadersDownloaderResult<Vec<SealedHeader<H::Header>>, H::Header>;
830
831    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
832        let this = self.get_mut();
833
834        // The downloader boundaries (local head and sync target) have to be set in order
835        // to start downloading data.
836        if this.local_head.is_none() || this.sync_target.is_none() {
837            trace!(
838                target: "downloaders::headers",
839                head=?this.local_block_number(),
840                sync_target=?this.sync_target,
841                "The downloader sync boundaries have not been set"
842            );
843            return Poll::Pending
844        }
845
846        // If we have a new tip request we need to complete that first before we send batched
847        // requests
848        while let Some(mut req) = this.sync_target_request.take() {
849            match req.poll_unpin(cx) {
850                Poll::Ready(outcome) => {
851                    match this.on_sync_target_outcome(outcome) {
852                        Ok(()) => break,
853                        Err(ReverseHeadersDownloaderError::Response(error)) => {
854                            trace!(target: "downloaders::headers", %error, "invalid sync target response");
855                            if error.is_channel_closed() {
856                                // download channel closed which means the network was dropped
857                                return Poll::Ready(None)
858                            }
859
860                            this.penalize_peer(error.peer_id, &error.error);
861                            this.metrics.increment_errors(&error.error);
862                            this.sync_target_request =
863                                Some(this.request_fut(error.request, Priority::High));
864                        }
865                        Err(ReverseHeadersDownloaderError::Downloader(error)) => {
866                            this.clear();
867                            return Poll::Ready(Some(Err(error)))
868                        }
869                    };
870                }
871                Poll::Pending => {
872                    this.sync_target_request = Some(req);
873                    return Poll::Pending
874                }
875            }
876        }
877
878        // shrink the buffer after handling sync target outcomes
879        this.buffered_responses.shrink_to_fit();
880
881        // this loop will submit new requests and poll them, if a new batch is ready it is returned
882        // The actual work is done by the receiver of the request channel, this means, polling the
883        // request future is just reading from a `oneshot::Receiver`. Hence, this loop tries to keep
884        // the downloader at capacity at all times The order of loops is as follows:
885        // 1. poll futures to make room for followup requests (this will also prepare validated
886        // headers for 3.) 2. exhaust all capacity by sending requests
887        // 3. return batch, if enough validated
888        // 4. return Pending if 2.) did not submit a new request, else continue
889        loop {
890            // poll requests
891            while let Poll::Ready(Some(outcome)) = this.in_progress_queue.poll_next_unpin(cx) {
892                this.metrics.in_flight_requests.decrement(1.);
893                // handle response
894                match this.on_headers_outcome(outcome) {
895                    Ok(()) => (),
896                    Err(ReverseHeadersDownloaderError::Response(error)) => {
897                        if error.is_channel_closed() {
898                            // download channel closed which means the network was dropped
899                            return Poll::Ready(None)
900                        }
901                        this.on_headers_error(error);
902                    }
903                    Err(ReverseHeadersDownloaderError::Downloader(error)) => {
904                        this.clear();
905                        return Poll::Ready(Some(Err(error)))
906                    }
907                };
908            }
909
910            // shrink the buffer after handling headers outcomes
911            this.buffered_responses.shrink_to_fit();
912
913            // marks the loop's exit condition: exit if no requests submitted
914            let mut progress = false;
915
916            let concurrent_request_limit = this.concurrent_request_limit();
917            // populate requests
918            while this.in_progress_queue.len() < concurrent_request_limit &&
919                this.buffered_responses.len() < this.max_buffered_responses
920            {
921                if let Some(request) = this.next_request() {
922                    trace!(
923                        target: "downloaders::headers",
924                        "Requesting headers {request:?}"
925                    );
926                    progress = true;
927                    this.submit_request(request, Priority::Normal);
928                } else {
929                    // no more requests
930                    break
931                }
932            }
933
934            // yield next batch
935            if this.queued_validated_headers.len() >= this.stream_batch_size {
936                let next_batch = this.split_next_batch();
937
938                // Note: if this would drain all headers, we need to keep the lowest (last index)
939                // around so we can continue validating headers responses.
940                if this.queued_validated_headers.is_empty() {
941                    this.lowest_validated_header = next_batch.last().cloned();
942                }
943
944                trace!(target: "downloaders::headers", batch=%next_batch.len(), "Returning validated batch");
945
946                this.metrics.total_flushed.increment(next_batch.len() as u64);
947                return Poll::Ready(Some(Ok(next_batch)))
948            }
949
950            if !progress {
951                break
952            }
953        }
954
955        // all requests are handled, stream is finished
956        if this.in_progress_queue.is_empty() {
957            let next_batch = this.split_next_batch();
958            if next_batch.is_empty() {
959                this.clear();
960                return Poll::Ready(None)
961            }
962            this.metrics.total_flushed.increment(next_batch.len() as u64);
963            return Poll::Ready(Some(Ok(next_batch)))
964        }
965
966        Poll::Pending
967    }
968}
969
970/// A future that returns a list of headers on success.
971#[derive(Debug)]
972struct HeadersRequestFuture<F> {
973    request: Option<HeadersRequest>,
974    fut: F,
975}
976
977impl<F, H> Future for HeadersRequestFuture<F>
978where
979    F: Future<Output = PeerRequestResult<Vec<H>>> + Sync + Send + Unpin,
980{
981    type Output = HeadersRequestOutcome<H>;
982
983    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
984        let this = self.get_mut();
985        let outcome = ready!(this.fut.poll_unpin(cx));
986        let request = this.request.take().unwrap();
987
988        Poll::Ready(HeadersRequestOutcome { request, outcome })
989    }
990}
991
992/// The outcome of the [`HeadersRequestFuture`]
993struct HeadersRequestOutcome<H> {
994    request: HeadersRequest,
995    outcome: PeerRequestResult<Vec<H>>,
996}
997
998// === impl OrderedHeadersResponse ===
999
1000impl<H> HeadersRequestOutcome<H> {
1001    const fn block_number(&self) -> u64 {
1002        self.request.start.as_number().expect("is number")
1003    }
1004}
1005
1006/// Wrapper type to order responses
1007#[derive(Debug)]
1008struct OrderedHeadersResponse<H> {
1009    headers: Vec<H>,
1010    request: HeadersRequest,
1011    peer_id: PeerId,
1012}
1013
1014// === impl OrderedHeadersResponse ===
1015
1016impl<H> OrderedHeadersResponse<H> {
1017    const fn block_number(&self) -> u64 {
1018        self.request.start.as_number().expect("is number")
1019    }
1020}
1021
1022impl<H> PartialEq for OrderedHeadersResponse<H> {
1023    fn eq(&self, other: &Self) -> bool {
1024        self.block_number() == other.block_number()
1025    }
1026}
1027
1028impl<H> Eq for OrderedHeadersResponse<H> {}
1029
1030impl<H> PartialOrd for OrderedHeadersResponse<H> {
1031    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1032        Some(self.cmp(other))
1033    }
1034}
1035
1036impl<H> Ord for OrderedHeadersResponse<H> {
1037    fn cmp(&self, other: &Self) -> Ordering {
1038        self.block_number().cmp(&other.block_number())
1039    }
1040}
1041
1042/// Type returned if a bad response was processed
1043#[derive(Debug, Error)]
1044#[error("error requesting headers from peer {peer_id:?}: {error}; request: {request:?}")]
1045struct HeadersResponseError {
1046    request: HeadersRequest,
1047    peer_id: Option<PeerId>,
1048    #[source]
1049    error: DownloadError,
1050}
1051
1052impl HeadersResponseError {
1053    /// Returns true if the error was caused by a closed channel to the network.
1054    const fn is_channel_closed(&self) -> bool {
1055        if let DownloadError::RequestError(ref err) = self.error {
1056            return err.is_channel_closed()
1057        }
1058        false
1059    }
1060}
1061
1062/// The block to which we want to close the gap: (local head...sync target]
1063/// This tracks the sync target block, so this could be either a block number or hash.
1064#[derive(Clone, Debug)]
1065pub enum SyncTargetBlock {
1066    /// Block hash of the targeted block
1067    Hash(B256),
1068    /// Block number of the targeted block
1069    Number(u64),
1070    /// Both the block hash and number of the targeted block
1071    HashAndNumber {
1072        /// Block hash of the targeted block
1073        hash: B256,
1074        /// Block number of the targeted block
1075        number: u64,
1076    },
1077}
1078
1079impl SyncTargetBlock {
1080    /// Create new instance from hash.
1081    const fn from_hash(hash: B256) -> Self {
1082        Self::Hash(hash)
1083    }
1084
1085    /// Create new instance from number.
1086    const fn from_number(num: u64) -> Self {
1087        Self::Number(num)
1088    }
1089
1090    /// Set the hash for the sync target.
1091    const fn with_hash(self, hash: B256) -> Self {
1092        match self {
1093            Self::Hash(_) => Self::Hash(hash),
1094            Self::Number(number) | Self::HashAndNumber { number, .. } => {
1095                Self::HashAndNumber { hash, number }
1096            }
1097        }
1098    }
1099
1100    /// Set a number on the instance.
1101    const fn with_number(self, number: u64) -> Self {
1102        match self {
1103            Self::Hash(hash) | Self::HashAndNumber { hash, .. } => {
1104                Self::HashAndNumber { hash, number }
1105            }
1106            Self::Number(_) => Self::Number(number),
1107        }
1108    }
1109
1110    /// Replace the target block number, and return the old block number, if it was set.
1111    ///
1112    /// If the target block is a hash, this be converted into a `HashAndNumber`, but return `None`.
1113    /// The semantics should be equivalent to that of `Option::replace`.
1114    const fn replace_number(&mut self, number: u64) -> Option<u64> {
1115        match self {
1116            Self::Hash(hash) => {
1117                *self = Self::HashAndNumber { hash: *hash, number };
1118                None
1119            }
1120            Self::Number(old_number) => {
1121                let res = Some(*old_number);
1122                *self = Self::Number(number);
1123                res
1124            }
1125            Self::HashAndNumber { number: old_number, hash } => {
1126                let res = Some(*old_number);
1127                *self = Self::HashAndNumber { hash: *hash, number };
1128                res
1129            }
1130        }
1131    }
1132
1133    /// Return the hash of the target block, if it is set.
1134    const fn hash(&self) -> Option<B256> {
1135        match self {
1136            Self::Hash(hash) | Self::HashAndNumber { hash, .. } => Some(*hash),
1137            Self::Number(_) => None,
1138        }
1139    }
1140
1141    /// Return the block number of the sync target, if it is set.
1142    const fn number(&self) -> Option<u64> {
1143        match self {
1144            Self::Hash(_) => None,
1145            Self::Number(number) | Self::HashAndNumber { number, .. } => Some(*number),
1146        }
1147    }
1148}
1149
1150/// The builder for [`ReverseHeadersDownloader`] with
1151/// some default settings
1152#[derive(Debug)]
1153pub struct ReverseHeadersDownloaderBuilder {
1154    /// The batch size per one request
1155    request_limit: u64,
1156    /// Batch size for headers
1157    stream_batch_size: usize,
1158    /// Batch size for headers
1159    min_concurrent_requests: usize,
1160    /// Batch size for headers
1161    max_concurrent_requests: usize,
1162    /// How many responses to buffer
1163    max_buffered_responses: usize,
1164}
1165
1166impl ReverseHeadersDownloaderBuilder {
1167    /// Creates a new [`ReverseHeadersDownloaderBuilder`] with configurations based on the provided
1168    /// [`HeadersConfig`].
1169    pub fn new(config: HeadersConfig) -> Self {
1170        Self::default()
1171            .request_limit(config.downloader_request_limit)
1172            .min_concurrent_requests(config.downloader_min_concurrent_requests)
1173            .max_concurrent_requests(config.downloader_max_concurrent_requests)
1174            .max_buffered_responses(config.downloader_max_buffered_responses)
1175            .stream_batch_size(config.commit_threshold as usize)
1176    }
1177}
1178
1179impl Default for ReverseHeadersDownloaderBuilder {
1180    fn default() -> Self {
1181        Self {
1182            stream_batch_size: 10_000,
1183            // 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>
1184            // with ~500bytes per header this around 0.5MB per request max
1185            request_limit: 1_000,
1186            max_concurrent_requests: 100,
1187            min_concurrent_requests: 5,
1188            max_buffered_responses: 100,
1189        }
1190    }
1191}
1192
1193impl ReverseHeadersDownloaderBuilder {
1194    /// Set the request batch size.
1195    ///
1196    /// This determines the `limit` for a `GetBlockHeaders` requests, the number of headers we ask
1197    /// for.
1198    pub const fn request_limit(mut self, limit: u64) -> Self {
1199        self.request_limit = limit;
1200        self
1201    }
1202
1203    /// Set the stream batch size
1204    ///
1205    /// This determines the number of headers the [`ReverseHeadersDownloader`] will yield on
1206    /// `Stream::next`. This will be the amount of headers the headers stage will commit at a
1207    /// time.
1208    pub const fn stream_batch_size(mut self, size: usize) -> Self {
1209        self.stream_batch_size = size;
1210        self
1211    }
1212
1213    /// Set the min amount of concurrent requests.
1214    ///
1215    /// If there's capacity the [`ReverseHeadersDownloader`] will keep at least this many requests
1216    /// active at a time.
1217    pub const fn min_concurrent_requests(mut self, min_concurrent_requests: usize) -> Self {
1218        self.min_concurrent_requests = min_concurrent_requests;
1219        self
1220    }
1221
1222    /// Set the max amount of concurrent requests.
1223    ///
1224    /// The downloader's concurrent requests won't exceed the given amount.
1225    pub const fn max_concurrent_requests(mut self, max_concurrent_requests: usize) -> Self {
1226        self.max_concurrent_requests = max_concurrent_requests;
1227        self
1228    }
1229
1230    /// How many responses to buffer internally.
1231    ///
1232    /// This essentially determines how much memory the downloader can use for buffering responses
1233    /// that arrive out of order. The total number of buffered headers is `request_limit *
1234    /// max_buffered_responses`. If the [`ReverseHeadersDownloader`]'s buffered responses exceeds
1235    /// this threshold it waits until there's capacity again before sending new requests.
1236    pub const fn max_buffered_responses(mut self, max_buffered_responses: usize) -> Self {
1237        self.max_buffered_responses = max_buffered_responses;
1238        self
1239    }
1240
1241    /// Build [`ReverseHeadersDownloader`] with provided consensus
1242    /// and header client implementations
1243    pub fn build<H>(
1244        self,
1245        client: H,
1246        consensus: Arc<dyn HeaderValidator<H::Header>>,
1247    ) -> ReverseHeadersDownloader<H>
1248    where
1249        H: HeadersClient + 'static,
1250    {
1251        let Self {
1252            request_limit,
1253            stream_batch_size,
1254            min_concurrent_requests,
1255            max_concurrent_requests,
1256            max_buffered_responses,
1257        } = self;
1258        ReverseHeadersDownloader {
1259            consensus,
1260            client: Arc::new(client),
1261            local_head: None,
1262            sync_target: None,
1263            // Note: we set these to `0` first, they'll be updated once the sync target response is
1264            // handled and only used afterwards
1265            next_request_block_number: 0,
1266            next_chain_tip_block_number: 0,
1267            lowest_validated_header: None,
1268            request_limit,
1269            min_concurrent_requests,
1270            max_concurrent_requests,
1271            stream_batch_size,
1272            max_buffered_responses,
1273            sync_target_request: None,
1274            in_progress_queue: Default::default(),
1275            buffered_responses: Default::default(),
1276            queued_validated_headers: Default::default(),
1277            metrics: Default::default(),
1278        }
1279    }
1280}
1281
1282/// Configures and returns the next [`HeadersRequest`] based on the given parameters
1283///
1284/// The request will start at the given `next_request_block_number` block.
1285/// The `limit` of the request will either be the targeted `request_limit` or the difference of
1286/// `next_request_block_number` and the `local_head` in case this is smaller than the targeted
1287/// `request_limit`.
1288#[inline]
1289fn calc_next_request(
1290    local_head: u64,
1291    next_request_block_number: u64,
1292    request_limit: u64,
1293) -> HeadersRequest {
1294    // downloading is in reverse
1295    let diff = next_request_block_number - local_head;
1296    let limit = diff.min(request_limit);
1297    let start = next_request_block_number;
1298    HeadersRequest::falling(start.into(), limit)
1299}
1300
1301#[cfg(test)]
1302mod tests {
1303    use super::*;
1304    use crate::headers::test_utils::child_header;
1305    use alloy_consensus::Header;
1306    use alloy_eips::{eip1898::BlockWithParent, BlockNumHash};
1307    use assert_matches::assert_matches;
1308    use reth_consensus::test_utils::TestConsensus;
1309    use reth_network_p2p::{
1310        download::DownloadClient, error::PeerRequestResult, test_utils::TestHeadersClient,
1311    };
1312    use reth_network_peers::WithPeerId;
1313    use std::sync::{
1314        atomic::{AtomicU64, Ordering as AtomicOrdering},
1315        Mutex,
1316    };
1317
1318    #[derive(Clone, Debug)]
1319    struct CappedHeadersClient {
1320        responses: Arc<Mutex<Vec<Header>>>,
1321        requests: Arc<Mutex<Vec<HeadersRequest>>>,
1322        bad_messages: Arc<AtomicU64>,
1323        response_limit: usize,
1324    }
1325
1326    impl CappedHeadersClient {
1327        fn new(responses: Vec<Header>, response_limit: usize) -> Self {
1328            Self {
1329                responses: Arc::new(Mutex::new(responses)),
1330                requests: Default::default(),
1331                bad_messages: Default::default(),
1332                response_limit,
1333            }
1334        }
1335
1336        fn numeric_requests(&self) -> Vec<(u64, u64)> {
1337            self.requests
1338                .lock()
1339                .unwrap()
1340                .iter()
1341                .filter_map(|request| request.start.as_number().map(|start| (start, request.limit)))
1342                .collect()
1343        }
1344
1345        fn bad_message_count(&self) -> u64 {
1346            self.bad_messages.load(AtomicOrdering::SeqCst)
1347        }
1348    }
1349
1350    impl DownloadClient for CappedHeadersClient {
1351        fn report_bad_message(&self, _peer_id: PeerId) {
1352            self.bad_messages.fetch_add(1, AtomicOrdering::SeqCst);
1353        }
1354
1355        fn num_connected_peers(&self) -> usize {
1356            1
1357        }
1358    }
1359
1360    impl HeadersClient for CappedHeadersClient {
1361        type Header = Header;
1362        type Output = futures::future::Ready<PeerRequestResult<Vec<Header>>>;
1363
1364        fn get_headers_with_priority(
1365            &self,
1366            request: HeadersRequest,
1367            _priority: Priority,
1368        ) -> Self::Output {
1369            self.requests.lock().unwrap().push(request.clone());
1370            let mut responses = self.responses.lock().unwrap();
1371            let response_length = request.limit.min(self.response_limit as u64) as usize;
1372            let response = responses.drain(..response_length).collect();
1373
1374            futures::future::ready(Ok(WithPeerId::new(PeerId::default(), response)))
1375        }
1376    }
1377
1378    /// Tests that `replace_number` works the same way as `Option::replace`
1379    #[test]
1380    fn test_replace_number_semantics() {
1381        struct Fixture {
1382            // input fields (both SyncTargetBlock and Option<u64>)
1383            sync_target_block: SyncTargetBlock,
1384            sync_target_option: Option<u64>,
1385
1386            // option to replace
1387            replace_number: u64,
1388
1389            // expected method result
1390            expected_result: Option<u64>,
1391
1392            // output state
1393            new_number: u64,
1394        }
1395
1396        let fixtures = vec![
1397            Fixture {
1398                sync_target_block: SyncTargetBlock::Hash(B256::random()),
1399                // Hash maps to None here, all other variants map to Some
1400                sync_target_option: None,
1401                replace_number: 1,
1402                expected_result: None,
1403                new_number: 1,
1404            },
1405            Fixture {
1406                sync_target_block: SyncTargetBlock::Number(1),
1407                sync_target_option: Some(1),
1408                replace_number: 2,
1409                expected_result: Some(1),
1410                new_number: 2,
1411            },
1412            Fixture {
1413                sync_target_block: SyncTargetBlock::HashAndNumber {
1414                    hash: B256::random(),
1415                    number: 1,
1416                },
1417                sync_target_option: Some(1),
1418                replace_number: 2,
1419                expected_result: Some(1),
1420                new_number: 2,
1421            },
1422        ];
1423
1424        for fixture in fixtures {
1425            let mut sync_target_block = fixture.sync_target_block;
1426            let result = sync_target_block.replace_number(fixture.replace_number);
1427            assert_eq!(result, fixture.expected_result);
1428            assert_eq!(sync_target_block.number(), Some(fixture.new_number));
1429
1430            let mut sync_target_option = fixture.sync_target_option;
1431            let option_result = sync_target_option.replace(fixture.replace_number);
1432            assert_eq!(option_result, fixture.expected_result);
1433            assert_eq!(sync_target_option, Some(fixture.new_number));
1434        }
1435    }
1436
1437    /// Tests that request calc works
1438    #[test]
1439    fn test_sync_target_update() {
1440        let client = Arc::new(TestHeadersClient::default());
1441
1442        let genesis = SealedHeader::default();
1443
1444        let mut downloader = ReverseHeadersDownloaderBuilder::default()
1445            .build(Arc::clone(&client), Arc::new(TestConsensus::default()));
1446        downloader.update_local_head(genesis);
1447        downloader.update_sync_target(SyncTarget::Tip(B256::random()));
1448
1449        downloader.sync_target_request.take();
1450
1451        let target = SyncTarget::Tip(B256::random());
1452        downloader.update_sync_target(target);
1453        assert!(downloader.sync_target_request.is_some());
1454
1455        downloader.sync_target_request.take();
1456        let target = SyncTarget::Gap(BlockWithParent {
1457            block: BlockNumHash::new(0, B256::random()),
1458            parent: Default::default(),
1459        });
1460        downloader.update_sync_target(target);
1461        assert!(downloader.sync_target_request.is_none());
1462        assert_matches!(
1463            downloader.sync_target,
1464            Some(target) => target.number().is_some()
1465        );
1466    }
1467
1468    /// Tests that request calc works
1469    #[test]
1470    fn test_head_update() {
1471        let client = Arc::new(TestHeadersClient::default());
1472
1473        let header: SealedHeader = SealedHeader::default();
1474
1475        let mut downloader = ReverseHeadersDownloaderBuilder::default()
1476            .build(Arc::clone(&client), Arc::new(TestConsensus::default()));
1477        downloader.update_local_head(header.clone());
1478        downloader.update_sync_target(SyncTarget::Tip(B256::random()));
1479
1480        downloader.queued_validated_headers.push(header.clone());
1481        let mut next = header.as_ref().clone();
1482        next.number += 1;
1483        downloader.update_local_head(SealedHeader::new(next, B256::random()));
1484        assert!(downloader.queued_validated_headers.is_empty());
1485    }
1486
1487    #[test]
1488    fn test_request_calc() {
1489        // request an entire batch
1490        let local = 0;
1491        let next = 1000;
1492        let batch_size = 2;
1493        let request = calc_next_request(local, next, batch_size);
1494        assert_eq!(request.start, next.into());
1495        assert_eq!(request.limit, batch_size);
1496
1497        // only request 1
1498        let local = 999;
1499        let next = 1000;
1500        let batch_size = 2;
1501        let request = calc_next_request(local, next, batch_size);
1502        assert_eq!(request.start, next.into());
1503        assert_eq!(request.limit, 1);
1504    }
1505
1506    #[test]
1507    fn default_request_limit_matches_sync_config() {
1508        assert_eq!(
1509            ReverseHeadersDownloaderBuilder::default().request_limit,
1510            HeadersConfig::default().downloader_request_limit
1511        );
1512    }
1513
1514    /// Tests that request calc works
1515    #[test]
1516    fn test_next_request() {
1517        let client = Arc::new(TestHeadersClient::default());
1518
1519        let genesis = SealedHeader::default();
1520
1521        let batch_size = 99;
1522        let start = 1000;
1523        let mut downloader = ReverseHeadersDownloaderBuilder::default()
1524            .request_limit(batch_size)
1525            .build(Arc::clone(&client), Arc::new(TestConsensus::default()));
1526        downloader.update_local_head(genesis);
1527        downloader.update_sync_target(SyncTarget::Tip(B256::random()));
1528
1529        downloader.next_request_block_number = start;
1530
1531        let mut total = 0;
1532        while let Some(req) = downloader.next_request() {
1533            assert_eq!(req.start, (start - total).into());
1534            total += req.limit;
1535        }
1536        assert_eq!(total, start);
1537        assert_eq!(Some(downloader.next_request_block_number), downloader.local_block_number());
1538    }
1539
1540    #[test]
1541    fn test_resp_order() {
1542        let mut heap = BinaryHeap::new();
1543        let hi = 1u64;
1544        heap.push(OrderedHeadersResponse::<Header> {
1545            headers: vec![],
1546            request: HeadersRequest { start: hi.into(), limit: 0, direction: Default::default() },
1547            peer_id: Default::default(),
1548        });
1549
1550        let lo = 0u64;
1551        heap.push(OrderedHeadersResponse {
1552            headers: vec![],
1553            request: HeadersRequest { start: lo.into(), limit: 0, direction: Default::default() },
1554            peer_id: Default::default(),
1555        });
1556
1557        assert_eq!(heap.pop().unwrap().block_number(), hi);
1558        assert_eq!(heap.pop().unwrap().block_number(), lo);
1559    }
1560
1561    #[tokio::test]
1562    async fn rejects_a_detached_sync_target_above_the_local_head() {
1563        let head = SealedHeader::<Header>::default();
1564        let target = SealedHeader::seal_slow(Header {
1565            number: 1,
1566            parent_hash: B256::repeat_byte(1),
1567            ..Default::default()
1568        });
1569        let client = Arc::new(TestHeadersClient::default());
1570        client.extend(vec![target.as_ref().clone()]).await;
1571        let consensus = TestConsensus::default();
1572        consensus.set_fail_header_against_parent(true);
1573        let mut downloader = ReverseHeadersDownloaderBuilder::default()
1574            .build(Arc::clone(&client), Arc::new(consensus));
1575        downloader.update_local_head(head.clone());
1576        downloader.update_sync_target(SyncTarget::Tip(target.hash()));
1577
1578        let error = downloader.next().await.unwrap().unwrap_err();
1579
1580        assert_matches!(
1581            error,
1582            HeadersDownloaderError::DetachedHead { local_head, header, .. }
1583                if *local_head == head && *header == target
1584        );
1585    }
1586
1587    #[tokio::test]
1588    async fn download_at_fork_head() {
1589        reth_tracing::init_test_tracing();
1590
1591        let client = Arc::new(TestHeadersClient::default());
1592
1593        let p3 = SealedHeader::default();
1594        let p2 = child_header(&p3);
1595        let p1 = child_header(&p2);
1596        let p0 = child_header(&p1);
1597
1598        let mut downloader = ReverseHeadersDownloaderBuilder::default()
1599            .stream_batch_size(3)
1600            .request_limit(3)
1601            .build(Arc::clone(&client), Arc::new(TestConsensus::default()));
1602        downloader.update_local_head(p3.clone());
1603        downloader.update_sync_target(SyncTarget::Tip(p0.hash()));
1604
1605        client
1606            .extend(vec![
1607                p0.as_ref().clone(),
1608                p1.as_ref().clone(),
1609                p2.as_ref().clone(),
1610                p3.as_ref().clone(),
1611            ])
1612            .await;
1613
1614        let headers = downloader.next().await.unwrap();
1615        assert_eq!(headers.unwrap(), vec![p0, p1, p2,]);
1616        assert!(downloader.buffered_responses.is_empty());
1617        assert!(downloader.next().await.is_none());
1618        assert!(downloader.next().await.is_none());
1619    }
1620
1621    #[tokio::test]
1622    async fn download_one_by_one() {
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(1)
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 download_one_by_one_larger_request_limit() {
1666        reth_tracing::init_test_tracing();
1667        let p3 = SealedHeader::default();
1668        let p2 = child_header(&p3);
1669        let p1 = child_header(&p2);
1670        let p0 = child_header(&p1);
1671
1672        let client = Arc::new(TestHeadersClient::default());
1673        let mut downloader = ReverseHeadersDownloaderBuilder::default()
1674            .stream_batch_size(1)
1675            .request_limit(3)
1676            .build(Arc::clone(&client), Arc::new(TestConsensus::default()));
1677        downloader.update_local_head(p3.clone());
1678        downloader.update_sync_target(SyncTarget::Tip(p0.hash()));
1679
1680        client
1681            .extend(vec![
1682                p0.as_ref().clone(),
1683                p1.as_ref().clone(),
1684                p2.as_ref().clone(),
1685                p3.as_ref().clone(),
1686            ])
1687            .await;
1688
1689        let headers = downloader.next().await.unwrap();
1690        let headers = headers.unwrap();
1691        assert_eq!(headers, vec![p0]);
1692        assert_eq!(headers.capacity(), headers.len());
1693
1694        let headers = downloader.next().await.unwrap();
1695        let headers = headers.unwrap();
1696        assert_eq!(headers, vec![p1]);
1697        assert_eq!(headers.capacity(), headers.len());
1698
1699        let headers = downloader.next().await.unwrap();
1700        let headers = headers.unwrap();
1701        assert_eq!(headers, vec![p2]);
1702        assert_eq!(headers.capacity(), headers.len());
1703
1704        assert!(downloader.next().await.is_none());
1705    }
1706
1707    #[tokio::test]
1708    async fn downloads_capped_header_responses() {
1709        let p3 = SealedHeader::default();
1710        let p2 = child_header(&p3);
1711        let p1 = child_header(&p2);
1712        let p0 = child_header(&p1);
1713
1714        let client = CappedHeadersClient::new(
1715            vec![p0.as_ref().clone(), p1.as_ref().clone(), p2.as_ref().clone()],
1716            1,
1717        );
1718        let mut downloader = ReverseHeadersDownloaderBuilder::default()
1719            .stream_batch_size(1)
1720            .request_limit(2)
1721            .min_concurrent_requests(1)
1722            .max_concurrent_requests(1)
1723            .build(client.clone(), Arc::new(TestConsensus::default()));
1724        downloader.update_local_head(p3);
1725        downloader.update_sync_target(SyncTarget::Tip(p0.hash()));
1726
1727        assert_eq!(downloader.next().await.unwrap().unwrap(), vec![p0]);
1728        assert_eq!(downloader.next().await.unwrap().unwrap(), vec![p1]);
1729        assert_eq!(downloader.next().await.unwrap().unwrap(), vec![p2]);
1730        assert!(downloader.next().await.is_none());
1731
1732        assert_eq!(client.numeric_requests(), vec![(2, 2), (1, 1)]);
1733        assert_eq!(client.bad_message_count(), 0);
1734    }
1735
1736    #[test]
1737    fn requeues_missing_headers_for_buffered_partial_response() {
1738        let client = CappedHeadersClient::new(Vec::new(), 0);
1739        let mut downloader = ReverseHeadersDownloaderBuilder::default()
1740            .build(client.clone(), Arc::new(TestConsensus::default()));
1741        downloader.local_head = Some(SealedHeader::default());
1742        downloader.sync_target = Some(SyncTargetBlock::from_number(10));
1743        downloader.next_chain_tip_block_number = 10;
1744
1745        // a partial response that can't be validated yet is buffered, but the missing suffix must
1746        // be requested immediately
1747        let request = HeadersRequest::falling(5u64.into(), 3);
1748        let response = vec![Header { number: 5, ..Default::default() }];
1749        let outcome = downloader.on_headers_outcome(HeadersRequestOutcome {
1750            request,
1751            outcome: Ok(WithPeerId::new(PeerId::default(), response)),
1752        });
1753
1754        assert!(outcome.is_ok());
1755        assert_eq!(downloader.buffered_responses.len(), 1);
1756        assert_eq!(client.numeric_requests(), vec![(4, 2)]);
1757        assert_eq!(client.bad_message_count(), 0);
1758    }
1759
1760    #[test]
1761    fn rejects_over_long_headers_response() {
1762        let client = CappedHeadersClient::new(Vec::new(), 0);
1763        let mut downloader = ReverseHeadersDownloaderBuilder::default()
1764            .build(client.clone(), Arc::new(TestConsensus::default()));
1765
1766        let request = HeadersRequest::falling(5u64.into(), 1);
1767        let response = vec![
1768            Header { number: 5, ..Default::default() },
1769            Header { number: 4, ..Default::default() },
1770        ];
1771        let outcome = downloader.on_headers_outcome(HeadersRequestOutcome {
1772            request,
1773            outcome: Ok(WithPeerId::new(PeerId::default(), response)),
1774        });
1775
1776        assert_matches!(
1777            outcome,
1778            Err(ReverseHeadersDownloaderError::Response(err))
1779                if matches!(err.error, DownloadError::HeadersResponseTooLong(_))
1780        );
1781        assert!(client.numeric_requests().is_empty());
1782    }
1783}