Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0b3989c
Consumer ancestor blocks in lookup sync
dapplion Jun 11, 2026
ebe6bab
Implement beacon_blocks_by_head requester for block lookups
dapplion Jun 11, 2026
32e9b22
Serialize peer supported_protocols instead of skipping
dapplion Jun 11, 2026
ad0bcf9
Extract rpc_protocol_from_id helper and unit test it
dapplion Jun 11, 2026
0c4de40
Address review: iterator-based from_vec, if-let chain check, trim test
dapplion Jun 11, 2026
2662295
Address review: split send fns, build AncestorBlocks in network_context
dapplion Jun 11, 2026
77f660a
Minimize peer-selection diff: keep flat tuple + min()
dapplion Jun 11, 2026
21fec2e
Gate lookup peer selection on per-protocol concurrent request limit
dapplion Jun 11, 2026
41dfc46
Use precomputed per-protocol request-count maps for peer selection
dapplion Jun 11, 2026
00f662d
Refactor: next_id in send fns, ActiveRequestsPerPeer, AncestorBlocks …
dapplion Jun 11, 2026
06b5897
Restore unstable comments in send_block_by_root_request; inline req_id
dapplion Jun 11, 2026
5bd5df0
Treat already-imported blocks as parent-imported; finality conflict h…
dapplion Jun 11, 2026
c44b5f8
Thread known ancestor blocks through parent-lookup creation
dapplion Jun 11, 2026
307ed67
Test: one beacon_blocks_by_head request fetches a whole ancestor chain
dapplion Jun 11, 2026
6062cde
Treat genesis/anchor blocks as parent-imported
dapplion Jun 11, 2026
48b967f
Run the lookup test suite against both by_root and by_head peers
dapplion Jun 11, 2026
3f743c2
Dynamic beacon_blocks_by_head count: lone lookups fetch a few, chains…
dapplion Jun 11, 2026
5ddee3a
Use the chain count for the by-head escalation test depth
dapplion Jun 11, 2026
9b7498a
Rename PeerSupport enum to ByHeadSupport { Supported, Unsupported }
dapplion Jun 11, 2026
d8f5dc6
Fall back to other peers after a failed lookup download
dapplion Jun 12, 2026
768ef00
Test peer fallback after a failed lookup download
dapplion Jun 12, 2026
f00393a
Merge remote-tracking branch 'sigp/unstable' into lookup-ancestor-blocks
dapplion Jun 25, 2026
25ef766
Address review: drop peer-fallback, guard future-slot blocks, fold an…
dapplion Jun 25, 2026
b46eefb
Keep anchor special-case out of consensus get_parent_import_status
dapplion Jun 26, 2026
9a95c8c
Address review: BlocksByHead tuple variant, drop request-count tiebre…
dapplion Jun 26, 2026
8a045fd
Use build_chain_and_trigger_last_block helper in by-head count test
dapplion Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6272,6 +6272,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Ok(())
}

pub fn is_parent_imported(&self, block: &SignedBeaconBlock<T::EthSpec>) -> bool {
self.canonical_head
.fork_choice_read_lock()
.is_parent_imported(block)
}

pub fn block_is_known_to_fork_choice(&self, root: &Hash256) -> bool {
self.canonical_head
.fork_choice_read_lock()
Expand Down
7 changes: 7 additions & 0 deletions beacon_node/beacon_chain/src/canonical_head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,13 @@ impl<E: EthSpec> CachedHead<E> {
self.finalized_checkpoint
}

/// Returns the finalized slot from the fork choice finalized checkpoint
pub fn finalized_slot(&self) -> Slot {
self.finalized_checkpoint
.epoch
.start_slot(E::slots_per_epoch())
}

/// Returns the justified checkpoint, as determined by fork choice.
///
/// ## Note
Expand Down
27 changes: 27 additions & 0 deletions beacon_node/lighthouse_network/src/peer_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use peerdb::score::{PeerAction, ReportSource};
pub use peerdb::sync_status::{SyncInfo, SyncStatus};
use std::collections::{HashMap, HashSet, hash_map::Entry};
use std::net::IpAddr;
use std::str::FromStr;
use strum::IntoEnumIterator;
use types::data::{CustodyIndex, compute_subnets_from_custody_group, get_custody_groups};

Expand Down Expand Up @@ -487,6 +488,12 @@ impl<E: EthSpec> PeerManager<E> {
let previous_listening_addresses =
peer_info.set_listening_addresses(info.listen_addrs.clone());
peer_info.set_client(peerdb::client::Client::from_identify_info(info));
peer_info.set_supported_protocols(
info.protocols
.iter()
.filter_map(|protocol| rpc_protocol_from_id(protocol.as_ref()))
.collect(),
);

if previous_kind != peer_info.client().kind
|| *peer_info.listening_addresses() != previous_listening_addresses
Expand Down Expand Up @@ -1706,13 +1713,33 @@ enum ConnectingType {
},
}

/// Parses a libp2p protocol id of the form `/eth2/beacon_chain/req/<name>/<version>/<encoding>`
/// into the corresponding ReqResp [`Protocol`], returning `None` for non-ReqResp protocols.
fn rpc_protocol_from_id(protocol_id: &str) -> Option<Protocol> {
protocol_id
.strip_prefix("/eth2/beacon_chain/req/")
.and_then(|rest| rest.split('/').next())
.and_then(|name| Protocol::from_str(name).ok())
}

#[cfg(test)]
mod tests {
use super::*;
use crate::NetworkConfig;
use crate::rpc::MetaDataV3;
use types::{ChainSpec, ForkName, MainnetEthSpec as E};

#[test]
fn rpc_protocol_from_id_parses_reqresp_protocols() {
// A known ReqResp protocol is parsed from its name segment.
assert_eq!(
rpc_protocol_from_id("/eth2/beacon_chain/req/beacon_blocks_by_head/1/ssz_snappy"),
Some(Protocol::BlocksByHead)
);
// Non-ReqResp protocols are ignored.
assert_eq!(rpc_protocol_from_id("/meshsub/1.1.0"), None);
}

async fn build_peer_manager(target_peer_count: usize) -> PeerManager<E> {
build_peer_manager_with_trusted_peers(vec![], target_peer_count).await
}
Expand Down
11 changes: 11 additions & 0 deletions beacon_node/lighthouse_network/src/peer_manager/peerdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,17 @@ impl<E: EthSpec> PeerDB<E> {
.ok_or_else(|| "Cannot set custody subnets, peer not found".to_string())
}

pub fn __set_supported_protocols(
&mut self,
peer_id: &PeerId,
supported_protocols: HashSet<crate::rpc::Protocol>,
) -> Result<(), String> {
self.peers
.get_mut(peer_id)
.map(|info| info.set_supported_protocols(supported_protocols))
.ok_or_else(|| "Cannot set supported protocols, peer not found".to_string())
}

/// The connection state of the peer has been changed. Modify the peer in the db to ensure all
/// variables are in sync with libp2p.
/// Updating the state can lead to a `BanOperation` which needs to be processed via the peer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::client::Client;
use super::score::{PeerAction, Score, ScoreState};
use super::sync_status::SyncStatus;
use crate::discovery::Eth2Enr;
use crate::rpc::Protocol as RpcProtocol;
use crate::{rpc::MetaData, types::Subnet};
use PeerConnectionStatus::*;
use discv5::Enr;
Expand Down Expand Up @@ -59,6 +60,8 @@ pub struct PeerInfo<E: EthSpec> {
connection_direction: Option<ConnectionDirection>,
/// The enr of the peer, if known.
enr: Option<Enr>,
/// The set of ReqResp protocols the peer advertised via `identify`. Empty until identified.
supported_protocols: HashSet<RpcProtocol>,
}

impl<E: EthSpec> Default for PeerInfo<E> {
Expand All @@ -78,6 +81,7 @@ impl<E: EthSpec> Default for PeerInfo<E> {
is_trusted: false,
connection_direction: None,
enr: None,
supported_protocols: HashSet::new(),
}
}
}
Expand Down Expand Up @@ -172,6 +176,11 @@ impl<E: EthSpec> PeerInfo<E> {
self.enr.as_ref()
}

/// Returns true if the peer advertised support for `protocol` via `identify`.
pub fn supports_protocol(&self, protocol: RpcProtocol) -> bool {
self.supported_protocols.contains(&protocol)
}

/// An iterator over all the subnets this peer is subscribed to.
pub fn subnets(&self) -> impl Iterator<Item = &Subnet> {
self.subnets.iter()
Expand Down Expand Up @@ -400,6 +409,15 @@ impl<E: EthSpec> PeerInfo<E> {
self.client = client
}

/// Replaces the set of ReqResp protocols the peer advertised via `identify`.
// VISIBILITY: The peer manager is able to set the supported protocols
pub(in crate::peer_manager) fn set_supported_protocols(
&mut self,
supported_protocols: HashSet<RpcProtocol>,
) {
self.supported_protocols = supported_protocols;
}

/// Replaces the current listening addresses with those specified, returning the current
/// listening addresses.
// VISIBILITY: The peer manager is able to set the listening addresses
Expand Down
7 changes: 7 additions & 0 deletions beacon_node/lighthouse_network/src/rpc/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,13 @@ pub enum Protocol {
LightClientUpdatesByRange,
}

impl serde::Serialize for Protocol {
/// Serialize as the canonical protocol name (matching `AsRef<str>`/`Display`).
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_ref())
}
}

impl Protocol {
pub(crate) fn terminator(self) -> Option<ResponseTermination> {
match self {
Expand Down
2 changes: 2 additions & 0 deletions beacon_node/lighthouse_network/src/service/api_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ pub struct SingleLookupReqId {
pub enum SyncRequestId {
/// Request searching for a block given a hash.
SingleBlock { id: SingleLookupReqId },
/// Request walking the ancestors of a block given its root, via `beacon_blocks_by_head`.
BlocksByHead(SingleLookupReqId),
/// Request searching for a payload envelope given a hash.
SinglePayloadEnvelope { id: SingleLookupReqId },
/// Request searching for a set of data columns given a hash and list of column indices.
Expand Down
29 changes: 25 additions & 4 deletions beacon_node/network/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,10 +355,8 @@ impl<T: BeaconChainTypes> Router<T> {
Response::PayloadEnvelopesByRange(envelope) => {
self.on_payload_envelopes_by_range_response(peer_id, app_request_id, envelope);
}
// Lighthouse currently only serves BlocksByHead and does not issue it as a client,
// so receiving a response is unexpected. Drop it without crashing.
Response::BlocksByHead(_) => {
debug!("BlocksByHead response received but not requested by lighthouse");
Response::BlocksByHead(beacon_block) => {
self.on_blocks_by_head_response(peer_id, app_request_id, beacon_block);
}
// Light client responses should not be received
Response::LightClientBootstrap(_)
Expand Down Expand Up @@ -717,6 +715,29 @@ impl<T: BeaconChainTypes> Router<T> {
});
}

/// Handle a `BlocksByHead` response from the peer.
/// A `beacon_block` behaves as a stream which is terminated on a `None` response.
pub fn on_blocks_by_head_response(
&mut self,
peer_id: PeerId,
app_request_id: AppRequestId,
beacon_block: Option<Arc<SignedBeaconBlock<T::EthSpec>>>,
) {
let sync_request_id = match app_request_id {
AppRequestId::Sync(id @ SyncRequestId::BlocksByHead { .. }) => id,
other => {
crit!(request = ?other, "BlocksByHead response on incorrect request");
return;
}
};

self.send_to_sync(SyncMessage::RpcBlock {
peer_id,
sync_request_id,
beacon_block,
});
}

/// Handle a `DataColumnsByRoot` response from the peer.
pub fn on_data_columns_by_root_response(
&mut self,
Expand Down
Loading