reth_network_p2p/headers/
downloader.rs

1use super::error::HeadersDownloaderResult;
2use crate::error::{DownloadError, DownloadResult};
3use alloy_eips::{eip1898::BlockWithParent, BlockHashOrNumber};
4use alloy_primitives::{Sealable, B256};
5use futures::Stream;
6use reth_consensus::HeaderValidator;
7use reth_primitives_traits::{BlockHeader, Header, SealedHeader};
8use std::fmt::Debug;
9
10/// A downloader capable of fetching and yielding block headers.
11///
12/// A downloader represents a distinct strategy for submitting requests to download block headers,
13/// while a [`HeadersClient`][crate::headers::client::HeadersClient] represents a client capable
14/// of fulfilling these requests.
15///
16/// A [`HeaderDownloader`] is a [Stream] that returns batches of headers.
17pub trait HeaderDownloader:
18    Send
19    + Sync
20    + Stream<Item = HeadersDownloaderResult<Vec<SealedHeader<Self::Header>>, Self::Header>>
21    + Unpin
22{
23    /// The header type being downloaded.
24    type Header: Sealable + Debug + Send + Sync + Unpin + 'static;
25
26    /// Updates the gap to sync which ranges from local head to the sync target.
27    ///
28    /// See also [`HeaderDownloader::update_sync_target`] and
29    /// [`HeaderDownloader::update_local_head`]
30    fn update_sync_gap(&mut self, head: SealedHeader<Self::Header>, target: SyncTarget) {
31        self.update_local_head(head);
32        self.update_sync_target(target);
33    }
34
35    /// Updates the block number of the local database
36    fn update_local_head(&mut self, head: SealedHeader<Self::Header>);
37
38    /// Updates the target we want to sync to.
39    fn update_sync_target(&mut self, target: SyncTarget);
40
41    /// Sets the headers batch size that the Stream should return.
42    fn set_batch_size(&mut self, limit: usize);
43}
44
45/// Specifies the target to sync for [`HeaderDownloader::update_sync_target`]
46#[derive(Debug, Clone, Eq, PartialEq)]
47pub enum SyncTarget {
48    /// This represents a range missing headers in the form of `(head,..`
49    ///
50    /// Sync _inclusively_ to the given block hash.
51    ///
52    /// This target specifies the upper end of the sync gap `(head...tip]`
53    Tip(B256),
54    /// This represents a gap missing headers bounded by the given header `h` in the form of
55    /// `(head,..h),h+1,h+2...`
56    ///
57    /// Sync _exclusively_ to the given header's parent which is: `(head..h-1]`
58    ///
59    /// The benefit of this variant is, that this already provides the block number of the highest
60    /// missing block.
61    Gap(BlockWithParent),
62    /// This represents a tip by block number
63    TipNum(u64),
64}
65
66// === impl SyncTarget ===
67
68impl SyncTarget {
69    /// Returns the tip to sync to _inclusively_
70    ///
71    /// This returns the hash if the target is [`SyncTarget::Tip`] or the `parent_hash` of the given
72    /// header in [`SyncTarget::Gap`]
73    pub fn tip(&self) -> BlockHashOrNumber {
74        match self {
75            Self::Tip(tip) => (*tip).into(),
76            Self::Gap(gap) => gap.parent.into(),
77            Self::TipNum(num) => (*num).into(),
78        }
79    }
80}
81
82/// Represents a gap to sync: from `local_head` to `target`
83#[derive(Clone, Debug)]
84pub struct HeaderSyncGap<H = Header> {
85    /// The local head block. Represents lower bound of sync range.
86    pub local_head: SealedHeader<H>,
87
88    /// The sync target. Represents upper bound of sync range.
89    pub target: SyncTarget,
90}
91
92impl<H: BlockHeader + Sealable> HeaderSyncGap<H> {
93    /// Returns `true` if the gap from the head to the target was closed
94    #[inline]
95    pub fn is_closed(&self) -> bool {
96        match self.target.tip() {
97            BlockHashOrNumber::Hash(hash) => self.local_head.hash() == hash,
98            BlockHashOrNumber::Number(num) => self.local_head.number() == num,
99        }
100    }
101}
102
103/// Validate whether the header is valid in relation to it's parent
104///
105/// Returns Ok(false) if the
106pub fn validate_header_download<H: BlockHeader>(
107    consensus: &dyn HeaderValidator<H>,
108    header: &SealedHeader<H>,
109    parent: &SealedHeader<H>,
110) -> DownloadResult<()> {
111    // validate header against parent
112    consensus.validate_header_against_parent(header, parent).map_err(|error| {
113        DownloadError::HeaderValidation {
114            hash: header.hash(),
115            number: header.number(),
116            error: Box::new(error),
117        }
118    })?;
119    // validate header standalone
120    consensus.validate_header(header).map_err(|error| DownloadError::HeaderValidation {
121        hash: header.hash(),
122        number: header.number(),
123        error: Box::new(error),
124    })?;
125    Ok(())
126}