diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 527680fc0d..5e5ece2356 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -6221,10 +6221,10 @@ impl BeaconChain { .contains_block(root) } - // TODO(gloas): implement this once issue #8956 is resolved pub fn envelope_is_known_to_fork_choice(&self, root: &Hash256) -> bool { - // for now just check the database - self.store.payload_envelope_exists(root).unwrap_or(false) + self.canonical_head + .fork_choice_read_lock() + .is_payload_received(root) } /// Determines the beacon proposer for the next slot. If that proposer is registered in the diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index 0135d7f5dd..5f7e236a96 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -720,17 +720,19 @@ impl NetworkBeaconProcessor { MessageAcceptance::Accept, ); } - GossipDataColumnError::ParentUnknown { parent_root, .. } => { + GossipDataColumnError::ParentUnknown { parent_root, slot } => { debug!( action = "requesting parent", %block_root, %parent_root, "Unknown parent hash for column" ); - self.send_sync_message(SyncMessage::UnknownParentDataColumn( + self.send_sync_message(SyncMessage::UnknownParentSidecarHeader { peer_id, - column_sidecar, - )); + block_root, + parent_root, + slot, + }); } GossipDataColumnError::PubkeyCacheTimeout | GossipDataColumnError::BeaconChainError(_) => { @@ -926,7 +928,7 @@ impl NetworkBeaconProcessor { %parent_root, "Unknown parent hash for partial column" ); - self.send_sync_message(SyncMessage::UnknownParentPartialDataColumn { + self.send_sync_message(SyncMessage::UnknownParentSidecarHeader { peer_id, block_root, parent_root, @@ -1143,10 +1145,12 @@ impl NetworkBeaconProcessor { %commitment, "Unknown parent hash for blob" ); - self.send_sync_message(SyncMessage::UnknownParentBlob( + self.send_sync_message(SyncMessage::UnknownParentSidecarHeader { peer_id, - blob_sidecar, - )); + block_root: root, + parent_root, + slot, + }); } GossipBlobError::PubkeyCacheTimeout | GossipBlobError::BeaconChainError(_) => { crit!( diff --git a/beacon_node/network/src/sync/block_lookups/mod.rs b/beacon_node/network/src/sync/block_lookups/mod.rs index 23c1167bfe..da6ab2c06d 100644 --- a/beacon_node/network/src/sync/block_lookups/mod.rs +++ b/beacon_node/network/src/sync/block_lookups/mod.rs @@ -21,16 +21,14 @@ //! returned to this module as `LookupRequestResult` variants. use self::parent_chain::{NodeChain, compute_parent_chains}; -pub use self::single_block_lookup::DownloadResult; -use self::single_block_lookup::{ - AwaitingParent, LookupRequestError, LookupResult, PeerType, SingleBlockLookup, -}; +pub use self::single_block_lookup::{AwaitingParent, DownloadResult}; +use self::single_block_lookup::{LookupRequestError, LookupResult, SingleBlockLookup}; use super::manager::{BlockProcessType, BlockProcessingResult, SLOT_IMPORT_TOLERANCE}; use super::network_context::{PeerGroup, RpcResponseError, SyncNetworkContext}; use crate::metrics; use crate::sync::SyncMessage; use crate::sync::block_lookups::parent_chain::find_oldest_fork_ancestor; -use beacon_chain::block_verification_types::AsBlock; +use crate::sync::block_lookups::single_block_lookup::PeerType; use beacon_chain::data_availability_checker::{ AvailabilityCheckError, AvailabilityCheckErrorCategory, }; @@ -87,28 +85,7 @@ type PayloadDownloadResponse = pub enum BlockComponent { Block(DownloadResult>>), - Blob(DownloadResult), - DataColumn(DownloadResult), - PartialDataColumn(DownloadResult), -} - -impl BlockComponent { - fn parent_root(&self) -> Hash256 { - match self { - BlockComponent::Block(block) => block.value.parent_root(), - BlockComponent::Blob(parent_root) - | BlockComponent::DataColumn(parent_root) - | BlockComponent::PartialDataColumn(parent_root) => parent_root.value, - } - } - fn get_type(&self) -> &'static str { - match self { - BlockComponent::Block(_) => "block", - BlockComponent::Blob(_) => "blob", - BlockComponent::DataColumn(_) => "data_column", - BlockComponent::PartialDataColumn(_) => "partial_data_column", - } - } + Sidecar, } pub type SingleLookupId = u32; @@ -200,31 +177,26 @@ impl BlockLookups { &mut self, block_root: Hash256, block_component: BlockComponent, + awaiting_parent: AwaitingParent, peer_id: PeerId, cx: &mut SyncNetworkContext, ) -> bool { - let parent_root = block_component.parent_root(); - // We don't know the child's fork yet (no block downloaded), use PreGloas conservatively. // The correct AwaitingParent will be set when the child's block downloads. - let awaiting = AwaitingParent::pre_gloas(parent_root); let parent_lookup_exists = - self.search_parent_of_child(awaiting, block_root, &[peer_id], cx); + self.search_parent_of_child(awaiting_parent, block_root, &[peer_id], cx); // Only create the child lookup if the parent exists if parent_lookup_exists { // `search_parent_of_child` ensures that the parent lookup exists so we can safely wait for it self.new_current_lookup( block_root, Some(block_component), - Some(parent_root), + Some(awaiting_parent), // On a `UnknownParentBlock` or `UnknownParentBlob` event the peer is not required // to have the rest of the block components (refer to decoupled blob gossip). Create // the lookup with zero peers to house the block components. &[], - &PeerType { - data: false, - payload: false, - }, + &PeerType::PreGloas, cx, ) } else { @@ -242,41 +214,7 @@ impl BlockLookups { peer_source: &[PeerId], cx: &mut SyncNetworkContext, ) -> bool { - self.new_current_lookup( - block_root, - None, - None, - peer_source, - &PeerType { - data: false, - payload: false, - }, - cx, - ) - } - - /// Search for a block triggered by a Gloas data column. The peer that sent the data column - /// is a valid data source, so mark it as data-capable. - /// - /// Returns true if the lookup is created or already exists - #[must_use = "only reference the new lookup if returns true"] - pub fn search_unknown_block_with_data_peer( - &mut self, - block_root: Hash256, - peer_source: &[PeerId], - cx: &mut SyncNetworkContext, - ) -> bool { - self.new_current_lookup( - block_root, - None, - None, - peer_source, - &PeerType { - data: true, - payload: false, - }, - cx, - ) + self.new_current_lookup(block_root, None, None, peer_source, &PeerType::PreGloas, cx) } /// A block or blob triggers the search of a parent. @@ -391,24 +329,10 @@ impl BlockLookups { // Child's peers can serve block, and data + payload if the parent is full. // In Gloas, data and payload are coupled: empty blocks have neither. // Pre-Gloas: data is always needed with block, payload is never needed. - let peer_type = if awaiting_parent.is_post_gloas() { - let is_full = self - .single_block_lookups - .values() - .find(|l| l.is_for_block(block_root_to_search)) - .map(|parent| parent.is_full_payload(&awaiting_parent)) - .unwrap_or(false); - PeerType { - data: is_full, - payload: is_full, - } - } else { - PeerType { - data: true, - payload: false, - } + let peer_type = match awaiting_parent.parent_hash() { + Some(parent_hash) => PeerType::PostGloas(parent_hash), + None => PeerType::PreGloas, }; - // `block_root_to_search` is a failed chain check happens inside new_current_lookup self.new_current_lookup(block_root_to_search, None, None, peers, &peer_type, cx) } @@ -421,7 +345,7 @@ impl BlockLookups { &mut self, block_root: Hash256, block_component: Option>, - awaiting_parent: Option, + awaiting_parent: Option, peers: &[PeerId], peer_type: &PeerType, cx: &mut SyncNetworkContext, @@ -436,16 +360,12 @@ impl BlockLookups { if let Some((&lookup_id, lookup)) = self .single_block_lookups .iter_mut() - .find(|(_id, lookup)| lookup.is_for_block(block_root)) + .find(|(_id, lookup)| lookup.block_root() == block_root) { if let Some(block_component) = block_component { - let component_type = block_component.get_type(); let imported = lookup.add_child_components(block_component); if !imported { - debug!( - ?block_root, - component_type, "Lookup child component ignored" - ); + debug!(?block_root, "Lookup child component ignored"); } } @@ -462,7 +382,7 @@ impl BlockLookups { && !self .single_block_lookups .iter() - .any(|(_, lookup)| lookup.is_for_block(awaiting_parent)) + .any(|(_, lookup)| lookup.block_root() == awaiting_parent.parent_root()) { warn!(block_root = ?awaiting_parent, "Ignoring child lookup parent lookup not found"); return false; @@ -477,13 +397,8 @@ impl BlockLookups { // If we know that this lookup has unknown parent (is awaiting a parent lookup to resolve), // signal here to hold processing downloaded data. - let mut lookup = SingleBlockLookup::new( - block_root, - peers, - peer_type, - cx.next_id(), - awaiting_parent.map(AwaitingParent::pre_gloas), - ); + let mut lookup = + SingleBlockLookup::new(block_root, peers, peer_type, cx.next_id(), awaiting_parent); let _guard = lookup.span.clone().entered(); // Add block components to the new request @@ -771,10 +686,7 @@ impl BlockLookups { // Use the data kind to pick a penalty string the peer-scoring tests // distinguish on (blobs vs custody columns). - let penalty_msg = match lookup.data_is_columns() { - Some(true) => "lookup_custody_column_processing_failure", - _ => "lookup_blobs_processing_failure", - }; + let penalty_msg = "lookup_data_processing_failure"; match &e { // No penalization for internal / non-attributable errors @@ -818,7 +730,7 @@ impl BlockLookups { let Some((id, lookup)) = self .single_block_lookups .iter_mut() - .find(|(_, lookup)| lookup.is_for_block(block_root)) + .find(|(_, lookup)| lookup.block_root() == block_root) else { // Ok to ignore gossip process events return; @@ -1111,18 +1023,7 @@ impl BlockLookups { .iter() .find(|(_, l)| l.block_root() == parent_root) { - let peer_type = if awaiting.is_post_gloas() { - let is_full = parent_lookup.is_full_payload(&awaiting); - PeerType { - data: is_full, - payload: is_full, - } - } else { - PeerType { - data: true, - payload: false, - } - }; + let peer_type = PeerType::from_awaiting_parent(awaiting); self.add_peers_to_lookup_and_ancestors(parent_id, peers, &peer_type, cx) } else { Err(format!("Lookup references unknown parent {parent_root:?}")) diff --git a/beacon_node/network/src/sync/block_lookups/single_block_lookup.rs b/beacon_node/network/src/sync/block_lookups/single_block_lookup.rs index dcc9a861b8..89f23b3052 100644 --- a/beacon_node/network/src/sync/block_lookups/single_block_lookup.rs +++ b/beacon_node/network/src/sync/block_lookups/single_block_lookup.rs @@ -10,7 +10,7 @@ use beacon_chain::block_verification_types::AsBlock; use educe::Educe; use lighthouse_network::service::api_types::Id; use parking_lot::RwLock; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::sync::Arc; use std::time::{Duration, Instant}; use store::Hash256; @@ -18,7 +18,7 @@ use strum::IntoStaticStr; use tracing::{Span, debug, debug_span}; use types::data::FixedBlobSidecarList; use types::{ - DataColumnSidecarList, EthSpec, ExecutionBlockHash, ForkName, SignedBeaconBlock, + ChainSpec, DataColumnSidecarList, EthSpec, ExecutionBlockHash, ForkName, SignedBeaconBlock, SignedExecutionPayloadEnvelope, Slot, }; @@ -37,20 +37,6 @@ pub struct AwaitingParent { } impl AwaitingParent { - pub fn pre_gloas(parent_root: Hash256) -> Self { - Self { - parent_root, - parent_hash: None, - } - } - - pub fn post_gloas(parent_root: Hash256, parent_hash: ExecutionBlockHash) -> Self { - Self { - parent_root, - parent_hash: Some(parent_hash), - } - } - pub fn parent_root(&self) -> Hash256 { self.parent_root } @@ -59,8 +45,33 @@ impl AwaitingParent { self.parent_hash } - pub fn is_post_gloas(&self) -> bool { - self.parent_hash.is_some() + pub fn from_block(block: &SignedBeaconBlock) -> Self { + let parent_hash = if let Ok(bid) = block.message().body().signed_execution_payload_bid() { + Some(bid.message.parent_block_hash) + } else { + None + }; + Self { + parent_root: block.message().parent_root(), + parent_hash, + } + } + + pub fn from_block_header( + parent_root: Hash256, + slot: Slot, + spec: &ChainSpec, + ) -> Result { + if spec.fork_name_at_slot::(slot).gloas_enabled() { + Err(format!( + "AwaitingParent can not be created from a Gloas header" + )) + } else { + Ok(Self { + parent_root, + parent_hash: None, + }) + } } } @@ -225,9 +236,13 @@ impl BlockRequest { // === Data request: WaitingForBlock → Downloading → Downloaded → Processing → Complete === #[derive(Debug)] -enum DataRequest { - /// Waiting for block to be downloaded to determine what data is needed - WaitingForBlock, +struct DataRequest { + peers: PeerSet, + state: DataRequestState, +} + +#[derive(Debug)] +enum DataRequestState { /// Data downloading or awaiting download Downloading(DataDownload), /// Data downloaded, waiting for block processing to complete before import @@ -244,21 +259,22 @@ enum DataRequest { Complete, } -impl DataRequest { +impl DataRequestState { fn is_awaiting_event(&self) -> bool { - match self { - DataRequest::Downloading(dl) => dl.is_awaiting_event(), - DataRequest::Processing { .. } => true, + match &self { + Self::Downloading(dl) => dl.is_awaiting_event(), + Self::Processing { .. } => true, _ => false, } } fn peer_group(&self) -> Option<&PeerGroup> { match self { - DataRequest::Downloading(dl) => dl.peek_downloaded_peer_group(), - DataRequest::Downloaded { peer_group, .. } - | DataRequest::Processing { peer_group, .. } => Some(peer_group), - DataRequest::WaitingForBlock | DataRequest::Complete => None, + Self::Downloading(dl) => dl.peek_downloaded_peer_group(), + Self::Downloaded { peer_group, .. } | Self::Processing { peer_group, .. } => { + Some(peer_group) + } + Self::Complete => None, } } } @@ -401,11 +417,15 @@ impl DataDownloadKind { // === Payload request: WaitingForBlock → Downloading → Downloaded → Processing → Complete === +#[derive(Debug)] +struct PayloadRequest { + peers: PeerSet, + state: PayloadRequestState, +} + #[derive(Educe)] #[educe(Debug)] -enum PayloadRequest { - /// Waiting for block to be downloaded to determine if payload is needed - WaitingForBlock, +enum PayloadRequestState { Downloading { block_root: Hash256, state: SingleLookupRequestState>>, @@ -420,16 +440,84 @@ enum PayloadRequest { Complete, } -impl PayloadRequest { +impl PayloadRequestState { fn is_awaiting_event(&self) -> bool { match self { - PayloadRequest::Downloading { state, .. } => state.is_awaiting_event(), - PayloadRequest::Processing { .. } => true, + Self::Downloading { state, .. } => state.is_awaiting_event(), + Self::Processing { .. } => true, _ => false, } } } +impl DataRequestState { + fn new(slot: Slot, block_root: Hash256, expected_blobs: usize, spec: &ChainSpec) -> Self { + let block_fork = spec.fork_name_at_slot::(slot); + + match block_fork { + ForkName::Base | ForkName::Altair | ForkName::Bellatrix | ForkName::Capella => { + Self::Complete + } + ForkName::Deneb | ForkName::Electra => { + if expected_blobs > 0 { + Self::Downloading(DataDownload::Blobs { + block_root, + expected_blobs, + state: SingleLookupRequestState::new(), + }) + } else { + Self::Complete + } + } + ForkName::Fulu => { + if expected_blobs > 0 { + Self::Downloading(DataDownload::Columns { + block_root, + state: SingleLookupRequestState::new(), + }) + } else { + Self::Complete + } + } + ForkName::Gloas => { + if expected_blobs > 0 { + Self::Downloading(DataDownload::Columns { + block_root, + state: SingleLookupRequestState::new(), + }) + // Gloas: data peers start at 0, populated when children arrive + } else { + Self::Complete + } + } + } + } +} + +impl PayloadRequestState { + /// Create payload request based on the downloaded block's content and fork. + fn new(slot: Slot, block_root: Hash256, spec: &ChainSpec) -> Self { + let block_fork = spec.fork_name_at_slot::(slot); + + match block_fork { + ForkName::Base + | ForkName::Altair + | ForkName::Bellatrix + | ForkName::Capella + | ForkName::Deneb + | ForkName::Electra + | ForkName::Fulu => Self::Complete, + ForkName::Gloas => Self::Downloading { + block_root, + state: SingleLookupRequestState::new(), + }, + } + } +} + +type PeerSet = Arc>>; +type GloasChildPeers = Arc>>; + // === SingleBlockLookup — three independent requests === #[derive(Educe)] @@ -442,10 +530,10 @@ pub struct SingleBlockLookup { block_request: BlockRequest, // Data request — starts as WaitingForBlock, set after block downloaded - data_request: DataRequest, + data_request: Option>, // Payload request — starts as WaitingForBlock, set after block downloaded - payload_request: PayloadRequest, + payload_request: Option>, // Peer sets. // @@ -455,13 +543,10 @@ pub struct SingleBlockLookup { // consistency so all three sets plug into the same `add_peer` / `remove_peer` surface. /// Peers for block download (also used for data in pre-Gloas forks). #[educe(Debug(method(fmt_peer_set_as_len)))] - peers: Arc>>, - /// Peers for data download (0 initially for Gloas, shared with block for pre-Gloas). - #[educe(Debug(method(fmt_peer_set_as_len)))] - data_peers: Arc>>, + peers: PeerSet, /// Peers for payload download (0 initially, Gloas only). - #[educe(Debug(method(fmt_peer_set_as_len)))] - payload_peers: Arc>>, + #[educe(Debug(method(fmt_peer_map_as_len)))] + gload_child_peers: GloasChildPeers, // Parent tracking awaiting_parent: Option, @@ -472,6 +557,20 @@ pub struct SingleBlockLookup { failed_processing: u8, } +pub enum PeerType { + PreGloas, + PostGloas(ExecutionBlockHash), +} + +impl PeerType { + pub fn from_awaiting_parent(awaiting_parent: AwaitingParent) -> Self { + match awaiting_parent.parent_hash() { + Some(parent_hash) => Self::PostGloas(parent_hash), + None => Self::PreGloas, + } + } +} + impl SingleBlockLookup { pub fn new( requested_block_root: Hash256, @@ -486,27 +585,24 @@ impl SingleBlockLookup { id = id, ); - let peer_set: HashSet = peers.iter().copied().collect(); - let data_peers = if peer_type.data { - peer_set.clone() - } else { - HashSet::new() - }; - let payload_peers = if peer_type.payload { - peer_set.clone() - } else { - HashSet::new() - }; + let block_peers: PeerSet = Arc::new(RwLock::new(peers.iter().copied().collect())); + let mut gloas_child_peers = HashMap::new(); + + match peer_type { + PeerType::PreGloas => {} + PeerType::PostGloas(execution_hash) => { + gloas_child_peers.insert(*execution_hash, block_peers.clone()); + } + } Self { id, block_root: requested_block_root, block_request: BlockRequest::new(requested_block_root), - data_request: DataRequest::WaitingForBlock, - payload_request: PayloadRequest::WaitingForBlock, - data_peers: Arc::new(RwLock::new(data_peers)), - payload_peers: Arc::new(RwLock::new(payload_peers)), - peers: Arc::new(RwLock::new(peer_set)), + data_request: None, + payload_request: None, + peers: block_peers, + gload_child_peers: Arc::new(RwLock::new(gloas_child_peers)), awaiting_parent, created: Instant::now(), failed_processing: 0, @@ -546,8 +642,8 @@ impl SingleBlockLookup { // Reset to fresh Downloading state with the updated counter self.block_request = BlockRequest::new_with_processing_failures(self.block_root, self.failed_processing); - self.data_request = DataRequest::WaitingForBlock; - self.payload_request = PayloadRequest::WaitingForBlock; + self.data_request = None; + self.payload_request = None; } /// Return the slot of this lookup's block if it's currently cached @@ -579,9 +675,7 @@ impl SingleBlockLookup { pub fn add_child_components(&mut self, block_component: BlockComponent) -> bool { match block_component { BlockComponent::Block(block) => self.block_request.insert_verified_response(block), - BlockComponent::Blob(_) - | BlockComponent::DataColumn(_) - | BlockComponent::PartialDataColumn(_) => { + BlockComponent::Sidecar => { // For now ignore single blobs and columns, as the blob request state assumes all // blobs are attributed to the same peer = the peer serving the remaining blobs. false @@ -589,17 +683,18 @@ impl SingleBlockLookup { } } - /// Check the block root matches the requested block root. - pub fn is_for_block(&self, block_root: Hash256) -> bool { - self.block_root() == block_root - } - /// Returns true if this request is expecting some event to make progress pub fn is_awaiting_event(&self) -> bool { self.awaiting_parent.is_some() || self.block_request.is_awaiting_event() - || self.data_request.is_awaiting_event() - || self.payload_request.is_awaiting_event() + || match &self.data_request { + Some(request) => request.state.is_awaiting_event(), + None => true, + } + || match &self.payload_request { + Some(request) => request.state.is_awaiting_event(), + None => true, + } } /// Returns the block peer if block has been downloaded. Used for peer penalization. @@ -609,22 +704,7 @@ impl SingleBlockLookup { /// Returns custody column peer group if data has been downloaded. Used for peer penalization. pub fn data_peer_group(&self) -> Option<&PeerGroup> { - self.data_request.peer_group() - } - - /// Returns `Some(true)` if the current data request is for custody columns (Fulu/Gloas), - /// `Some(false)` for blobs (Deneb/Electra), `None` when no active data request. Used to - /// pick the right penalty string on processing failure. - pub fn data_is_columns(&self) -> Option { - match &self.data_request { - DataRequest::Downloading(DataDownload::Columns { .. }) => Some(true), - DataRequest::Downloading(DataDownload::Blobs { .. }) => Some(false), - DataRequest::Downloaded { data, .. } => { - Some(matches!(data, DownloadedData::Columns(_))) - } - DataRequest::Processing { kind, .. } => Some(matches!(kind, DataDownloadKind::Columns)), - DataRequest::WaitingForBlock | DataRequest::Complete => None, - } + todo!(); } // -- Main state machine driver -- @@ -692,72 +772,25 @@ impl SingleBlockLookup { let parent_root = block.parent_root(); // Zero hash is the parent of the genesis block — not a real block. - if parent_root != Hash256::ZERO { - let parent_in_fork_choice = cx - .chain - .canonical_head - .fork_choice_read_lock() - .get_block(&parent_root) - .is_some(); - if !parent_in_fork_choice { - let awaiting_parent = if let Ok(bid) = - block.message().body().signed_execution_payload_bid() - { - AwaitingParent::post_gloas( - parent_root, - bid.message.parent_block_hash, - ) - } else { - AwaitingParent::pre_gloas(parent_root) - }; - self.awaiting_parent = Some(awaiting_parent); - return Ok(LookupResult::ParentUnknown { - awaiting_parent, - block_root: self.block_root, - peers: self.all_peers(), - }); - } - // post-gloas we need to also check if the envelope is known to fork choice - if let Ok(child_bid) = block.message().body().signed_execution_payload_bid() - { - // TODO(gloas): after fork-choice: use parent_proto_block.execution_payload_block_hash here - let parent_is_full = cx - .chain - .get_blinded_block(&parent_root) - .map(|maybe_parent_block| { - if let Some(parent_block) = maybe_parent_block { - parent_block - .message() - .body() - .signed_execution_payload_bid() - .map(|parent_bid| { - parent_bid.message.block_hash - == child_bid.message.parent_block_hash - }) - .unwrap_or(false) - } else { - false - } - }) - .unwrap_or(false); - - if parent_is_full - && !cx.chain.envelope_is_known_to_fork_choice(&parent_root) - { - let awaiting_parent = AwaitingParent::post_gloas( - parent_root, - child_bid.message.parent_block_hash, - ); - self.awaiting_parent = Some(awaiting_parent); - return Ok(LookupResult::ParentUnknown { - awaiting_parent, - block_root: self.block_root, - peers: self.all_peers(), - }); - } - } + if parent_root == Hash256::ZERO { + todo!(); } + let Some(parent_in_fork_choice) = cx + .chain + .canonical_head + .fork_choice_read_lock() + .get_block(&parent_root) + else { + let awaiting_parent = AwaitingParent::from_block(block); + self.awaiting_parent = Some(awaiting_parent.clone()); + return Ok(LookupResult::ParentUnknown { + awaiting_parent, + block_root: self.block_root, + peers: self.all_peers(), + }); + }; + let block = block.clone(); let peer = *peer; cx.send_block_for_processing( @@ -779,116 +812,136 @@ impl SingleBlockLookup { // === Data request === loop { match &mut self.data_request { - DataRequest::WaitingForBlock => { + None => { // Prefer a block downloaded by this lookup. Otherwise fall back to the // chain's processing-status cache: the block may already be in the // availability checker via gossip/HTTP API before this lookup downloads // it, and we can still drive the data request in parallel. - let block_metadata = self - .block_request - .peek_block() - .map(|b| (b.slot(), b.num_expected_blobs())) - .or_else(|| match cx.chain.get_block_process_status(&block_root) { + let block = self.block_request.peek_block().cloned().or_else(|| { + match cx.chain.get_block_process_status(&block_root) { BlockProcessStatus::NotValidated(block, _) - | BlockProcessStatus::ExecutionValidated(block) => { - Some((block.slot(), block.num_expected_blobs())) - } + | BlockProcessStatus::ExecutionValidated(block) => Some(block), BlockProcessStatus::Unknown => None, + } + }); + if let Some(block) = block { + let peers = self + .get_data_peers::( + block.slot(), + block.execution_hash(), + cx.spec(), + ) + .map_err(LookupRequestError::InternalError)?; + self.data_request = Some(DataRequest { + peers, + state: DataRequestState::new( + block.slot(), + self.block_root, + block.num_expected_blobs(), + cx.spec(), + ), }); - if let Some((slot, expected_blobs)) = block_metadata { - self.create_data_request(slot, expected_blobs, cx); } else { // Wait for block to be downloaded break; } } - DataRequest::Downloading(dl) => { - // Custody column downloads dispatch against the global synced peer pool - // inside `ActiveCustodyRequest`, not against `data_peers`. Only gate on - // `data_peers` for post-Gloas, where peer sets are strictly partitioned - // and no fallback pool exists. - let has_peers = !self.data_peers.read().is_empty(); - let is_gloas = matches!(dl, DataDownload::Columns { .. }) - && self.awaiting_parent.is_some_and(|a| a.is_post_gloas()); - if has_peers || !is_gloas { - dl.continue_requests(id, self.data_peers.clone(), cx)?; + Some(request) => match &mut request.state { + DataRequestState::Downloading(dl) => { + // Custody column downloads dispatch against the global synced peer pool + // inside `ActiveCustodyRequest`, not against `data_peers`. Only gate on + // `data_peers` for post-Gloas, where peer sets are strictly partitioned + // and no fallback pool exists. + dl.continue_requests(id, request.peers.clone(), cx)?; + + if dl.is_completed() { + // All data already imported (e.g. received via gossip) + request.state = DataRequestState::Complete; + } else if let Some((data, peer_group)) = dl.take_download_result() { + request.state = DataRequestState::Downloaded { data, peer_group }; + } else { + // Wait for data to be downloaded + break; + } } - if dl.is_completed() { - // All data already imported (e.g. received via gossip) - self.data_request = DataRequest::Complete; - } else if let Some((data, peer_group)) = dl.take_download_result() { - self.data_request = DataRequest::Downloaded { data, peer_group }; - } else { - // Wait for data to be downloaded + DataRequestState::Downloaded { data, peer_group } => { + match data { + DownloadedData::Blobs { blobs, .. } => { + cx.send_blobs_for_processing( + id, + self.block_root, + blobs.clone(), + Duration::ZERO, + ) + .map_err(LookupRequestError::SendFailedProcessor)?; + } + DownloadedData::Columns(columns) => { + cx.send_custody_columns_for_processing( + id, + self.block_root, + columns.clone(), + Duration::ZERO, + BlockProcessType::SingleCustodyColumn(id), + ) + .map_err(LookupRequestError::SendFailedProcessor)?; + } + } + let kind = data.kind(); + let peer_group = peer_group.clone(); + request.state = DataRequestState::Processing { kind, peer_group }; + // Processing needs an async trigger. break; } - } - DataRequest::Downloaded { data, peer_group } => { - match data { - DownloadedData::Blobs { blobs, .. } => { - cx.send_blobs_for_processing( - id, - self.block_root, - blobs.clone(), - Duration::ZERO, - ) - .map_err(LookupRequestError::SendFailedProcessor)?; - } - DownloadedData::Columns(columns) => { - cx.send_custody_columns_for_processing( - id, - self.block_root, - columns.clone(), - Duration::ZERO, - BlockProcessType::SingleCustodyColumn(id), - ) - .map_err(LookupRequestError::SendFailedProcessor)?; - } - } - let kind = data.kind(); - let peer_group = peer_group.clone(); - self.data_request = DataRequest::Processing { kind, peer_group }; - // Processing needs an async trigger. - break; - } - DataRequest::Processing { .. } | DataRequest::Complete => break, + DataRequestState::Processing { .. } | DataRequestState::Complete => break, + }, } } // === Payload request === loop { match &mut self.payload_request { - PayloadRequest::WaitingForBlock => { + None => { // Same fallback as the data stream: the block may be in the availability // checker via gossip before this lookup downloads it. - let block_metadata = self - .block_request - .peek_block() - .map(|b| (b.slot(), b.num_expected_blobs())) - .or_else(|| match cx.chain.get_block_process_status(&block_root) { + let block = self.block_request.peek_block().cloned().or_else(|| { + match cx.chain.get_block_process_status(&block_root) { BlockProcessStatus::NotValidated(block, _) - | BlockProcessStatus::ExecutionValidated(block) => { - Some((block.slot(), block.num_expected_blobs())) - } + | BlockProcessStatus::ExecutionValidated(block) => Some(block), BlockProcessStatus::Unknown => None, + } + }); + if let Some(block) = block { + let peers = self + .get_data_peers::( + block.slot(), + block.execution_hash(), + cx.spec(), + ) + .map_err(LookupRequestError::InternalError)?; + self.payload_request = Some(PayloadRequest { + peers, + state: PayloadRequestState::new( + block.slot(), + self.block_root, + cx.spec(), + ), }); - if let Some((slot, expected_blobs)) = block_metadata { - self.create_payload_request(slot, expected_blobs, cx); } else { break; } } - PayloadRequest::Downloading { state, .. } => { - if !self.payload_peers.read().is_empty() { - let peers = self.payload_peers.clone(); - match cx.payload_lookup_request(id, peers, block_root) { + Some(request) => match &mut request.state { + PayloadRequestState::Downloading { state, .. } => { + // This are peers that claim to have imported a block whose parent_hash == + // this block's execution's hash + match cx.payload_lookup_request(id, request.peers.clone(), block_root) { Ok(LookupRequestResult::RequestSent(req_id)) => { state.on_download_start(req_id)?; } Ok(LookupRequestResult::NoRequestNeeded(_reason)) => { // Envelope is already known (e.g. imported by gossip). Skip // download and mark payload stream complete. - self.payload_request = PayloadRequest::Complete; + request.state = PayloadRequestState::Complete; continue; } Ok(LookupRequestResult::Pending(reason)) => { @@ -898,34 +951,46 @@ impl SingleBlockLookup { return Err(LookupRequestError::SendFailedNetwork(e)); } } + if let Some(result) = state.take_download_result() { + request.state = PayloadRequestState::Downloaded { + peer_group: result.peer_group, + }; + } else { + break; + } } - if let Some(result) = state.take_download_result() { - self.payload_request = PayloadRequest::Downloaded { - peer_group: result.peer_group, - }; - } else { + PayloadRequestState::Downloaded { peer_group } => { + if !self.block_request.is_complete() { + break; + } + // TODO(gloas): send payload for processing + // cx.send_payload_for_processing(...) + let peer_group = peer_group.clone(); + request.state = PayloadRequestState::Processing { peer_group }; + // Processing needs an async trigger. break; } - } - PayloadRequest::Downloaded { peer_group } => { - if !self.block_request.is_complete() { - break; - } - // TODO(gloas): send payload for processing - // cx.send_payload_for_processing(...) - let peer_group = peer_group.clone(); - self.payload_request = PayloadRequest::Processing { peer_group }; - // Processing needs an async trigger. - break; - } - PayloadRequest::Processing { .. } | PayloadRequest::Complete => break, + PayloadRequestState::Processing { .. } | PayloadRequestState::Complete => break, + }, } } // === Check completion === if self.block_request.is_complete() - && matches!(self.data_request, DataRequest::Complete) - && matches!(self.payload_request, PayloadRequest::Complete) + && matches!( + self.data_request, + Some(DataRequest { + state: DataRequestState::Complete, + .. + }) + ) + && matches!( + self.payload_request, + Some(PayloadRequest { + state: PayloadRequestState::Complete, + .. + }) + ) { return Ok(LookupResult::Completed); } @@ -933,90 +998,28 @@ impl SingleBlockLookup { Ok(LookupResult::Pending) } - /// Create data request based on the downloaded block's content and fork. - fn create_data_request( - &mut self, - slot: Slot, - expected_blobs: usize, - cx: &SyncNetworkContext, - ) { - let block_fork = cx.chain.spec.fork_name_at_slot::(slot); - - match block_fork { - ForkName::Base | ForkName::Altair | ForkName::Bellatrix | ForkName::Capella => { - self.data_request = DataRequest::Complete; - } - ForkName::Deneb | ForkName::Electra => { - if expected_blobs > 0 { - self.data_request = DataRequest::Downloading(DataDownload::Blobs { - block_root: self.block_root, - expected_blobs, - state: SingleLookupRequestState::new(), - }); - // Pre-Gloas: data peers = block peers (always need data with block) - self.data_peers = self.peers.clone(); - } else { - self.data_request = DataRequest::Complete; - } - } - ForkName::Fulu => { - if expected_blobs > 0 { - self.data_request = DataRequest::Downloading(DataDownload::Columns { - block_root: self.block_root, - state: SingleLookupRequestState::new(), - }); - // Pre-Gloas: data peers = block peers - self.data_peers = self.peers.clone(); - } else { - self.data_request = DataRequest::Complete; - } - } - ForkName::Gloas => { - if expected_blobs > 0 { - self.data_request = DataRequest::Downloading(DataDownload::Columns { - block_root: self.block_root, - state: SingleLookupRequestState::new(), - }); - // Gloas: data peers start at 0, populated when children arrive - } else { - self.data_request = DataRequest::Complete; - } - } - } + fn get_peer_set(&self) -> PeerSet { + todo!(); } - /// Create payload request based on the downloaded block's content and fork. - fn create_payload_request( - &mut self, + fn get_data_peers( + &self, slot: Slot, - expected_blobs: usize, - cx: &SyncNetworkContext, - ) { - let block_fork = cx.chain.spec.fork_name_at_slot::(slot); - - match block_fork { - ForkName::Base - | ForkName::Altair - | ForkName::Bellatrix - | ForkName::Capella - | ForkName::Deneb - | ForkName::Electra - | ForkName::Fulu => { - self.payload_request = PayloadRequest::Complete; - } - ForkName::Gloas => { - if expected_blobs > 0 { - self.payload_request = PayloadRequest::Downloading { - block_root: self.block_root, - state: SingleLookupRequestState::new(), - }; - // Payload peers start at 0, download gated until children provide peers - } else { - // Empty blocks have no payload and no data — both are Done - self.payload_request = PayloadRequest::Complete; - } - } - } + execution_hash: Option, + spec: &ChainSpec, + ) -> Result { + Ok(if spec.fork_name_at_slot::(slot).gloas_enabled() { + let Some(execution_hash) = execution_hash else { + return Err("execution_hash is None post gloas".to_string()); + }; + self.gload_child_peers + .write() + .entry(execution_hash) + .or_default() + .clone() + } else { + self.peers.clone() + }) } // -- Processing result handlers -- @@ -1050,13 +1053,25 @@ impl SingleBlockLookup { result_is_ok: bool, cx: &mut SyncNetworkContext, ) -> Result { - if !matches!(self.data_request, DataRequest::Processing { .. }) { + let Some(request) = &mut self.data_request else { + return Err(LookupRequestError::BadState( + "data processing result but not in Processing state".to_owned(), + )); + }; + + if !matches!( + request, + DataRequest { + state: DataRequestState::Processing { .. }, + .. + } + ) { return Err(LookupRequestError::BadState( "data processing result but not in Processing state".to_owned(), )); } if result_is_ok { - self.data_request = DataRequest::Complete; + request.state = DataRequestState::Complete; self.continue_requests(cx) } else { // Data processing failed — bump the shared processing-failure counter so the @@ -1074,18 +1089,24 @@ impl SingleBlockLookup { result_is_ok: bool, cx: &mut SyncNetworkContext, ) -> Result { - if !matches!(self.payload_request, PayloadRequest::Processing { .. }) { + let Some(request) = &mut self.payload_request else { + return Err(LookupRequestError::BadState( + "payload processing result but not in Processing state".to_owned(), + )); + }; + + if !matches!(request.state, PayloadRequestState::Processing { .. }) { return Err(LookupRequestError::BadState( "payload processing result but not in Processing state".to_owned(), )); } if result_is_ok { - self.payload_request = PayloadRequest::Complete; + request.state = PayloadRequestState::Complete; self.continue_requests(cx) } else { // Bump the shared processing-failure counter to bound retries. self.failed_processing = self.failed_processing.saturating_add(1); - self.payload_request = PayloadRequest::Downloading { + request.state = PayloadRequestState::Downloading { block_root: self.block_root, state: SingleLookupRequestState::new_with_processing_failures( self.failed_processing, @@ -1097,21 +1118,23 @@ impl SingleBlockLookup { /// Reset data request to a fresh download, preserving the download kind. fn reset_data_request(&mut self) { - let kind = match &self.data_request { - DataRequest::Downloading(dl) => match dl { - DataDownload::Blobs { expected_blobs, .. } => Some(DataDownloadKind::Blobs { - expected_blobs: *expected_blobs, - }), - DataDownload::Columns { .. } => Some(DataDownloadKind::Columns), - }, - DataRequest::Downloaded { data, .. } => Some(data.kind()), - DataRequest::Processing { kind, .. } => Some(*kind), - DataRequest::WaitingForBlock | DataRequest::Complete => None, - }; - if let Some(kind) = kind { - self.data_request = DataRequest::Downloading( - kind.into_fresh_download(self.block_root, self.failed_processing), - ); + if let Some(request) = &mut self.data_request { + let kind = match &request.state { + DataRequestState::Downloading(dl) => match dl { + DataDownload::Blobs { expected_blobs, .. } => Some(DataDownloadKind::Blobs { + expected_blobs: *expected_blobs, + }), + DataDownload::Columns { .. } => Some(DataDownloadKind::Columns), + }, + DataRequestState::Downloaded { data, .. } => Some(data.kind()), + DataRequestState::Processing { kind, .. } => Some(*kind), + DataRequestState::Complete => None, + }; + if let Some(kind) = kind { + request.state = DataRequestState::Downloading( + kind.into_fresh_download(self.block_root, self.failed_processing), + ); + } } } @@ -1141,7 +1164,10 @@ impl SingleBlockLookup { result: Result<(FixedBlobSidecarList, PeerGroup, Duration), ()>, cx: &mut SyncNetworkContext, ) -> Result { - let DataRequest::Downloading(DataDownload::Blobs { state, .. }) = &mut self.data_request + let Some(DataRequest { + state: DataRequestState::Downloading(DataDownload::Blobs { state, .. }), + .. + }) = &mut self.data_request else { return Err(LookupRequestError::BadState( "blob response but not downloading blobs".to_owned(), @@ -1158,7 +1184,10 @@ impl SingleBlockLookup { result: Result<(DataColumnSidecarList, PeerGroup, Duration), ()>, cx: &mut SyncNetworkContext, ) -> Result { - let DataRequest::Downloading(DataDownload::Columns { state, .. }) = &mut self.data_request + let Some(DataRequest { + state: DataRequestState::Downloading(DataDownload::Columns { state, .. }), + .. + }) = &mut self.data_request else { return Err(LookupRequestError::BadState( "custody response but not downloading columns".to_owned(), @@ -1183,7 +1212,11 @@ impl SingleBlockLookup { >, cx: &mut SyncNetworkContext, ) -> Result { - let PayloadRequest::Downloading { state, .. } = &mut self.payload_request else { + let Some(PayloadRequest { + state: PayloadRequestState::Downloading { state, .. }, + .. + }) = &mut self.payload_request + else { return Err(LookupRequestError::BadState( "payload envelope response but not downloading payload".to_owned(), )); @@ -1201,12 +1234,25 @@ impl SingleBlockLookup { /// Returns true if the peer was newly inserted into any peer set. pub fn add_peer(&mut self, peer_id: PeerId, peer_type: &PeerType) -> bool { let mut added = false; - if peer_type.payload { - added |= self.payload_peers.write().insert(peer_id); - } - if peer_type.data { - added |= self.data_peers.write().insert(peer_id); + + match peer_type { + PeerType::PostGloas(execution_hash) => { + // This peer claims to have imported a child of this block with parent_hash. We + // can't known if the child is full or empty until we know the payload hash of this + // lookup + added + != self + .gload_child_peers + .write() + .entry(*execution_hash) + .or_default() + .write() + .insert(peer_id); + } + PeerType::PreGloas => {} } + + // Always add to the main block peers added |= self.peers.write().insert(peer_id); added } @@ -1214,8 +1260,9 @@ impl SingleBlockLookup { /// Remove peer from available peers. pub fn remove_peer(&mut self, peer_id: &PeerId) { self.peers.write().remove(peer_id); - self.data_peers.write().remove(peer_id); - self.payload_peers.write().remove(peer_id); + for set in self.gload_child_peers.write().values_mut() { + set.write().remove(peer_id); + } } /// Returns true if this lookup has zero peers @@ -1224,11 +1271,6 @@ impl SingleBlockLookup { } } -pub struct PeerType { - pub data: bool, - pub payload: bool, -} - // === Generic download state machine === #[derive(IntoStaticStr)] @@ -1450,9 +1492,30 @@ impl std::fmt::Debug for DownloadState { } } +impl std::fmt::Display for AwaitingParent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.parent_hash { + Some(parent_hash) => write!(f, "{}/{}", self.parent_root, parent_hash), + None => write!(f, "{}", self.parent_root), + } + } +} + fn fmt_peer_set_as_len( - peer_set: &Arc>>, + peer_set: &PeerSet, f: &mut std::fmt::Formatter, ) -> Result<(), std::fmt::Error> { write!(f, "{}", peer_set.read().len()) } + +fn fmt_peer_map_as_len( + peer_map: &GloasChildPeers, + f: &mut std::fmt::Formatter, +) -> Result<(), std::fmt::Error> { + let total = peer_map + .read() + .values() + .map(|set| set.read().len()) + .sum::(); + write!(f, "{}", total) +} diff --git a/beacon_node/network/src/sync/manager.rs b/beacon_node/network/src/sync/manager.rs index df9e45bdad..9b59e4b0b7 100644 --- a/beacon_node/network/src/sync/manager.rs +++ b/beacon_node/network/src/sync/manager.rs @@ -43,7 +43,7 @@ use super::range_sync::{EPOCHS_PER_BATCH, RangeSync, RangeSyncType}; use crate::network_beacon_processor::{ChainSegmentProcessId, NetworkBeaconProcessor}; use crate::service::NetworkMessage; use crate::status::ToStatusMessage; -use crate::sync::block_lookups::{BlockComponent, DownloadResult}; +use crate::sync::block_lookups::{AwaitingParent, BlockComponent, DownloadResult}; use crate::sync::custody_backfill_sync::CustodyBackFillSync; use crate::sync::network_context::{PeerGroup, RpcResponseResult}; use beacon_chain::block_verification_types::AsBlock; @@ -71,7 +71,7 @@ use strum::IntoStaticStr; use tokio::sync::mpsc; use tracing::{debug, error, info, trace}; use types::{ - BlobSidecar, DataColumnSidecar, EthSpec, ForkContext, Hash256, SignedBeaconBlock, + BlobSidecar, ChainSpec, DataColumnSidecar, EthSpec, ForkContext, Hash256, SignedBeaconBlock, SignedExecutionPayloadEnvelope, Slot, }; @@ -142,14 +142,8 @@ pub enum SyncMessage { /// A block with an unknown parent has been received. UnknownParentBlock(PeerId, Arc>, Hash256), - /// A blob with an unknown parent has been received. - UnknownParentBlob(PeerId, Arc>), - - /// A data column with an unknown parent has been received. - UnknownParentDataColumn(PeerId, Arc>), - - /// A partial data column with an unknown parent has been received. - UnknownParentPartialDataColumn { + /// A sidecar with an unknown parent has been received. + UnknownParentSidecarHeader { peer_id: PeerId, block_root: Hash256, parent_root: Hash256, @@ -874,8 +868,8 @@ impl SyncManager { self.handle_unknown_parent( peer_id, block_root, - parent_root, block_slot, + AwaitingParent::from_block(&block), BlockComponent::Block(DownloadResult { value: block.block_cloned(), block_root, @@ -884,97 +878,34 @@ impl SyncManager { }), ); } - SyncMessage::UnknownParentBlob(peer_id, blob) => { - let blob_slot = blob.slot(); - let block_root = blob.block_root(); - let parent_root = blob.block_parent_root(); - debug!(%block_root, %parent_root, "Received unknown parent blob message"); - self.handle_unknown_parent( - peer_id, - block_root, - parent_root, - blob_slot, - BlockComponent::Blob(DownloadResult { - value: parent_root, - block_root, - seen_timestamp: self.chain.slot_clock.now_duration().unwrap_or_default(), - peer_group: PeerGroup::from_single(peer_id), - }), - ); - } - SyncMessage::UnknownParentDataColumn(peer_id, data_column) => { - let data_column_slot = data_column.slot(); - let block_root = data_column.block_root(); - match data_column.as_ref() { - DataColumnSidecar::Fulu(column) => { - let parent_root = column.block_parent_root(); - debug!(%block_root, %parent_root, "Received unknown parent data column message"); - self.handle_unknown_parent( - peer_id, - block_root, - parent_root, - data_column_slot, - BlockComponent::DataColumn(DownloadResult { - value: parent_root, - block_root, - seen_timestamp: self - .chain - .slot_clock - .now_duration() - .unwrap_or_default(), - peer_group: PeerGroup::from_single(peer_id), - }), - ); - } - // In Gloas, data columns identify the beacon block root but do not carry - // parent root. Treat as an unknown block-root trigger (attestation-style). - // The peer is marked as data-capable since it sent us a data column. - DataColumnSidecar::Gloas(_) => { - match self.should_search_for_block(Some(data_column_slot), &peer_id) { - Ok(_) => { - if self.block_lookups.search_unknown_block_with_data_peer( - block_root, - &[peer_id], - &mut self.network, - ) { - debug!( - ?block_root, - "Created unknown block lookup from Gloas data column" - ); - } else { - debug!(?block_root, "No lookup created from Gloas data column"); - } - } - Err(reason) => { - debug!( - %block_root, - reason, - "Ignoring Gloas data column unknown block request" - ); - } - } - } - } - } - SyncMessage::UnknownParentPartialDataColumn { + SyncMessage::UnknownParentSidecarHeader { peer_id, block_root, parent_root, slot, } => { - debug!(%block_root, %parent_root, "Received unknown parent partial column message"); - self.handle_unknown_parent( - peer_id, - block_root, + debug!(%block_root, %parent_root, "Received unknown parent sidecar message"); + match AwaitingParent::from_block_header::( parent_root, slot, - BlockComponent::PartialDataColumn(DownloadResult { - value: parent_root, - block_root, - seen_timestamp: self.chain.slot_clock.now_duration().unwrap_or_default(), - peer_group: PeerGroup::from_single(peer_id), - }), - ); + self.spec(), + ) { + Ok(awaiting_parent) => { + self.handle_unknown_parent( + peer_id, + block_root, + slot, + awaiting_parent, + BlockComponent::Sidecar, + ); + } + Err(e) => { + tracing::warn!( + ?e, + "Sent UnknownParentSidecarHeader with post-Gloas sidecar" + ); + } + } } SyncMessage::UnknownBlockHashFromAttestation(peer_id, block_root) => { if !self.notified_unknown_roots.contains(&(peer_id, block_root)) { @@ -1054,8 +985,8 @@ impl SyncManager { &mut self, peer_id: PeerId, block_root: Hash256, - parent_root: Hash256, slot: Slot, + awaiting_parent: AwaitingParent, block_component: BlockComponent, ) { match self.should_search_for_block(Some(slot), &peer_id) { @@ -1063,6 +994,7 @@ impl SyncManager { if self.block_lookups.search_child_and_parent( block_root, block_component, + awaiting_parent, peer_id, &mut self.network, ) { @@ -1070,13 +1002,18 @@ impl SyncManager { } else { debug!( ?block_root, - ?parent_root, + %awaiting_parent, "No lookup created for child and parent" ); } } Err(reason) => { - debug!(%block_root, %parent_root, reason, "Ignoring unknown parent request"); + debug!( + %block_root, + %awaiting_parent, + reason, + "Ignoring unknown parent request" + ); } } } @@ -1526,6 +1463,10 @@ impl SyncManager { } } } + + fn spec(&self) -> &ChainSpec { + &self.network_globals().spec + } } impl From> for BlockProcessingResult { diff --git a/beacon_node/network/src/sync/network_context.rs b/beacon_node/network/src/sync/network_context.rs index 9c11a317b7..326e2e89ad 100644 --- a/beacon_node/network/src/sync/network_context.rs +++ b/beacon_node/network/src/sync/network_context.rs @@ -55,8 +55,9 @@ use tokio::sync::mpsc; use tracing::{Span, debug, debug_span, error, warn}; use types::data::FixedBlobSidecarList; use types::{ - BlobSidecar, BlockImportSource, ColumnIndex, DataColumnSidecar, DataColumnSidecarList, EthSpec, - ForkContext, Hash256, SignedBeaconBlock, SignedExecutionPayloadEnvelope, Slot, + BlobSidecar, BlockImportSource, ChainSpec, ColumnIndex, DataColumnSidecar, + DataColumnSidecarList, EthSpec, ForkContext, Hash256, SignedBeaconBlock, + SignedExecutionPayloadEnvelope, Slot, }; pub mod custody; @@ -315,6 +316,10 @@ impl SyncNetworkContext { } } + pub fn spec(&self) -> &ChainSpec { + &self.chain.spec + } + pub fn send_sync_message(&mut self, sync_message: SyncMessage) { self.network_beacon_processor .send_sync_message(sync_message); diff --git a/consensus/fork_choice/src/fork_choice.rs b/consensus/fork_choice/src/fork_choice.rs index 593aa27915..876308c395 100644 --- a/consensus/fork_choice/src/fork_choice.rs +++ b/consensus/fork_choice/src/fork_choice.rs @@ -1522,6 +1522,15 @@ where .map_err(Error::ProtoArrayStringError) } + /// Returns whether the execution payload for the block has been received. + /// + /// Returns `false` for pre-Gloas blocks, unknown blocks, or blocks that are not + /// descendants of the finalized root. + pub fn is_payload_received(&self, block_root: &Hash256) -> bool { + self.is_finalized_checkpoint_or_descendant(*block_root) + && self.proto_array.is_payload_received(block_root) + } + /// Returns an `ExecutionStatus` if the block is known **and** a descendant of the finalized root. pub fn get_block_execution_status(&self, block_root: &Hash256) -> Option { if self.is_finalized_checkpoint_or_descendant(*block_root) { diff --git a/consensus/types/src/block/signed_beacon_block.rs b/consensus/types/src/block/signed_beacon_block.rs index 76bb9a09db..c7d6efe805 100644 --- a/consensus/types/src/block/signed_beacon_block.rs +++ b/consensus/types/src/block/signed_beacon_block.rs @@ -13,7 +13,7 @@ use tree_hash::TreeHash; use tree_hash_derive::TreeHash; use crate::{ - ExecutionBlockHash, + ExecPayload, ExecutionBlockHash, block::{ BLOB_KZG_COMMITMENTS_INDEX, BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockBellatrix, BeaconBlockBodyBellatrix, BeaconBlockBodyCapella, @@ -355,6 +355,16 @@ impl> SignedBeaconBlock .unwrap_or(0) } + pub fn execution_hash(&self) -> Option { + if let Ok(bid) = self.message().body().signed_execution_payload_bid() { + return Some(bid.message.block_hash); + } + if let Ok(payload) = self.message().body().execution_payload() { + return Some(payload.block_hash()); + } + None + } + /// Used for displaying commitments in logs. pub fn commitments_formatted(&self) -> String { let Ok(commitments) = self.message().body().blob_kzg_commitments() else {