mirror of
https://github.com/sigp/lighthouse.git
synced 2026-06-01 13:47:16 +00:00
Single blob lookups (#4152)
* some blob reprocessing work * remove ForceBlockLookup * reorder enum match arms in sync manager * a lot more reprocessing work * impl logic for triggerng blob lookups along with block lookups * deal with rpc blobs in groups per block in the da checker. don't cache missing blob ids in the da checker. * make single block lookup generic * more work * add delayed processing logic and combine some requests * start fixing some compile errors * fix compilation in main block lookup mod * much work * get things compiling * parent blob lookups * fix compile * revert red/stevie changes * fix up sync manager delay message logic * add peer usefulness enum * should remove lookup refactor * consolidate retry error handling * improve peer scoring during certain failures in parent lookups * improve retry code * drop parent lookup if either req has a peer disconnect during download * refactor single block processed method * processing peer refactor * smol bugfix * fix some todos * fix lints * fix lints * fix compile in lookup tests * fix lints * fix lints * fix existing block lookup tests * renamings * fix after merge * cargo fmt * compilation fix in beacon chain tests * fix * refactor lookup tests to work with multiple forks and response types * make tests into macros * wrap availability check error * fix compile after merge * add random blobs * start fixing up lookup verify error handling * some bug fixes and the start of deneb only tests * make tests work for all forks * track information about peer source * error refactoring * improve peer scoring * fix test compilation * make sure blobs are sent for processing after stream termination, delete copied tests * add some tests and fix a bug * smol bugfixes and moar tests * add tests and fix some things * compile after merge * lots of refactoring * retry on invalid block/blob * merge unknown parent messages before current slot lookup * get tests compiling * penalize blob peer on invalid blobs * Check disk on in-memory cache miss * Update beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs * Update beacon_node/network/src/sync/network_context.rs Co-authored-by: Divma <26765164+divagant-martian@users.noreply.github.com> * fix bug in matching blocks and blobs in range sync * pr feedback * fix conflicts * upgrade logs from warn to crit when we receive incorrect response in range * synced_and_connected_within_tolerance -> should_search_for_block * remove todo * Fix Broken Overflow Tests * fix merge conflicts * checkpoint sync without alignment * add import * query for checkpoint state by slot rather than state root (teku doesn't serve by state root) * get state first and query by most recent block root * simplify delay logic * rename unknown parent sync message variants * rename parameter, block_slot -> slot * add some docs to the lookup module * use interval instead of sleep * drop request if blocks and blobs requests both return `None` for `Id` * clean up `find_single_lookup` logic * add lookup source enum * clean up `find_single_lookup` logic * add docs to find_single_lookup_request * move LookupSource our of param where unnecessary * remove unnecessary todo * query for block by `state.latest_block_header.slot` * fix lint * fix test * fix test * fix observed blob sidecars test * PR updates * use optional params instead of a closure * create lookup and trigger request in separate method calls * remove `LookupSource` * make sure duplicate lookups are not dropped --------- Co-authored-by: Pawan Dhananjay <pawandhananjay@gmail.com> Co-authored-by: Mark Mackey <mark@sigmaprime.io> Co-authored-by: Divma <26765164+divagant-martian@users.noreply.github.com>
This commit is contained in:
84
beacon_node/network/src/sync/block_lookups/delayed_lookup.rs
Normal file
84
beacon_node/network/src/sync/block_lookups/delayed_lookup.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use crate::sync::SyncMessage;
|
||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||
use slog::{crit, warn};
|
||||
use slot_clock::SlotClock;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::interval_at;
|
||||
use tokio::time::Instant;
|
||||
use types::Hash256;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DelayedLookupMessage {
|
||||
/// A lookup for all components of a block or blob seen over gossip.
|
||||
MissingComponents(Hash256),
|
||||
}
|
||||
|
||||
/// This service is responsible for collecting lookup messages and sending them back to sync
|
||||
/// for processing after a short delay.
|
||||
///
|
||||
/// We want to delay lookups triggered from gossip for the following reasons:
|
||||
///
|
||||
/// - We only want to make one request for components we are unlikely to see on gossip. This means
|
||||
/// we don't have to repeatedly update our RPC request's state as we receive gossip components.
|
||||
///
|
||||
/// - We are likely to receive blocks/blobs over gossip more quickly than we could via an RPC request.
|
||||
///
|
||||
/// - Delaying a lookup means we are less likely to simultaneously download the same blocks/blobs
|
||||
/// over gossip and RPC.
|
||||
///
|
||||
/// - We would prefer to request peers based on whether we've seen them attest, because this gives
|
||||
/// us an idea about whether they *should* have the block/blobs we're missing. This is because a
|
||||
/// node should not attest to a block unless it has all the blobs for that block. This gives us a
|
||||
/// stronger basis for peer scoring.
|
||||
pub fn spawn_delayed_lookup_service<T: BeaconChainTypes>(
|
||||
executor: &task_executor::TaskExecutor,
|
||||
beacon_chain: Arc<BeaconChain<T>>,
|
||||
mut delayed_lookups_recv: mpsc::Receiver<DelayedLookupMessage>,
|
||||
sync_send: mpsc::UnboundedSender<SyncMessage<T::EthSpec>>,
|
||||
log: slog::Logger,
|
||||
) {
|
||||
executor.spawn(
|
||||
async move {
|
||||
let slot_duration = beacon_chain.slot_clock.slot_duration();
|
||||
let delay = beacon_chain.slot_clock.single_lookup_delay();
|
||||
let interval_start = match (
|
||||
beacon_chain.slot_clock.duration_to_next_slot(),
|
||||
beacon_chain.slot_clock.seconds_from_current_slot_start(),
|
||||
) {
|
||||
(Some(duration_to_next_slot), Some(seconds_from_current_slot_start)) => {
|
||||
let duration_until_start = if seconds_from_current_slot_start > delay {
|
||||
duration_to_next_slot + delay
|
||||
} else {
|
||||
delay - seconds_from_current_slot_start
|
||||
};
|
||||
tokio::time::Instant::now() + duration_until_start
|
||||
}
|
||||
_ => {
|
||||
crit!(log,
|
||||
"Failed to read slot clock, delayed lookup service timing will be inaccurate.\
|
||||
This may degrade performance"
|
||||
);
|
||||
Instant::now()
|
||||
}
|
||||
};
|
||||
|
||||
let mut interval = interval_at(interval_start, slot_duration);
|
||||
loop {
|
||||
interval.tick().await;
|
||||
while let Ok(msg) = delayed_lookups_recv.try_recv() {
|
||||
match msg {
|
||||
DelayedLookupMessage::MissingComponents(block_root) => {
|
||||
if let Err(e) = sync_send
|
||||
.send(SyncMessage::MissingGossipBlockComponentsDelayed(block_root))
|
||||
{
|
||||
warn!(log, "Failed to send delayed lookup message"; "error" => ?e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delayed_lookups",
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,18 @@
|
||||
use super::RootBlockTuple;
|
||||
use super::single_block_lookup::{LookupRequestError, LookupVerifyError, SingleBlockLookup};
|
||||
use super::{BlobRequestId, BlockRequestId, DownloadedBlocks, PeerShouldHave, ResponseType};
|
||||
use crate::sync::block_lookups::single_block_lookup::{State, UnknownParentComponents};
|
||||
use crate::sync::block_lookups::{RootBlobsTuple, RootBlockTuple};
|
||||
use crate::sync::{manager::SLOT_IMPORT_TOLERANCE, network_context::SyncNetworkContext};
|
||||
use beacon_chain::blob_verification::AsBlock;
|
||||
use beacon_chain::blob_verification::BlockWrapper;
|
||||
use beacon_chain::data_availability_checker::DataAvailabilityChecker;
|
||||
use beacon_chain::BeaconChainTypes;
|
||||
use lighthouse_network::PeerId;
|
||||
use std::sync::Arc;
|
||||
use store::Hash256;
|
||||
use strum::IntoStaticStr;
|
||||
|
||||
use crate::sync::block_lookups::ForceBlockRequest;
|
||||
use crate::sync::{
|
||||
manager::{Id, SLOT_IMPORT_TOLERANCE},
|
||||
network_context::SyncNetworkContext,
|
||||
};
|
||||
|
||||
use super::single_block_lookup::{self, SingleBlockRequest};
|
||||
use types::blob_sidecar::FixedBlobSidecarList;
|
||||
use types::{BlobSidecar, SignedBeaconBlock};
|
||||
|
||||
/// How many attempts we try to find a parent of a block before we give up trying.
|
||||
pub(crate) const PARENT_FAIL_TOLERANCE: u8 = 5;
|
||||
@@ -26,19 +26,22 @@ pub(crate) struct ParentLookup<T: BeaconChainTypes> {
|
||||
/// The root of the block triggering this parent request.
|
||||
chain_hash: Hash256,
|
||||
/// The blocks that have currently been downloaded.
|
||||
downloaded_blocks: Vec<RootBlockTuple<T::EthSpec>>,
|
||||
downloaded_blocks: Vec<DownloadedBlocks<T::EthSpec>>,
|
||||
/// Request of the last parent.
|
||||
current_parent_request: SingleBlockRequest<PARENT_FAIL_TOLERANCE>,
|
||||
/// Id of the last parent request.
|
||||
current_parent_request_id: Option<Id>,
|
||||
pub current_parent_request: SingleBlockLookup<PARENT_FAIL_TOLERANCE, T>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, IntoStaticStr)]
|
||||
pub enum VerifyError {
|
||||
pub enum ParentVerifyError {
|
||||
RootMismatch,
|
||||
NoBlockReturned,
|
||||
NotEnoughBlobsReturned,
|
||||
ExtraBlocksReturned,
|
||||
UnrequestedBlobId,
|
||||
ExtraBlobsReturned,
|
||||
InvalidIndex(u64),
|
||||
PreviousFailure { parent_root: Hash256 },
|
||||
BenignFailure,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -55,62 +58,143 @@ pub enum RequestError {
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> ParentLookup<T> {
|
||||
pub fn new(
|
||||
block_root: Hash256,
|
||||
parent_root: Hash256,
|
||||
peer_id: PeerShouldHave,
|
||||
da_checker: Arc<DataAvailabilityChecker<T>>,
|
||||
) -> Self {
|
||||
let current_parent_request =
|
||||
SingleBlockLookup::new(parent_root, Some(<_>::default()), &[peer_id], da_checker);
|
||||
|
||||
Self {
|
||||
chain_hash: block_root,
|
||||
downloaded_blocks: vec![],
|
||||
current_parent_request,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_block(&self, block_root: &Hash256) -> bool {
|
||||
self.downloaded_blocks
|
||||
.iter()
|
||||
.any(|(root, _d_block)| root == block_root)
|
||||
}
|
||||
|
||||
pub fn new(block_root: Hash256, block: BlockWrapper<T::EthSpec>, peer_id: PeerId) -> Self {
|
||||
let current_parent_request = SingleBlockRequest::new(block.parent_root(), peer_id);
|
||||
|
||||
Self {
|
||||
chain_hash: block_root,
|
||||
downloaded_blocks: vec![(block_root, block)],
|
||||
current_parent_request,
|
||||
current_parent_request_id: None,
|
||||
}
|
||||
pub fn is_for_block(&self, block_root: Hash256) -> bool {
|
||||
self.current_parent_request.is_for_block(block_root)
|
||||
}
|
||||
|
||||
/// Attempts to request the next unknown parent. If the request fails, it should be removed.
|
||||
pub fn request_parent(
|
||||
pub fn request_parent_block(
|
||||
&mut self,
|
||||
cx: &mut SyncNetworkContext<T>,
|
||||
force_block_request: ForceBlockRequest,
|
||||
) -> Result<(), RequestError> {
|
||||
// check to make sure this request hasn't failed
|
||||
if self.downloaded_blocks.len() >= PARENT_DEPTH_TOLERANCE {
|
||||
if self.downloaded_blocks.len() + 1 >= PARENT_DEPTH_TOLERANCE {
|
||||
return Err(RequestError::ChainTooLong);
|
||||
}
|
||||
|
||||
let (peer_id, request) = self.current_parent_request.request_block()?;
|
||||
match cx.parent_lookup_request(peer_id, request, force_block_request) {
|
||||
Ok(request_id) => {
|
||||
self.current_parent_request_id = Some(request_id);
|
||||
Ok(())
|
||||
}
|
||||
Err(reason) => {
|
||||
self.current_parent_request_id = None;
|
||||
Err(RequestError::SendFailed(reason))
|
||||
if let Some((peer_id, request)) = self.current_parent_request.request_block()? {
|
||||
match cx.parent_lookup_block_request(peer_id, request) {
|
||||
Ok(request_id) => {
|
||||
self.current_parent_request.id.block_request_id = Some(request_id);
|
||||
return Ok(());
|
||||
}
|
||||
Err(reason) => {
|
||||
self.current_parent_request.id.block_request_id = None;
|
||||
return Err(RequestError::SendFailed(reason));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_peer_disconnected(&mut self, peer_id: &PeerId) -> Result<(), ()> {
|
||||
self.current_parent_request.check_peer_disconnected(peer_id)
|
||||
pub fn request_parent_blobs(
|
||||
&mut self,
|
||||
cx: &mut SyncNetworkContext<T>,
|
||||
) -> Result<(), RequestError> {
|
||||
// check to make sure this request hasn't failed
|
||||
if self.downloaded_blocks.len() + 1 >= PARENT_DEPTH_TOLERANCE {
|
||||
return Err(RequestError::ChainTooLong);
|
||||
}
|
||||
|
||||
if let Some((peer_id, request)) = self.current_parent_request.request_blobs()? {
|
||||
match cx.parent_lookup_blobs_request(peer_id, request) {
|
||||
Ok(request_id) => {
|
||||
self.current_parent_request.id.blob_request_id = Some(request_id);
|
||||
return Ok(());
|
||||
}
|
||||
Err(reason) => {
|
||||
self.current_parent_request.id.blob_request_id = None;
|
||||
return Err(RequestError::SendFailed(reason));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_block(&mut self, block: BlockWrapper<T::EthSpec>) {
|
||||
pub fn check_block_peer_disconnected(&mut self, peer_id: &PeerId) -> Result<(), ()> {
|
||||
self.current_parent_request
|
||||
.block_request_state
|
||||
.state
|
||||
.check_peer_disconnected(peer_id)
|
||||
}
|
||||
|
||||
pub fn check_blob_peer_disconnected(&mut self, peer_id: &PeerId) -> Result<(), ()> {
|
||||
self.current_parent_request
|
||||
.blob_request_state
|
||||
.state
|
||||
.check_peer_disconnected(peer_id)
|
||||
}
|
||||
|
||||
pub fn add_unknown_parent_block(&mut self, block: BlockWrapper<T::EthSpec>) {
|
||||
let next_parent = block.parent_root();
|
||||
let current_root = self.current_parent_request.hash;
|
||||
|
||||
// Cache the block.
|
||||
let current_root = self
|
||||
.current_parent_request
|
||||
.block_request_state
|
||||
.requested_block_root;
|
||||
self.downloaded_blocks.push((current_root, block));
|
||||
self.current_parent_request.hash = next_parent;
|
||||
self.current_parent_request.state = single_block_lookup::State::AwaitingDownload;
|
||||
self.current_parent_request_id = None;
|
||||
|
||||
// Update the block request.
|
||||
self.current_parent_request
|
||||
.block_request_state
|
||||
.requested_block_root = next_parent;
|
||||
self.current_parent_request.block_request_state.state.state = State::AwaitingDownload;
|
||||
self.current_parent_request.id.block_request_id = None;
|
||||
|
||||
// Update the blobs request.
|
||||
self.current_parent_request.blob_request_state.state.state = State::AwaitingDownload;
|
||||
self.current_parent_request.id.blob_request_id = None;
|
||||
|
||||
// Reset the unknown parent components.
|
||||
self.current_parent_request.unknown_parent_components =
|
||||
Some(UnknownParentComponents::default());
|
||||
}
|
||||
|
||||
pub fn pending_response(&self, req_id: Id) -> bool {
|
||||
self.current_parent_request_id == Some(req_id)
|
||||
pub fn add_current_request_block(&mut self, block: Arc<SignedBeaconBlock<T::EthSpec>>) {
|
||||
// Cache the block.
|
||||
self.current_parent_request.add_unknown_parent_block(block);
|
||||
|
||||
// Update the request.
|
||||
self.current_parent_request.id.block_request_id = None;
|
||||
}
|
||||
|
||||
pub fn add_current_request_blobs(&mut self, blobs: FixedBlobSidecarList<T::EthSpec>) {
|
||||
// Cache the blobs.
|
||||
self.current_parent_request.add_unknown_parent_blobs(blobs);
|
||||
|
||||
// Update the request.
|
||||
self.current_parent_request.id.blob_request_id = None;
|
||||
}
|
||||
|
||||
pub fn pending_block_response(&self, req_id: BlockRequestId) -> bool {
|
||||
self.current_parent_request.id.block_request_id == Some(req_id)
|
||||
}
|
||||
|
||||
pub fn pending_blob_response(&self, req_id: BlobRequestId) -> bool {
|
||||
self.current_parent_request.id.blob_request_id == Some(req_id)
|
||||
}
|
||||
|
||||
/// Consumes the parent request and destructures it into it's parts.
|
||||
@@ -121,18 +205,17 @@ impl<T: BeaconChainTypes> ParentLookup<T> {
|
||||
Hash256,
|
||||
Vec<BlockWrapper<T::EthSpec>>,
|
||||
Vec<Hash256>,
|
||||
SingleBlockRequest<PARENT_FAIL_TOLERANCE>,
|
||||
SingleBlockLookup<PARENT_FAIL_TOLERANCE, T>,
|
||||
) {
|
||||
let ParentLookup {
|
||||
chain_hash,
|
||||
downloaded_blocks,
|
||||
current_parent_request,
|
||||
current_parent_request_id: _,
|
||||
} = self;
|
||||
let block_count = downloaded_blocks.len();
|
||||
let mut blocks = Vec::with_capacity(block_count);
|
||||
let mut hashes = Vec::with_capacity(block_count);
|
||||
for (hash, block) in downloaded_blocks {
|
||||
for (hash, block) in downloaded_blocks.into_iter() {
|
||||
blocks.push(block);
|
||||
hashes.push(hash);
|
||||
}
|
||||
@@ -144,23 +227,59 @@ impl<T: BeaconChainTypes> ParentLookup<T> {
|
||||
self.chain_hash
|
||||
}
|
||||
|
||||
pub fn download_failed(&mut self) {
|
||||
self.current_parent_request.register_failure_downloading();
|
||||
self.current_parent_request_id = None;
|
||||
pub fn block_download_failed(&mut self) {
|
||||
self.current_parent_request
|
||||
.block_request_state
|
||||
.state
|
||||
.register_failure_downloading();
|
||||
self.current_parent_request.id.block_request_id = None;
|
||||
}
|
||||
|
||||
pub fn processing_failed(&mut self) {
|
||||
self.current_parent_request.register_failure_processing();
|
||||
self.current_parent_request_id = None;
|
||||
pub fn blob_download_failed(&mut self) {
|
||||
self.current_parent_request
|
||||
.blob_request_state
|
||||
.state
|
||||
.register_failure_downloading();
|
||||
self.current_parent_request.id.blob_request_id = None;
|
||||
}
|
||||
|
||||
pub fn block_processing_failed(&mut self) {
|
||||
self.current_parent_request
|
||||
.block_request_state
|
||||
.state
|
||||
.register_failure_processing();
|
||||
if let Some(components) = self
|
||||
.current_parent_request
|
||||
.unknown_parent_components
|
||||
.as_mut()
|
||||
{
|
||||
components.downloaded_block = None;
|
||||
}
|
||||
self.current_parent_request.id.block_request_id = None;
|
||||
}
|
||||
|
||||
pub fn blob_processing_failed(&mut self) {
|
||||
self.current_parent_request
|
||||
.blob_request_state
|
||||
.state
|
||||
.register_failure_processing();
|
||||
if let Some(components) = self
|
||||
.current_parent_request
|
||||
.unknown_parent_components
|
||||
.as_mut()
|
||||
{
|
||||
components.downloaded_blobs = <_>::default();
|
||||
}
|
||||
self.current_parent_request.id.blob_request_id = None;
|
||||
}
|
||||
|
||||
/// Verifies that the received block is what we requested. If so, parent lookup now waits for
|
||||
/// the processing result of the block.
|
||||
pub fn verify_block(
|
||||
&mut self,
|
||||
block: Option<BlockWrapper<T::EthSpec>>,
|
||||
block: Option<Arc<SignedBeaconBlock<T::EthSpec>>>,
|
||||
failed_chains: &mut lru_cache::LRUTimeCache<Hash256>,
|
||||
) -> Result<Option<RootBlockTuple<T::EthSpec>>, VerifyError> {
|
||||
) -> Result<Option<RootBlockTuple<T::EthSpec>>, ParentVerifyError> {
|
||||
let root_and_block = self.current_parent_request.verify_block(block)?;
|
||||
|
||||
// check if the parent of this block isn't in the failed cache. If it is, this chain should
|
||||
@@ -170,50 +289,83 @@ impl<T: BeaconChainTypes> ParentLookup<T> {
|
||||
.map(|(_, block)| block.parent_root())
|
||||
{
|
||||
if failed_chains.contains(&parent_root) {
|
||||
self.current_parent_request.register_failure_downloading();
|
||||
self.current_parent_request_id = None;
|
||||
return Err(VerifyError::PreviousFailure { parent_root });
|
||||
self.current_parent_request
|
||||
.block_request_state
|
||||
.state
|
||||
.register_failure_downloading();
|
||||
self.current_parent_request.id.block_request_id = None;
|
||||
return Err(ParentVerifyError::PreviousFailure { parent_root });
|
||||
}
|
||||
}
|
||||
|
||||
Ok(root_and_block)
|
||||
}
|
||||
|
||||
pub fn get_processing_peer(&self, chain_hash: Hash256) -> Option<PeerId> {
|
||||
if self.chain_hash == chain_hash {
|
||||
return self.current_parent_request.processing_peer().ok();
|
||||
pub fn verify_blob(
|
||||
&mut self,
|
||||
blob: Option<Arc<BlobSidecar<T::EthSpec>>>,
|
||||
failed_chains: &mut lru_cache::LRUTimeCache<Hash256>,
|
||||
) -> Result<Option<RootBlobsTuple<T::EthSpec>>, ParentVerifyError> {
|
||||
let parent_root_opt = blob.as_ref().map(|b| b.block_parent_root);
|
||||
let blobs = self.current_parent_request.verify_blob(blob)?;
|
||||
|
||||
// check if the parent of this block isn't in the failed cache. If it is, this chain should
|
||||
// be dropped and the peer downscored.
|
||||
if let Some(parent_root) = parent_root_opt {
|
||||
if failed_chains.contains(&parent_root) {
|
||||
self.current_parent_request
|
||||
.blob_request_state
|
||||
.state
|
||||
.register_failure_downloading();
|
||||
self.current_parent_request.id.blob_request_id = None;
|
||||
return Err(ParentVerifyError::PreviousFailure { parent_root });
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
Ok(blobs)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn failed_attempts(&self) -> u8 {
|
||||
self.current_parent_request.failed_attempts()
|
||||
pub fn add_peers(&mut self, peer_source: &[PeerShouldHave]) {
|
||||
self.current_parent_request.add_peers(peer_source)
|
||||
}
|
||||
|
||||
pub fn add_peer(&mut self, block_root: &Hash256, peer_id: &PeerId) -> bool {
|
||||
self.current_parent_request.add_peer(block_root, peer_id)
|
||||
}
|
||||
|
||||
pub fn used_peers(&self) -> impl Iterator<Item = &PeerId> + '_ {
|
||||
self.current_parent_request.used_peers.iter()
|
||||
pub fn used_peers(&self, response_type: ResponseType) -> impl Iterator<Item = &PeerId> + '_ {
|
||||
match response_type {
|
||||
ResponseType::Block => self
|
||||
.current_parent_request
|
||||
.block_request_state
|
||||
.state
|
||||
.used_peers
|
||||
.iter(),
|
||||
ResponseType::Blob => self
|
||||
.current_parent_request
|
||||
.blob_request_state
|
||||
.state
|
||||
.used_peers
|
||||
.iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<super::single_block_lookup::VerifyError> for VerifyError {
|
||||
fn from(e: super::single_block_lookup::VerifyError) -> Self {
|
||||
use super::single_block_lookup::VerifyError as E;
|
||||
impl From<LookupVerifyError> for ParentVerifyError {
|
||||
fn from(e: LookupVerifyError) -> Self {
|
||||
use LookupVerifyError as E;
|
||||
match e {
|
||||
E::RootMismatch => VerifyError::RootMismatch,
|
||||
E::NoBlockReturned => VerifyError::NoBlockReturned,
|
||||
E::ExtraBlocksReturned => VerifyError::ExtraBlocksReturned,
|
||||
E::RootMismatch => ParentVerifyError::RootMismatch,
|
||||
E::NoBlockReturned => ParentVerifyError::NoBlockReturned,
|
||||
E::ExtraBlocksReturned => ParentVerifyError::ExtraBlocksReturned,
|
||||
E::UnrequestedBlobId => ParentVerifyError::UnrequestedBlobId,
|
||||
E::ExtraBlobsReturned => ParentVerifyError::ExtraBlobsReturned,
|
||||
E::InvalidIndex(index) => ParentVerifyError::InvalidIndex(index),
|
||||
E::NotEnoughBlobsReturned => ParentVerifyError::NotEnoughBlobsReturned,
|
||||
E::BenignFailure => ParentVerifyError::BenignFailure,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<super::single_block_lookup::LookupRequestError> for RequestError {
|
||||
fn from(e: super::single_block_lookup::LookupRequestError) -> Self {
|
||||
use super::single_block_lookup::LookupRequestError as E;
|
||||
impl From<LookupRequestError> for RequestError {
|
||||
fn from(e: LookupRequestError) -> Self {
|
||||
use LookupRequestError as E;
|
||||
match e {
|
||||
E::TooManyAttempts { cannot_process } => {
|
||||
RequestError::TooManyAttempts { cannot_process }
|
||||
|
||||
@@ -1,43 +1,210 @@
|
||||
use super::RootBlockTuple;
|
||||
use beacon_chain::blob_verification::AsBlock;
|
||||
use crate::sync::block_lookups::{BlobRequestId, BlockRequestId, RootBlobsTuple, RootBlockTuple};
|
||||
use crate::sync::network_context::SyncNetworkContext;
|
||||
use beacon_chain::blob_verification::BlockWrapper;
|
||||
use beacon_chain::get_block_root;
|
||||
use beacon_chain::data_availability_checker::DataAvailabilityChecker;
|
||||
use beacon_chain::{get_block_root, BeaconChainTypes};
|
||||
use lighthouse_network::rpc::methods::BlobsByRootRequest;
|
||||
use lighthouse_network::{rpc::BlocksByRootRequest, PeerId};
|
||||
use rand::seq::IteratorRandom;
|
||||
use ssz_types::VariableList;
|
||||
use std::collections::HashSet;
|
||||
use store::{EthSpec, Hash256};
|
||||
use std::ops::IndexMut;
|
||||
use std::sync::Arc;
|
||||
use store::Hash256;
|
||||
use strum::IntoStaticStr;
|
||||
use types::blob_sidecar::{BlobIdentifier, FixedBlobSidecarList};
|
||||
use types::{BlobSidecar, EthSpec, SignedBeaconBlock};
|
||||
|
||||
/// Object representing a single block lookup request.
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct SingleBlockRequest<const MAX_ATTEMPTS: u8> {
|
||||
/// The hash of the requested block.
|
||||
pub hash: Hash256,
|
||||
use super::{PeerShouldHave, ResponseType};
|
||||
|
||||
pub struct SingleBlockLookup<const MAX_ATTEMPTS: u8, T: BeaconChainTypes> {
|
||||
pub id: LookupId,
|
||||
pub block_request_state: BlockRequestState<MAX_ATTEMPTS>,
|
||||
pub blob_request_state: BlobRequestState<MAX_ATTEMPTS, T::EthSpec>,
|
||||
pub da_checker: Arc<DataAvailabilityChecker<T>>,
|
||||
/// Only necessary for requests triggered by an `UnknownBlockParent` or `UnknownBlockParent` because any
|
||||
/// blocks or blobs without parents won't hit the data availability cache.
|
||||
pub unknown_parent_components: Option<UnknownParentComponents<T::EthSpec>>,
|
||||
/// We may want to delay the actual request trigger to give us a chance to receive all block
|
||||
/// components over gossip.
|
||||
pub triggered: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct LookupId {
|
||||
pub block_request_id: Option<BlockRequestId>,
|
||||
pub blob_request_id: Option<BlobRequestId>,
|
||||
}
|
||||
|
||||
pub struct BlobRequestState<const MAX_ATTEMPTS: u8, T: EthSpec> {
|
||||
pub requested_ids: Vec<BlobIdentifier>,
|
||||
/// Where we store blobs until we receive the stream terminator.
|
||||
pub blob_download_queue: FixedBlobSidecarList<T>,
|
||||
pub state: SingleLookupRequestState<MAX_ATTEMPTS>,
|
||||
}
|
||||
|
||||
impl<const MAX_ATTEMPTS: u8, T: EthSpec> BlobRequestState<MAX_ATTEMPTS, T> {
|
||||
pub fn new(peer_source: &[PeerShouldHave]) -> Self {
|
||||
Self {
|
||||
requested_ids: <_>::default(),
|
||||
blob_download_queue: <_>::default(),
|
||||
state: SingleLookupRequestState::new(peer_source),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BlockRequestState<const MAX_ATTEMPTS: u8> {
|
||||
pub requested_block_root: Hash256,
|
||||
pub state: SingleLookupRequestState<MAX_ATTEMPTS>,
|
||||
}
|
||||
|
||||
impl<const MAX_ATTEMPTS: u8> BlockRequestState<MAX_ATTEMPTS> {
|
||||
pub fn new(block_root: Hash256, peers: &[PeerShouldHave]) -> Self {
|
||||
Self {
|
||||
requested_block_root: block_root,
|
||||
state: SingleLookupRequestState::new(peers),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MAX_ATTEMPTS: u8, T: BeaconChainTypes> SingleBlockLookup<MAX_ATTEMPTS, T> {
|
||||
pub(crate) fn register_failure_downloading(&mut self, response_type: ResponseType) {
|
||||
match response_type {
|
||||
ResponseType::Block => self
|
||||
.block_request_state
|
||||
.state
|
||||
.register_failure_downloading(),
|
||||
ResponseType::Blob => self.blob_request_state.state.register_failure_downloading(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MAX_ATTEMPTS: u8, T: BeaconChainTypes> SingleBlockLookup<MAX_ATTEMPTS, T> {
|
||||
pub(crate) fn downloading(&mut self, response_type: ResponseType) -> bool {
|
||||
match response_type {
|
||||
ResponseType::Block => {
|
||||
matches!(
|
||||
self.block_request_state.state.state,
|
||||
State::Downloading { .. }
|
||||
)
|
||||
}
|
||||
ResponseType::Blob => {
|
||||
matches!(
|
||||
self.blob_request_state.state.state,
|
||||
State::Downloading { .. }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn remove_peer_if_useless(&mut self, peer_id: &PeerId, response_type: ResponseType) {
|
||||
match response_type {
|
||||
ResponseType::Block => self
|
||||
.block_request_state
|
||||
.state
|
||||
.remove_peer_if_useless(peer_id),
|
||||
ResponseType::Blob => self
|
||||
.blob_request_state
|
||||
.state
|
||||
.remove_peer_if_useless(peer_id),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_peer_disconnected(
|
||||
&mut self,
|
||||
peer_id: &PeerId,
|
||||
response_type: ResponseType,
|
||||
) -> Result<(), ()> {
|
||||
match response_type {
|
||||
ResponseType::Block => self
|
||||
.block_request_state
|
||||
.state
|
||||
.check_peer_disconnected(peer_id),
|
||||
ResponseType::Blob => self
|
||||
.blob_request_state
|
||||
.state
|
||||
.check_peer_disconnected(peer_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// For requests triggered by an `UnknownBlockParent` or `UnknownBlockParent`, this struct
|
||||
/// is used to cache components as they are sent to the networking layer. We can't use the
|
||||
/// data availability cache currently because any blocks or blobs without parents won't hit
|
||||
/// won't pass validation and therefore won't make it into the cache.
|
||||
#[derive(Default)]
|
||||
pub struct UnknownParentComponents<E: EthSpec> {
|
||||
pub downloaded_block: Option<Arc<SignedBeaconBlock<E>>>,
|
||||
pub downloaded_blobs: FixedBlobSidecarList<E>,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> UnknownParentComponents<E> {
|
||||
pub fn new(
|
||||
block: Option<Arc<SignedBeaconBlock<E>>>,
|
||||
blobs: Option<FixedBlobSidecarList<E>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
downloaded_block: block,
|
||||
downloaded_blobs: blobs.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
pub fn add_unknown_parent_block(&mut self, block: Arc<SignedBeaconBlock<E>>) {
|
||||
self.downloaded_block = Some(block);
|
||||
}
|
||||
pub fn add_unknown_parent_blobs(&mut self, blobs: FixedBlobSidecarList<E>) {
|
||||
for (index, blob_opt) in self.downloaded_blobs.iter_mut().enumerate() {
|
||||
if let Some(Some(downloaded_blob)) = blobs.get(index) {
|
||||
*blob_opt = Some(downloaded_blob.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn downloaded_indices(&self) -> HashSet<usize> {
|
||||
self.downloaded_blobs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, blob_opt)| blob_opt.as_ref().map(|_| i))
|
||||
.collect::<HashSet<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
/// Object representing the state of a single block or blob lookup request.
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct SingleLookupRequestState<const MAX_ATTEMPTS: u8> {
|
||||
/// State of this request.
|
||||
pub state: State,
|
||||
/// Peers that should have this block.
|
||||
/// Peers that should have this block or blob.
|
||||
pub available_peers: HashSet<PeerId>,
|
||||
/// Peers that mar or may not have this block or blob.
|
||||
pub potential_peers: HashSet<PeerId>,
|
||||
/// Peers from which we have requested this block.
|
||||
pub used_peers: HashSet<PeerId>,
|
||||
/// How many times have we attempted to process this block.
|
||||
/// How many times have we attempted to process this block or blob.
|
||||
failed_processing: u8,
|
||||
/// How many times have we attempted to download this block.
|
||||
/// How many times have we attempted to download this block or blob.
|
||||
failed_downloading: u8,
|
||||
pub component_processed: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum State {
|
||||
AwaitingDownload,
|
||||
Downloading { peer_id: PeerId },
|
||||
Processing { peer_id: PeerId },
|
||||
Downloading { peer_id: PeerShouldHave },
|
||||
Processing { peer_id: PeerShouldHave },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, IntoStaticStr)]
|
||||
pub enum VerifyError {
|
||||
pub enum LookupVerifyError {
|
||||
RootMismatch,
|
||||
NoBlockReturned,
|
||||
ExtraBlocksReturned,
|
||||
UnrequestedBlobId,
|
||||
ExtraBlobsReturned,
|
||||
NotEnoughBlobsReturned,
|
||||
InvalidIndex(u64),
|
||||
/// We don't have enough information to know
|
||||
/// whether the peer is at fault or simply missed
|
||||
/// what was requested on gossip.
|
||||
BenignFailure,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, IntoStaticStr)]
|
||||
@@ -50,15 +217,465 @@ pub enum LookupRequestError {
|
||||
NoPeers,
|
||||
}
|
||||
|
||||
impl<const MAX_ATTEMPTS: u8> SingleBlockRequest<MAX_ATTEMPTS> {
|
||||
pub fn new(hash: Hash256, peer_id: PeerId) -> Self {
|
||||
impl<const MAX_ATTEMPTS: u8, T: BeaconChainTypes> SingleBlockLookup<MAX_ATTEMPTS, T> {
|
||||
pub fn new(
|
||||
requested_block_root: Hash256,
|
||||
unknown_parent_components: Option<UnknownParentComponents<T::EthSpec>>,
|
||||
peers: &[PeerShouldHave],
|
||||
da_checker: Arc<DataAvailabilityChecker<T>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: <_>::default(),
|
||||
block_request_state: BlockRequestState::new(requested_block_root, peers),
|
||||
blob_request_state: BlobRequestState::new(peers),
|
||||
da_checker,
|
||||
unknown_parent_components,
|
||||
triggered: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_for_block(&self, block_root: Hash256) -> bool {
|
||||
self.block_request_state.requested_block_root == block_root
|
||||
}
|
||||
|
||||
/// Send the necessary request for blobs and blocks and update `self.id` with the latest
|
||||
/// request `Id`s. This will return `Err(())` if neither the block nor blob request could be made
|
||||
/// or are no longer required.
|
||||
pub fn request_block_and_blobs(&mut self, cx: &mut SyncNetworkContext<T>) -> Result<(), ()> {
|
||||
let block_request_id = if let Ok(Some((peer_id, block_request))) = self.request_block() {
|
||||
cx.single_block_lookup_request(peer_id, block_request).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let blob_request_id = if let Ok(Some((peer_id, blob_request))) = self.request_blobs() {
|
||||
cx.single_blobs_lookup_request(peer_id, blob_request).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if block_request_id.is_none() && blob_request_id.is_none() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
self.id = LookupId {
|
||||
block_request_id,
|
||||
blob_request_id,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_blobs_request(&mut self) {
|
||||
self.blob_request_state.requested_ids = if let Some(components) =
|
||||
self.unknown_parent_components.as_ref()
|
||||
{
|
||||
let blobs = components.downloaded_indices();
|
||||
self.da_checker
|
||||
.get_missing_blob_ids(
|
||||
self.block_request_state.requested_block_root,
|
||||
components.downloaded_block.as_ref(),
|
||||
Some(blobs),
|
||||
)
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
self.da_checker
|
||||
.get_missing_blob_ids_checking_cache(self.block_request_state.requested_block_root)
|
||||
.unwrap_or_default()
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_downloaded_block(&mut self) -> Option<BlockWrapper<T::EthSpec>> {
|
||||
self.unknown_parent_components
|
||||
.as_mut()
|
||||
.and_then(|components| {
|
||||
let downloaded_block = components.downloaded_block.as_ref();
|
||||
let downloaded_indices = components.downloaded_indices();
|
||||
let missing_ids = self.da_checker.get_missing_blob_ids(
|
||||
self.block_request_state.requested_block_root,
|
||||
downloaded_block,
|
||||
Some(downloaded_indices),
|
||||
);
|
||||
let download_complete =
|
||||
missing_ids.map_or(true, |missing_ids| missing_ids.is_empty());
|
||||
if download_complete {
|
||||
let UnknownParentComponents {
|
||||
downloaded_block,
|
||||
downloaded_blobs,
|
||||
} = components;
|
||||
downloaded_block.as_ref().map(|block| {
|
||||
BlockWrapper::BlockAndBlobs(block.clone(), std::mem::take(downloaded_blobs))
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_unknown_parent_components(
|
||||
&mut self,
|
||||
components: UnknownParentComponents<T::EthSpec>,
|
||||
) {
|
||||
if let Some(ref mut existing_components) = self.unknown_parent_components {
|
||||
let UnknownParentComponents {
|
||||
downloaded_block,
|
||||
downloaded_blobs,
|
||||
} = components;
|
||||
if let Some(block) = downloaded_block {
|
||||
existing_components.add_unknown_parent_block(block);
|
||||
}
|
||||
existing_components.add_unknown_parent_blobs(downloaded_blobs);
|
||||
} else {
|
||||
self.unknown_parent_components = Some(components);
|
||||
}
|
||||
}
|
||||
pub fn add_unknown_parent_block(&mut self, block: Arc<SignedBeaconBlock<T::EthSpec>>) {
|
||||
if let Some(ref mut components) = self.unknown_parent_components {
|
||||
components.add_unknown_parent_block(block)
|
||||
} else {
|
||||
self.unknown_parent_components = Some(UnknownParentComponents {
|
||||
downloaded_block: Some(block),
|
||||
downloaded_blobs: FixedBlobSidecarList::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_unknown_parent_blobs(&mut self, blobs: FixedBlobSidecarList<T::EthSpec>) {
|
||||
if let Some(ref mut components) = self.unknown_parent_components {
|
||||
components.add_unknown_parent_blobs(blobs)
|
||||
} else {
|
||||
self.unknown_parent_components = Some(UnknownParentComponents {
|
||||
downloaded_block: None,
|
||||
downloaded_blobs: blobs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies if the received block matches the requested one.
|
||||
/// Returns the block for processing if the response is what we expected.
|
||||
pub fn verify_block(
|
||||
&mut self,
|
||||
block: Option<Arc<SignedBeaconBlock<T::EthSpec>>>,
|
||||
) -> Result<Option<RootBlockTuple<T::EthSpec>>, LookupVerifyError> {
|
||||
match self.block_request_state.state.state {
|
||||
State::AwaitingDownload => {
|
||||
self.block_request_state
|
||||
.state
|
||||
.register_failure_downloading();
|
||||
Err(LookupVerifyError::ExtraBlocksReturned)
|
||||
}
|
||||
State::Downloading { peer_id } => {
|
||||
match block {
|
||||
Some(block) => {
|
||||
// Compute the block root using this specific function so that we can get timing
|
||||
// metrics.
|
||||
let block_root = get_block_root(&block);
|
||||
if block_root != self.block_request_state.requested_block_root {
|
||||
// return an error and drop the block
|
||||
// NOTE: we take this is as a download failure to prevent counting the
|
||||
// attempt as a chain failure, but simply a peer failure.
|
||||
self.block_request_state
|
||||
.state
|
||||
.register_failure_downloading();
|
||||
Err(LookupVerifyError::RootMismatch)
|
||||
} else {
|
||||
// Return the block for processing.
|
||||
self.block_request_state.state.state = State::Processing { peer_id };
|
||||
Ok(Some((block_root, block)))
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if peer_id.should_have_block() {
|
||||
self.block_request_state
|
||||
.state
|
||||
.register_failure_downloading();
|
||||
Err(LookupVerifyError::NoBlockReturned)
|
||||
} else {
|
||||
self.block_request_state.state.state = State::AwaitingDownload;
|
||||
Err(LookupVerifyError::BenignFailure)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
State::Processing { peer_id: _ } => match block {
|
||||
Some(_) => {
|
||||
// We sent the block for processing and received an extra block.
|
||||
self.block_request_state
|
||||
.state
|
||||
.register_failure_downloading();
|
||||
Err(LookupVerifyError::ExtraBlocksReturned)
|
||||
}
|
||||
None => {
|
||||
// This is simply the stream termination and we are already processing the
|
||||
// block
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_blob(
|
||||
&mut self,
|
||||
blob: Option<Arc<BlobSidecar<T::EthSpec>>>,
|
||||
) -> Result<Option<RootBlobsTuple<T::EthSpec>>, LookupVerifyError> {
|
||||
match self.blob_request_state.state.state {
|
||||
State::AwaitingDownload => {
|
||||
self.blob_request_state.state.register_failure_downloading();
|
||||
Err(LookupVerifyError::ExtraBlobsReturned)
|
||||
}
|
||||
State::Downloading {
|
||||
peer_id: peer_source,
|
||||
} => match blob {
|
||||
Some(blob) => {
|
||||
let received_id = blob.id();
|
||||
if !self.blob_request_state.requested_ids.contains(&received_id) {
|
||||
self.blob_request_state.state.register_failure_downloading();
|
||||
Err(LookupVerifyError::UnrequestedBlobId)
|
||||
} else {
|
||||
// State should remain downloading until we receive the stream terminator.
|
||||
self.blob_request_state
|
||||
.requested_ids
|
||||
.retain(|id| *id != received_id);
|
||||
let blob_index = blob.index;
|
||||
|
||||
if blob_index >= T::EthSpec::max_blobs_per_block() as u64 {
|
||||
return Err(LookupVerifyError::InvalidIndex(blob.index));
|
||||
}
|
||||
*self
|
||||
.blob_request_state
|
||||
.blob_download_queue
|
||||
.index_mut(blob_index as usize) = Some(blob);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.blob_request_state.state.state = State::Processing {
|
||||
peer_id: peer_source,
|
||||
};
|
||||
Ok(Some((
|
||||
self.block_request_state.requested_block_root,
|
||||
std::mem::take(&mut self.blob_request_state.blob_download_queue),
|
||||
)))
|
||||
}
|
||||
},
|
||||
State::Processing { peer_id: _ } => match blob {
|
||||
Some(_) => {
|
||||
// We sent the blob for processing and received an extra blob.
|
||||
self.blob_request_state.state.register_failure_downloading();
|
||||
Err(LookupVerifyError::ExtraBlobsReturned)
|
||||
}
|
||||
None => {
|
||||
// This is simply the stream termination and we are already processing the
|
||||
// block
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_block(
|
||||
&mut self,
|
||||
) -> Result<Option<(PeerId, BlocksByRootRequest)>, LookupRequestError> {
|
||||
let block_already_downloaded =
|
||||
if let Some(components) = self.unknown_parent_components.as_ref() {
|
||||
components.downloaded_block.is_some()
|
||||
} else {
|
||||
self.da_checker
|
||||
.has_block(&self.block_request_state.requested_block_root)
|
||||
};
|
||||
|
||||
if block_already_downloaded {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
debug_assert!(matches!(
|
||||
self.block_request_state.state.state,
|
||||
State::AwaitingDownload
|
||||
));
|
||||
let request = BlocksByRootRequest {
|
||||
block_roots: VariableList::from(vec![self.block_request_state.requested_block_root]),
|
||||
};
|
||||
let response_type = ResponseType::Block;
|
||||
if self.too_many_attempts(response_type) {
|
||||
Err(LookupRequestError::TooManyAttempts {
|
||||
cannot_process: self.cannot_process(response_type),
|
||||
})
|
||||
} else if let Some(peer_id) = self.get_peer(response_type) {
|
||||
self.add_used_peer(peer_id, response_type);
|
||||
Ok(Some((peer_id.to_peer_id(), request)))
|
||||
} else {
|
||||
Err(LookupRequestError::NoPeers)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_blobs(
|
||||
&mut self,
|
||||
) -> Result<Option<(PeerId, BlobsByRootRequest)>, LookupRequestError> {
|
||||
self.update_blobs_request();
|
||||
|
||||
if self.blob_request_state.requested_ids.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
debug_assert!(matches!(
|
||||
self.blob_request_state.state.state,
|
||||
State::AwaitingDownload
|
||||
));
|
||||
let request = BlobsByRootRequest {
|
||||
blob_ids: VariableList::from(self.blob_request_state.requested_ids.clone()),
|
||||
};
|
||||
let response_type = ResponseType::Blob;
|
||||
if self.too_many_attempts(response_type) {
|
||||
Err(LookupRequestError::TooManyAttempts {
|
||||
cannot_process: self.cannot_process(response_type),
|
||||
})
|
||||
} else if let Some(peer_id) = self.get_peer(response_type) {
|
||||
self.add_used_peer(peer_id, response_type);
|
||||
Ok(Some((peer_id.to_peer_id(), request)))
|
||||
} else {
|
||||
Err(LookupRequestError::NoPeers)
|
||||
}
|
||||
}
|
||||
|
||||
fn too_many_attempts(&self, response_type: ResponseType) -> bool {
|
||||
match response_type {
|
||||
ResponseType::Block => self.block_request_state.state.failed_attempts() >= MAX_ATTEMPTS,
|
||||
ResponseType::Blob => self.blob_request_state.state.failed_attempts() >= MAX_ATTEMPTS,
|
||||
}
|
||||
}
|
||||
|
||||
fn cannot_process(&self, response_type: ResponseType) -> bool {
|
||||
match response_type {
|
||||
ResponseType::Block => {
|
||||
self.block_request_state.state.failed_processing
|
||||
>= self.block_request_state.state.failed_downloading
|
||||
}
|
||||
ResponseType::Blob => {
|
||||
self.blob_request_state.state.failed_processing
|
||||
>= self.blob_request_state.state.failed_downloading
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_peer(&self, response_type: ResponseType) -> Option<PeerShouldHave> {
|
||||
match response_type {
|
||||
ResponseType::Block => self
|
||||
.block_request_state
|
||||
.state
|
||||
.available_peers
|
||||
.iter()
|
||||
.choose(&mut rand::thread_rng())
|
||||
.copied()
|
||||
.map(PeerShouldHave::BlockAndBlobs)
|
||||
.or(self
|
||||
.block_request_state
|
||||
.state
|
||||
.potential_peers
|
||||
.iter()
|
||||
.choose(&mut rand::thread_rng())
|
||||
.copied()
|
||||
.map(PeerShouldHave::Neither)),
|
||||
ResponseType::Blob => self
|
||||
.blob_request_state
|
||||
.state
|
||||
.available_peers
|
||||
.iter()
|
||||
.choose(&mut rand::thread_rng())
|
||||
.copied()
|
||||
.map(PeerShouldHave::BlockAndBlobs)
|
||||
.or(self
|
||||
.blob_request_state
|
||||
.state
|
||||
.potential_peers
|
||||
.iter()
|
||||
.choose(&mut rand::thread_rng())
|
||||
.copied()
|
||||
.map(PeerShouldHave::Neither)),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_used_peer(&mut self, peer_id: PeerShouldHave, response_type: ResponseType) {
|
||||
match response_type {
|
||||
ResponseType::Block => {
|
||||
self.block_request_state
|
||||
.state
|
||||
.used_peers
|
||||
.insert(peer_id.to_peer_id());
|
||||
self.block_request_state.state.state = State::Downloading { peer_id };
|
||||
}
|
||||
ResponseType::Blob => {
|
||||
self.blob_request_state
|
||||
.state
|
||||
.used_peers
|
||||
.insert(peer_id.to_peer_id());
|
||||
self.blob_request_state.state.state = State::Downloading { peer_id };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_peers(&mut self, peers: &[PeerShouldHave]) {
|
||||
for peer in peers {
|
||||
match peer {
|
||||
PeerShouldHave::BlockAndBlobs(peer_id) => {
|
||||
self.block_request_state.state.add_peer(peer_id);
|
||||
self.blob_request_state.state.add_peer(peer_id);
|
||||
}
|
||||
PeerShouldHave::Neither(peer_id) => {
|
||||
self.block_request_state.state.add_potential_peer(peer_id);
|
||||
self.blob_request_state.state.add_potential_peer(peer_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn processing_peer(&self, response_type: ResponseType) -> Result<PeerShouldHave, ()> {
|
||||
match response_type {
|
||||
ResponseType::Block => self.block_request_state.state.processing_peer(),
|
||||
ResponseType::Blob => self.blob_request_state.state.processing_peer(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn downloading_peer(&self, response_type: ResponseType) -> Result<PeerShouldHave, ()> {
|
||||
match response_type {
|
||||
ResponseType::Block => self.block_request_state.state.peer(),
|
||||
ResponseType::Blob => self.blob_request_state.state.peer(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn both_components_processed(&self) -> bool {
|
||||
self.block_request_state.state.component_processed
|
||||
&& self.blob_request_state.state.component_processed
|
||||
}
|
||||
|
||||
pub fn set_component_processed(&mut self, response_type: ResponseType) {
|
||||
match response_type {
|
||||
ResponseType::Block => self.block_request_state.state.component_processed = true,
|
||||
ResponseType::Blob => self.blob_request_state.state.component_processed = true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MAX_ATTEMPTS: u8> SingleLookupRequestState<MAX_ATTEMPTS> {
|
||||
pub fn new(peers: &[PeerShouldHave]) -> Self {
|
||||
let mut available_peers = HashSet::default();
|
||||
let mut potential_peers = HashSet::default();
|
||||
for peer in peers {
|
||||
match peer {
|
||||
PeerShouldHave::BlockAndBlobs(peer_id) => {
|
||||
available_peers.insert(*peer_id);
|
||||
}
|
||||
PeerShouldHave::Neither(peer_id) => {
|
||||
potential_peers.insert(*peer_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
Self {
|
||||
hash,
|
||||
state: State::AwaitingDownload,
|
||||
available_peers: HashSet::from([peer_id]),
|
||||
available_peers,
|
||||
potential_peers,
|
||||
used_peers: HashSet::default(),
|
||||
failed_processing: 0,
|
||||
failed_downloading: 0,
|
||||
component_processed: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,19 +697,23 @@ impl<const MAX_ATTEMPTS: u8> SingleBlockRequest<MAX_ATTEMPTS> {
|
||||
self.failed_processing + self.failed_downloading
|
||||
}
|
||||
|
||||
pub fn add_peer(&mut self, hash: &Hash256, peer_id: &PeerId) -> bool {
|
||||
let is_useful = &self.hash == hash;
|
||||
if is_useful {
|
||||
self.available_peers.insert(*peer_id);
|
||||
pub fn add_peer(&mut self, peer_id: &PeerId) {
|
||||
self.potential_peers.remove(peer_id);
|
||||
self.available_peers.insert(*peer_id);
|
||||
}
|
||||
|
||||
pub fn add_potential_peer(&mut self, peer_id: &PeerId) {
|
||||
if !self.available_peers.contains(peer_id) {
|
||||
self.potential_peers.insert(*peer_id);
|
||||
}
|
||||
is_useful
|
||||
}
|
||||
|
||||
/// If a peer disconnects, this request could be failed. If so, an error is returned
|
||||
pub fn check_peer_disconnected(&mut self, dc_peer_id: &PeerId) -> Result<(), ()> {
|
||||
self.available_peers.remove(dc_peer_id);
|
||||
self.potential_peers.remove(dc_peer_id);
|
||||
if let State::Downloading { peer_id } = &self.state {
|
||||
if peer_id == dc_peer_id {
|
||||
if peer_id.as_peer_id() == dc_peer_id {
|
||||
// Peer disconnected before providing a block
|
||||
self.register_failure_downloading();
|
||||
return Err(());
|
||||
@@ -101,90 +722,67 @@ impl<const MAX_ATTEMPTS: u8> SingleBlockRequest<MAX_ATTEMPTS> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verifies if the received block matches the requested one.
|
||||
/// Returns the block for processing if the response is what we expected.
|
||||
pub fn verify_block<T: EthSpec>(
|
||||
&mut self,
|
||||
block: Option<BlockWrapper<T>>,
|
||||
) -> Result<Option<RootBlockTuple<T>>, VerifyError> {
|
||||
match self.state {
|
||||
State::AwaitingDownload => {
|
||||
self.register_failure_downloading();
|
||||
Err(VerifyError::ExtraBlocksReturned)
|
||||
}
|
||||
State::Downloading { peer_id } => match block {
|
||||
Some(block) => {
|
||||
// Compute the block root using this specific function so that we can get timing
|
||||
// metrics.
|
||||
let block_root = get_block_root(block.as_block());
|
||||
if block_root != self.hash {
|
||||
// return an error and drop the block
|
||||
// NOTE: we take this is as a download failure to prevent counting the
|
||||
// attempt as a chain failure, but simply a peer failure.
|
||||
self.register_failure_downloading();
|
||||
Err(VerifyError::RootMismatch)
|
||||
} else {
|
||||
// Return the block for processing.
|
||||
self.state = State::Processing { peer_id };
|
||||
Ok(Some((block_root, block)))
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.register_failure_downloading();
|
||||
Err(VerifyError::NoBlockReturned)
|
||||
}
|
||||
},
|
||||
State::Processing { peer_id: _ } => match block {
|
||||
Some(_) => {
|
||||
// We sent the block for processing and received an extra block.
|
||||
self.register_failure_downloading();
|
||||
Err(VerifyError::ExtraBlocksReturned)
|
||||
}
|
||||
None => {
|
||||
// This is simply the stream termination and we are already processing the
|
||||
// block
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_block(&mut self) -> Result<(PeerId, BlocksByRootRequest), LookupRequestError> {
|
||||
debug_assert!(matches!(self.state, State::AwaitingDownload));
|
||||
if self.failed_attempts() >= MAX_ATTEMPTS {
|
||||
Err(LookupRequestError::TooManyAttempts {
|
||||
cannot_process: self.failed_processing >= self.failed_downloading,
|
||||
})
|
||||
} else if let Some(&peer_id) = self.available_peers.iter().choose(&mut rand::thread_rng()) {
|
||||
let request = BlocksByRootRequest {
|
||||
block_roots: VariableList::from(vec![self.hash]),
|
||||
};
|
||||
self.state = State::Downloading { peer_id };
|
||||
self.used_peers.insert(peer_id);
|
||||
Ok((peer_id, request))
|
||||
} else {
|
||||
Err(LookupRequestError::NoPeers)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn processing_peer(&self) -> Result<PeerId, ()> {
|
||||
pub fn processing_peer(&self) -> Result<PeerShouldHave, ()> {
|
||||
if let State::Processing { peer_id } = &self.state {
|
||||
Ok(*peer_id)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peer(&self) -> Result<PeerShouldHave, ()> {
|
||||
match &self.state {
|
||||
State::Processing { peer_id } => Ok(*peer_id),
|
||||
State::Downloading { peer_id } => Ok(*peer_id),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_peer_if_useless(&mut self, peer_id: &PeerId) {
|
||||
if !self.available_peers.is_empty() || self.potential_peers.len() > 1 {
|
||||
self.potential_peers.remove(peer_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MAX_ATTEMPTS: u8> slog::Value for SingleBlockRequest<MAX_ATTEMPTS> {
|
||||
impl<const MAX_ATTEMPTS: u8, T: BeaconChainTypes> slog::Value
|
||||
for SingleBlockLookup<MAX_ATTEMPTS, T>
|
||||
{
|
||||
fn serialize(
|
||||
&self,
|
||||
_record: &slog::Record,
|
||||
key: slog::Key,
|
||||
serializer: &mut dyn slog::Serializer,
|
||||
) -> slog::Result {
|
||||
serializer.emit_str("request", key)?;
|
||||
serializer.emit_arguments(
|
||||
"hash",
|
||||
&format_args!("{}", self.block_request_state.requested_block_root),
|
||||
)?;
|
||||
serializer.emit_arguments(
|
||||
"blob_ids",
|
||||
&format_args!("{:?}", self.blob_request_state.requested_ids),
|
||||
)?;
|
||||
serializer.emit_arguments(
|
||||
"block_request_state.state",
|
||||
&format_args!("{:?}", self.block_request_state.state),
|
||||
)?;
|
||||
serializer.emit_arguments(
|
||||
"blob_request_state.state",
|
||||
&format_args!("{:?}", self.blob_request_state.state),
|
||||
)?;
|
||||
slog::Result::Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const MAX_ATTEMPTS: u8> slog::Value for SingleLookupRequestState<MAX_ATTEMPTS> {
|
||||
fn serialize(
|
||||
&self,
|
||||
record: &slog::Record,
|
||||
key: slog::Key,
|
||||
serializer: &mut dyn slog::Serializer,
|
||||
) -> slog::Result {
|
||||
serializer.emit_str("request", key)?;
|
||||
serializer.emit_arguments("hash", &format_args!("{}", self.hash))?;
|
||||
serializer.emit_str("request_state", key)?;
|
||||
match &self.state {
|
||||
State::AwaitingDownload => {
|
||||
"awaiting_download".serialize(record, "state", serializer)?
|
||||
@@ -205,9 +803,16 @@ impl<const MAX_ATTEMPTS: u8> slog::Value for SingleBlockRequest<MAX_ATTEMPTS> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use beacon_chain::builder::Witness;
|
||||
use beacon_chain::eth1_chain::CachingEth1Backend;
|
||||
use sloggers::null::NullLoggerBuilder;
|
||||
use sloggers::Build;
|
||||
use slot_clock::{SlotClock, TestingSlotClock};
|
||||
use std::time::Duration;
|
||||
use store::{HotColdDB, MemoryStore, StoreConfig};
|
||||
use types::{
|
||||
test_utils::{SeedableRng, TestRandom, XorShiftRng},
|
||||
MinimalEthSpec as E, SignedBeaconBlock,
|
||||
ChainSpec, EthSpec, MinimalEthSpec as E, SignedBeaconBlock, Slot,
|
||||
};
|
||||
|
||||
fn rand_block() -> SignedBeaconBlock<E> {
|
||||
@@ -219,13 +824,27 @@ mod tests {
|
||||
types::Signature::random_for_test(&mut rng),
|
||||
)
|
||||
}
|
||||
type T = Witness<TestingSlotClock, CachingEth1Backend<E>, E, MemoryStore<E>, MemoryStore<E>>;
|
||||
|
||||
#[test]
|
||||
fn test_happy_path() {
|
||||
let peer_id = PeerId::random();
|
||||
let peer_id = PeerShouldHave::BlockAndBlobs(PeerId::random());
|
||||
let block = rand_block();
|
||||
|
||||
let mut sl = SingleBlockRequest::<4>::new(block.canonical_root(), peer_id);
|
||||
let spec = E::default_spec();
|
||||
let slot_clock = TestingSlotClock::new(
|
||||
Slot::new(0),
|
||||
Duration::from_secs(0),
|
||||
Duration::from_secs(spec.seconds_per_slot),
|
||||
);
|
||||
let log = NullLoggerBuilder.build().expect("logger should build");
|
||||
let store = HotColdDB::open_ephemeral(StoreConfig::default(), ChainSpec::minimal(), log)
|
||||
.expect("store");
|
||||
let da_checker = Arc::new(
|
||||
DataAvailabilityChecker::new(slot_clock, None, store.into(), spec)
|
||||
.expect("data availability checker"),
|
||||
);
|
||||
let mut sl =
|
||||
SingleBlockLookup::<4, T>::new(block.canonical_root(), None, &[peer_id], da_checker);
|
||||
sl.request_block().unwrap();
|
||||
sl.verify_block(Some(block.into())).unwrap().unwrap();
|
||||
}
|
||||
@@ -233,13 +852,32 @@ mod tests {
|
||||
#[test]
|
||||
fn test_block_lookup_failures() {
|
||||
const FAILURES: u8 = 3;
|
||||
let peer_id = PeerId::random();
|
||||
let peer_id = PeerShouldHave::BlockAndBlobs(PeerId::random());
|
||||
let block = rand_block();
|
||||
let spec = E::default_spec();
|
||||
let slot_clock = TestingSlotClock::new(
|
||||
Slot::new(0),
|
||||
Duration::from_secs(0),
|
||||
Duration::from_secs(spec.seconds_per_slot),
|
||||
);
|
||||
let log = NullLoggerBuilder.build().expect("logger should build");
|
||||
let store = HotColdDB::open_ephemeral(StoreConfig::default(), ChainSpec::minimal(), log)
|
||||
.expect("store");
|
||||
|
||||
let mut sl = SingleBlockRequest::<FAILURES>::new(block.canonical_root(), peer_id);
|
||||
let da_checker = Arc::new(
|
||||
DataAvailabilityChecker::new(slot_clock, None, store.into(), spec)
|
||||
.expect("data availability checker"),
|
||||
);
|
||||
|
||||
let mut sl = SingleBlockLookup::<FAILURES, T>::new(
|
||||
block.canonical_root(),
|
||||
None,
|
||||
&[peer_id],
|
||||
da_checker,
|
||||
);
|
||||
for _ in 1..FAILURES {
|
||||
sl.request_block().unwrap();
|
||||
sl.register_failure_downloading();
|
||||
sl.block_request_state.state.register_failure_downloading();
|
||||
}
|
||||
|
||||
// Now we receive the block and send it for processing
|
||||
@@ -247,7 +885,7 @@ mod tests {
|
||||
sl.verify_block(Some(block.into())).unwrap().unwrap();
|
||||
|
||||
// One processing failure maxes the available attempts
|
||||
sl.register_failure_processing();
|
||||
sl.block_request_state.state.register_failure_processing();
|
||||
assert_eq!(
|
||||
sl.request_block(),
|
||||
Err(LookupRequestError::TooManyAttempts {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user