-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wip: feat(sync): headers stage #58
Changes from all commits
07b9266
8284cc3
23b85e1
cef747c
0da8504
a3456a8
b59f74f
bd84305
82b9ade
d5fab60
28a614f
7e39155
b27f25c
30018c7
930f5bd
ad452a6
015d5bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
use async_trait::async_trait; | ||
use futures::Stream; | ||
use reth_primitives::{rpc::BlockId, Header, H256, H512}; | ||
use std::{collections::HashSet, fmt::Debug, pin::Pin}; | ||
|
||
/// The stream of messages | ||
pub type MessageStream<T> = Pin<Box<dyn Stream<Item = T> + Send>>; | ||
|
||
/// The header request struct | ||
#[derive(Debug)] | ||
pub struct HeaderRequest { | ||
/// The starting block | ||
pub start: BlockId, | ||
/// The response max size | ||
pub limit: u64, | ||
/// Flag indicating whether the blocks should | ||
/// arrive in reverse | ||
pub reverse: bool, | ||
} | ||
|
||
/// The block headers downloader client | ||
#[async_trait] | ||
#[auto_impl::auto_impl(&, Arc, Box)] | ||
pub trait HeadersClient: Send + Sync + Debug { | ||
/// Update the current node status | ||
async fn update_status(&self, height: u64, hash: H256, td: H256); | ||
|
||
/// Send the header request | ||
async fn send_header_request(&self, id: u64, request: HeaderRequest) -> HashSet<H512>; | ||
|
||
/// Stream the header response messages | ||
async fn stream_headers(&self) -> MessageStream<(u64, Vec<Header>)>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,3 +49,6 @@ pub use ethers_core::{ | |
types as rpc, | ||
types::{Bloom, Bytes, H128, H160, H256, H512, H64, U128, U256, U64}, | ||
}; | ||
|
||
// For uint to hash conversion | ||
pub use ethereum_types::BigEndianHash; | ||
Comment on lines
+53
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, will add this to primitives There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks ❤️🔥 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rkrasiuk this is done^ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
use async_trait::async_trait; | ||
use reth_interfaces::{ | ||
consensus::Consensus, | ||
stages::{HeaderRequest, HeadersClient, MessageStream}, | ||
}; | ||
use reth_primitives::{rpc::BlockId, Header, HeaderLocked, H256}; | ||
use reth_rpc_types::engine::ForkchoiceState; | ||
use std::{fmt::Debug, time::Duration}; | ||
use thiserror::Error; | ||
use tokio_stream::StreamExt; | ||
|
||
/// The header downloading strategy | ||
#[async_trait] | ||
pub trait Downloader: Sync + Send + Debug { | ||
/// The Consensus used to verify block validity when | ||
/// downloading | ||
type Consensus: Consensus; | ||
/// The Client used to download the headers | ||
type Client: HeadersClient; | ||
|
||
/// The request timeout in seconds | ||
fn timeout(&self) -> u64; | ||
|
||
/// The consensus engine | ||
fn consensus(&self) -> &Self::Consensus; | ||
|
||
/// The headers client | ||
fn client(&self) -> &Self::Client; | ||
|
||
/// Download the headers | ||
async fn download( | ||
&self, | ||
head: &HeaderLocked, | ||
forkchoice: &ForkchoiceState, | ||
) -> Result<Vec<HeaderLocked>, DownloadError>; | ||
|
||
/// Perform a header request. Return the request ID | ||
async fn download_headers( | ||
&self, | ||
stream: &mut MessageStream<(u64, Vec<Header>)>, | ||
start: BlockId, | ||
limit: u64, | ||
) -> Result<Vec<Header>, DownloadError> { | ||
let request_id = rand::random(); | ||
let request = HeaderRequest { start, limit, reverse: true }; | ||
let _ = self.client().send_header_request(request_id, request).await; | ||
|
||
// Filter stream by request id and non empty headers content | ||
let stream = stream.filter(|(id, headers)| request_id == *id && !headers.is_empty()); | ||
|
||
// Wrap the stream with a timeout | ||
let stream = stream.timeout(Duration::from_secs(self.timeout())); | ||
match Box::pin(stream).try_next().await { | ||
Ok(Some((_, h))) => Ok(h), | ||
_ => return Err(DownloadError::NoHeaderResponse { request_id }), | ||
} | ||
} | ||
|
||
/// Validate whether the header is valid in relation to it's parent | ||
fn validate( | ||
&self, | ||
header: &HeaderLocked, | ||
parent: &HeaderLocked, | ||
) -> Result<bool, DownloadError> { | ||
if !(parent.hash() == header.parent_hash && parent.number + 1 == header.number) { | ||
return Ok(false) | ||
} | ||
|
||
self.consensus().validate_header(&header, &parent).map_err(|e| { | ||
DownloadError::HeaderValidation { hash: parent.hash(), details: e.to_string() } | ||
})?; | ||
Ok(true) | ||
} | ||
} | ||
|
||
/// The downloader error type | ||
#[derive(Error, Debug, Clone)] | ||
pub enum DownloadError { | ||
/// Header validation failed | ||
#[error("Failed to validate header {hash}. Details: {details}.")] | ||
HeaderValidation { | ||
/// Hash of header failing validation | ||
hash: H256, | ||
/// The details of validation failure | ||
details: String, | ||
}, | ||
/// No headers reponse received | ||
#[error("Failed to get headers for request {request_id}.")] | ||
NoHeaderResponse { | ||
/// The last request ID | ||
request_id: u64, | ||
}, | ||
} | ||
|
||
impl DownloadError { | ||
/// Returns bool indicating whether this error is retryable or fatal | ||
pub fn is_retryable(&self) -> bool { | ||
matches!(self, DownloadError::NoHeaderResponse { .. }) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's missing from
primitives
that makes this necessary?