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:
realbigsean
2023-06-15 12:59:10 -04:00
committed by GitHub
parent 5428e68943
commit a62e52f319
47 changed files with 4981 additions and 1309 deletions

View 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

View File

@@ -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 }

View File

@@ -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